• 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.CheckResult;
27 import android.annotation.ColorInt;
28 import android.annotation.DrawableRes;
29 import android.annotation.FloatRange;
30 import android.annotation.IntDef;
31 import android.annotation.IntRange;
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.annotation.Px;
35 import android.annotation.RequiresPermission;
36 import android.annotation.Size;
37 import android.annotation.StringRes;
38 import android.annotation.StyleRes;
39 import android.annotation.UnsupportedAppUsage;
40 import android.annotation.XmlRes;
41 import android.app.Activity;
42 import android.app.PendingIntent;
43 import android.app.assist.AssistStructure;
44 import android.content.ClipData;
45 import android.content.ClipDescription;
46 import android.content.ClipboardManager;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.content.UndoManager;
50 import android.content.pm.PackageManager;
51 import android.content.res.ColorStateList;
52 import android.content.res.CompatibilityInfo;
53 import android.content.res.Configuration;
54 import android.content.res.Resources;
55 import android.content.res.TypedArray;
56 import android.content.res.XmlResourceParser;
57 import android.graphics.BaseCanvas;
58 import android.graphics.BlendMode;
59 import android.graphics.Canvas;
60 import android.graphics.Insets;
61 import android.graphics.Paint;
62 import android.graphics.Paint.FontMetricsInt;
63 import android.graphics.Path;
64 import android.graphics.PorterDuff;
65 import android.graphics.Rect;
66 import android.graphics.RectF;
67 import android.graphics.Typeface;
68 import android.graphics.drawable.Drawable;
69 import android.graphics.fonts.FontStyle;
70 import android.graphics.fonts.FontVariationAxis;
71 import android.icu.text.DecimalFormatSymbols;
72 import android.os.AsyncTask;
73 import android.os.Build;
74 import android.os.Build.VERSION_CODES;
75 import android.os.Bundle;
76 import android.os.LocaleList;
77 import android.os.Parcel;
78 import android.os.Parcelable;
79 import android.os.ParcelableParcel;
80 import android.os.Process;
81 import android.os.SystemClock;
82 import android.os.UserHandle;
83 import android.provider.Settings;
84 import android.text.BoringLayout;
85 import android.text.DynamicLayout;
86 import android.text.Editable;
87 import android.text.GetChars;
88 import android.text.GraphicsOperations;
89 import android.text.InputFilter;
90 import android.text.InputType;
91 import android.text.Layout;
92 import android.text.ParcelableSpan;
93 import android.text.PrecomputedText;
94 import android.text.Selection;
95 import android.text.SpanWatcher;
96 import android.text.Spannable;
97 import android.text.SpannableStringBuilder;
98 import android.text.Spanned;
99 import android.text.SpannedString;
100 import android.text.StaticLayout;
101 import android.text.TextDirectionHeuristic;
102 import android.text.TextDirectionHeuristics;
103 import android.text.TextPaint;
104 import android.text.TextUtils;
105 import android.text.TextUtils.TruncateAt;
106 import android.text.TextWatcher;
107 import android.text.method.AllCapsTransformationMethod;
108 import android.text.method.ArrowKeyMovementMethod;
109 import android.text.method.DateKeyListener;
110 import android.text.method.DateTimeKeyListener;
111 import android.text.method.DialerKeyListener;
112 import android.text.method.DigitsKeyListener;
113 import android.text.method.KeyListener;
114 import android.text.method.LinkMovementMethod;
115 import android.text.method.MetaKeyKeyListener;
116 import android.text.method.MovementMethod;
117 import android.text.method.PasswordTransformationMethod;
118 import android.text.method.SingleLineTransformationMethod;
119 import android.text.method.TextKeyListener;
120 import android.text.method.TimeKeyListener;
121 import android.text.method.TransformationMethod;
122 import android.text.method.TransformationMethod2;
123 import android.text.method.WordIterator;
124 import android.text.style.CharacterStyle;
125 import android.text.style.ClickableSpan;
126 import android.text.style.ParagraphStyle;
127 import android.text.style.SpellCheckSpan;
128 import android.text.style.SuggestionSpan;
129 import android.text.style.URLSpan;
130 import android.text.style.UpdateAppearance;
131 import android.text.util.Linkify;
132 import android.util.AttributeSet;
133 import android.util.DisplayMetrics;
134 import android.util.IntArray;
135 import android.util.Log;
136 import android.util.SparseIntArray;
137 import android.util.TypedValue;
138 import android.view.AccessibilityIterators.TextSegmentIterator;
139 import android.view.ActionMode;
140 import android.view.Choreographer;
141 import android.view.ContextMenu;
142 import android.view.DragEvent;
143 import android.view.Gravity;
144 import android.view.HapticFeedbackConstants;
145 import android.view.InputDevice;
146 import android.view.KeyCharacterMap;
147 import android.view.KeyEvent;
148 import android.view.MotionEvent;
149 import android.view.PointerIcon;
150 import android.view.View;
151 import android.view.ViewConfiguration;
152 import android.view.ViewDebug;
153 import android.view.ViewGroup.LayoutParams;
154 import android.view.ViewHierarchyEncoder;
155 import android.view.ViewParent;
156 import android.view.ViewRootImpl;
157 import android.view.ViewStructure;
158 import android.view.ViewTreeObserver;
159 import android.view.accessibility.AccessibilityEvent;
160 import android.view.accessibility.AccessibilityManager;
161 import android.view.accessibility.AccessibilityNodeInfo;
162 import android.view.animation.AnimationUtils;
163 import android.view.autofill.AutofillManager;
164 import android.view.autofill.AutofillValue;
165 import android.view.inputmethod.BaseInputConnection;
166 import android.view.inputmethod.CompletionInfo;
167 import android.view.inputmethod.CorrectionInfo;
168 import android.view.inputmethod.CursorAnchorInfo;
169 import android.view.inputmethod.EditorInfo;
170 import android.view.inputmethod.ExtractedText;
171 import android.view.inputmethod.ExtractedTextRequest;
172 import android.view.inputmethod.InputConnection;
173 import android.view.inputmethod.InputMethodManager;
174 import android.view.inspector.InspectableProperty;
175 import android.view.inspector.InspectableProperty.EnumEntry;
176 import android.view.inspector.InspectableProperty.FlagEntry;
177 import android.view.textclassifier.TextClassification;
178 import android.view.textclassifier.TextClassificationContext;
179 import android.view.textclassifier.TextClassificationManager;
180 import android.view.textclassifier.TextClassifier;
181 import android.view.textclassifier.TextLinks;
182 import android.view.textservice.SpellCheckerSubtype;
183 import android.view.textservice.TextServicesManager;
184 import android.widget.RemoteViews.RemoteView;
185 
186 import com.android.internal.annotations.VisibleForTesting;
187 import com.android.internal.logging.MetricsLogger;
188 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
189 import com.android.internal.util.FastMath;
190 import com.android.internal.util.Preconditions;
191 import com.android.internal.widget.EditableInputConnection;
192 
193 import libcore.util.EmptyArray;
194 
195 import org.xmlpull.v1.XmlPullParserException;
196 
197 import java.io.IOException;
198 import java.lang.annotation.Retention;
199 import java.lang.annotation.RetentionPolicy;
200 import java.lang.ref.WeakReference;
201 import java.util.ArrayList;
202 import java.util.Arrays;
203 import java.util.Locale;
204 import java.util.Objects;
205 import java.util.concurrent.CompletableFuture;
206 import java.util.concurrent.TimeUnit;
207 import java.util.function.Consumer;
208 import java.util.function.Supplier;
209 
210 /**
211  * A user interface element that displays text to the user.
212  * To provide user-editable text, see {@link EditText}.
213  * <p>
214  * The following code sample shows a typical use, with an XML layout
215  * and code to modify the contents of the text view:
216  * </p>
217 
218  * <pre>
219  * &lt;LinearLayout
220        xmlns:android="http://schemas.android.com/apk/res/android"
221        android:layout_width="match_parent"
222        android:layout_height="match_parent"&gt;
223  *    &lt;TextView
224  *        android:id="@+id/text_view_id"
225  *        android:layout_height="wrap_content"
226  *        android:layout_width="wrap_content"
227  *        android:text="@string/hello" /&gt;
228  * &lt;/LinearLayout&gt;
229  * </pre>
230  * <p>
231  * This code sample demonstrates how to modify the contents of the text view
232  * defined in the previous XML layout:
233  * </p>
234  * <pre>
235  * public class MainActivity extends Activity {
236  *
237  *    protected void onCreate(Bundle savedInstanceState) {
238  *         super.onCreate(savedInstanceState);
239  *         setContentView(R.layout.activity_main);
240  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
241  *         helloTextView.setText(R.string.user_greeting);
242  *     }
243  * }
244  * </pre>
245  * <p>
246  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
247  * </p>
248  * <p>
249  * <b>XML attributes</b>
250  * <p>
251  * See {@link android.R.styleable#TextView TextView Attributes},
252  * {@link android.R.styleable#View View Attributes}
253  *
254  * @attr ref android.R.styleable#TextView_text
255  * @attr ref android.R.styleable#TextView_bufferType
256  * @attr ref android.R.styleable#TextView_hint
257  * @attr ref android.R.styleable#TextView_textColor
258  * @attr ref android.R.styleable#TextView_textColorHighlight
259  * @attr ref android.R.styleable#TextView_textColorHint
260  * @attr ref android.R.styleable#TextView_textAppearance
261  * @attr ref android.R.styleable#TextView_textColorLink
262  * @attr ref android.R.styleable#TextView_textFontWeight
263  * @attr ref android.R.styleable#TextView_textSize
264  * @attr ref android.R.styleable#TextView_textScaleX
265  * @attr ref android.R.styleable#TextView_fontFamily
266  * @attr ref android.R.styleable#TextView_typeface
267  * @attr ref android.R.styleable#TextView_textStyle
268  * @attr ref android.R.styleable#TextView_cursorVisible
269  * @attr ref android.R.styleable#TextView_maxLines
270  * @attr ref android.R.styleable#TextView_maxHeight
271  * @attr ref android.R.styleable#TextView_lines
272  * @attr ref android.R.styleable#TextView_height
273  * @attr ref android.R.styleable#TextView_minLines
274  * @attr ref android.R.styleable#TextView_minHeight
275  * @attr ref android.R.styleable#TextView_maxEms
276  * @attr ref android.R.styleable#TextView_maxWidth
277  * @attr ref android.R.styleable#TextView_ems
278  * @attr ref android.R.styleable#TextView_width
279  * @attr ref android.R.styleable#TextView_minEms
280  * @attr ref android.R.styleable#TextView_minWidth
281  * @attr ref android.R.styleable#TextView_gravity
282  * @attr ref android.R.styleable#TextView_scrollHorizontally
283  * @attr ref android.R.styleable#TextView_password
284  * @attr ref android.R.styleable#TextView_singleLine
285  * @attr ref android.R.styleable#TextView_selectAllOnFocus
286  * @attr ref android.R.styleable#TextView_includeFontPadding
287  * @attr ref android.R.styleable#TextView_maxLength
288  * @attr ref android.R.styleable#TextView_shadowColor
289  * @attr ref android.R.styleable#TextView_shadowDx
290  * @attr ref android.R.styleable#TextView_shadowDy
291  * @attr ref android.R.styleable#TextView_shadowRadius
292  * @attr ref android.R.styleable#TextView_autoLink
293  * @attr ref android.R.styleable#TextView_linksClickable
294  * @attr ref android.R.styleable#TextView_numeric
295  * @attr ref android.R.styleable#TextView_digits
296  * @attr ref android.R.styleable#TextView_phoneNumber
297  * @attr ref android.R.styleable#TextView_inputMethod
298  * @attr ref android.R.styleable#TextView_capitalize
299  * @attr ref android.R.styleable#TextView_autoText
300  * @attr ref android.R.styleable#TextView_editable
301  * @attr ref android.R.styleable#TextView_freezesText
302  * @attr ref android.R.styleable#TextView_ellipsize
303  * @attr ref android.R.styleable#TextView_drawableTop
304  * @attr ref android.R.styleable#TextView_drawableBottom
305  * @attr ref android.R.styleable#TextView_drawableRight
306  * @attr ref android.R.styleable#TextView_drawableLeft
307  * @attr ref android.R.styleable#TextView_drawableStart
308  * @attr ref android.R.styleable#TextView_drawableEnd
309  * @attr ref android.R.styleable#TextView_drawablePadding
310  * @attr ref android.R.styleable#TextView_drawableTint
311  * @attr ref android.R.styleable#TextView_drawableTintMode
312  * @attr ref android.R.styleable#TextView_lineSpacingExtra
313  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
314  * @attr ref android.R.styleable#TextView_justificationMode
315  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
316  * @attr ref android.R.styleable#TextView_inputType
317  * @attr ref android.R.styleable#TextView_imeOptions
318  * @attr ref android.R.styleable#TextView_privateImeOptions
319  * @attr ref android.R.styleable#TextView_imeActionLabel
320  * @attr ref android.R.styleable#TextView_imeActionId
321  * @attr ref android.R.styleable#TextView_editorExtras
322  * @attr ref android.R.styleable#TextView_elegantTextHeight
323  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
324  * @attr ref android.R.styleable#TextView_letterSpacing
325  * @attr ref android.R.styleable#TextView_fontFeatureSettings
326  * @attr ref android.R.styleable#TextView_fontVariationSettings
327  * @attr ref android.R.styleable#TextView_breakStrategy
328  * @attr ref android.R.styleable#TextView_hyphenationFrequency
329  * @attr ref android.R.styleable#TextView_autoSizeTextType
330  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
331  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
332  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
333  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
334  * @attr ref android.R.styleable#TextView_textCursorDrawable
335  * @attr ref android.R.styleable#TextView_textSelectHandle
336  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
337  * @attr ref android.R.styleable#TextView_textSelectHandleRight
338  * @attr ref android.R.styleable#TextView_allowUndo
339  * @attr ref android.R.styleable#TextView_enabled
340  */
341 @RemoteView
342 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
343     static final String LOG_TAG = "TextView";
344     static final boolean DEBUG_EXTRACT = false;
345     private static final float[] TEMP_POSITION = new float[2];
346 
347     // Enum for the "typeface" XML parameter.
348     // TODO: How can we get this from the XML instead of hardcoding it here?
349     /** @hide */
350     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
351     @Retention(RetentionPolicy.SOURCE)
352     public @interface XMLTypefaceAttr{}
353     private static final int DEFAULT_TYPEFACE = -1;
354     private static final int SANS = 1;
355     private static final int SERIF = 2;
356     private static final int MONOSPACE = 3;
357 
358     // Enum for the "ellipsize" XML parameter.
359     private static final int ELLIPSIZE_NOT_SET = -1;
360     private static final int ELLIPSIZE_NONE = 0;
361     private static final int ELLIPSIZE_START = 1;
362     private static final int ELLIPSIZE_MIDDLE = 2;
363     private static final int ELLIPSIZE_END = 3;
364     private static final int ELLIPSIZE_MARQUEE = 4;
365 
366     // Bitfield for the "numeric" XML parameter.
367     // TODO: How can we get this from the XML instead of hardcoding it here?
368     private static final int SIGNED = 2;
369     private static final int DECIMAL = 4;
370 
371     /**
372      * Draw marquee text with fading edges as usual
373      */
374     private static final int MARQUEE_FADE_NORMAL = 0;
375 
376     /**
377      * Draw marquee text as ellipsize end while inactive instead of with the fade.
378      * (Useful for devices where the fade can be expensive if overdone)
379      */
380     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
381 
382     /**
383      * Draw marquee text with fading edges because it is currently active/animating.
384      */
385     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
386 
387     @UnsupportedAppUsage
388     private static final int LINES = 1;
389     private static final int EMS = LINES;
390     private static final int PIXELS = 2;
391 
392     private static final RectF TEMP_RECTF = new RectF();
393 
394     /** @hide */
395     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
396     private static final int ANIMATED_SCROLL_GAP = 250;
397 
398     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
399     private static final Spanned EMPTY_SPANNED = new SpannedString("");
400 
401     private static final int CHANGE_WATCHER_PRIORITY = 100;
402 
403     // New state used to change background based on whether this TextView is multiline.
404     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
405 
406     // Accessibility action to share selected text.
407     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
408 
409     /**
410      * @hide
411      */
412     // Accessibility action start id for "process text" actions.
413     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
414 
415     /**
416      * @hide
417      */
418     static final int PROCESS_TEXT_REQUEST_CODE = 100;
419 
420     /**
421      *  Return code of {@link #doKeyDown}.
422      */
423     private static final int KEY_EVENT_NOT_HANDLED = 0;
424     private static final int KEY_EVENT_HANDLED = -1;
425     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
426     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
427 
428     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
429 
430     // System wide time for last cut, copy or text changed action.
431     static long sLastCutCopyOrTextChangedTime;
432 
433     private ColorStateList mTextColor;
434     private ColorStateList mHintTextColor;
435     private ColorStateList mLinkTextColor;
436     @ViewDebug.ExportedProperty(category = "text")
437 
438     /**
439      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
440      */
441     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
442     private int mCurTextColor;
443 
444     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
445     private int mCurHintTextColor;
446     private boolean mFreezesText;
447 
448     @UnsupportedAppUsage
449     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
450     @UnsupportedAppUsage
451     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
452 
453     @UnsupportedAppUsage
454     private float mShadowRadius;
455     @UnsupportedAppUsage
456     private float mShadowDx;
457     @UnsupportedAppUsage
458     private float mShadowDy;
459     private int mShadowColor;
460 
461     private boolean mPreDrawRegistered;
462     private boolean mPreDrawListenerDetached;
463 
464     private TextClassifier mTextClassifier;
465     private TextClassifier mTextClassificationSession;
466     private TextClassificationContext mTextClassificationContext;
467 
468     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
469     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
470     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
471     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
472     // into by the user holding the movement key down) then we shouldn't prevent the focus from
473     // changing.
474     private boolean mPreventDefaultMovement;
475 
476     private TextUtils.TruncateAt mEllipsize;
477 
478     static class Drawables {
479         static final int LEFT = 0;
480         static final int TOP = 1;
481         static final int RIGHT = 2;
482         static final int BOTTOM = 3;
483 
484         static final int DRAWABLE_NONE = -1;
485         static final int DRAWABLE_RIGHT = 0;
486         static final int DRAWABLE_LEFT = 1;
487 
488         final Rect mCompoundRect = new Rect();
489 
490         final Drawable[] mShowing = new Drawable[4];
491 
492         ColorStateList mTintList;
493         BlendMode mBlendMode;
494         boolean mHasTint;
495         boolean mHasTintMode;
496 
497         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
498         Drawable mDrawableLeftInitial, mDrawableRightInitial;
499 
500         boolean mIsRtlCompatibilityMode;
501         boolean mOverride;
502 
503         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
504                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
505 
506         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
507                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
508 
509         int mDrawablePadding;
510 
511         int mDrawableSaved = DRAWABLE_NONE;
512 
Drawables(Context context)513         public Drawables(Context context) {
514             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
515             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
516                     || !context.getApplicationInfo().hasRtlSupport();
517             mOverride = false;
518         }
519 
520         /**
521          * @return {@code true} if this object contains metadata that needs to
522          *         be retained, {@code false} otherwise
523          */
524         public boolean hasMetadata() {
525             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
526         }
527 
528         /**
529          * Updates the list of displayed drawables to account for the current
530          * layout direction.
531          *
532          * @param layoutDirection the current layout direction
533          * @return {@code true} if the displayed drawables changed
534          */
535         public boolean resolveWithLayoutDirection(int layoutDirection) {
536             final Drawable previousLeft = mShowing[Drawables.LEFT];
537             final Drawable previousRight = mShowing[Drawables.RIGHT];
538 
539             // First reset "left" and "right" drawables to their initial values
540             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
541             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
542 
543             if (mIsRtlCompatibilityMode) {
544                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
545                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
546                     mShowing[Drawables.LEFT] = mDrawableStart;
547                     mDrawableSizeLeft = mDrawableSizeStart;
548                     mDrawableHeightLeft = mDrawableHeightStart;
549                 }
550                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
551                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
552                     mShowing[Drawables.RIGHT] = mDrawableEnd;
553                     mDrawableSizeRight = mDrawableSizeEnd;
554                     mDrawableHeightRight = mDrawableHeightEnd;
555                 }
556             } else {
557                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
558                 // drawable if and only if they have been defined
559                 switch(layoutDirection) {
560                     case LAYOUT_DIRECTION_RTL:
561                         if (mOverride) {
562                             mShowing[Drawables.RIGHT] = mDrawableStart;
563                             mDrawableSizeRight = mDrawableSizeStart;
564                             mDrawableHeightRight = mDrawableHeightStart;
565 
566                             mShowing[Drawables.LEFT] = mDrawableEnd;
567                             mDrawableSizeLeft = mDrawableSizeEnd;
568                             mDrawableHeightLeft = mDrawableHeightEnd;
569                         }
570                         break;
571 
572                     case LAYOUT_DIRECTION_LTR:
573                     default:
574                         if (mOverride) {
575                             mShowing[Drawables.LEFT] = mDrawableStart;
576                             mDrawableSizeLeft = mDrawableSizeStart;
577                             mDrawableHeightLeft = mDrawableHeightStart;
578 
579                             mShowing[Drawables.RIGHT] = mDrawableEnd;
580                             mDrawableSizeRight = mDrawableSizeEnd;
581                             mDrawableHeightRight = mDrawableHeightEnd;
582                         }
583                         break;
584                 }
585             }
586 
587             applyErrorDrawableIfNeeded(layoutDirection);
588 
589             return mShowing[Drawables.LEFT] != previousLeft
590                     || mShowing[Drawables.RIGHT] != previousRight;
591         }
592 
593         public void setErrorDrawable(Drawable dr, TextView tv) {
594             if (mDrawableError != dr && mDrawableError != null) {
595                 mDrawableError.setCallback(null);
596             }
597             mDrawableError = dr;
598 
599             if (mDrawableError != null) {
600                 final Rect compoundRect = mCompoundRect;
601                 final int[] state = tv.getDrawableState();
602 
603                 mDrawableError.setState(state);
604                 mDrawableError.copyBounds(compoundRect);
605                 mDrawableError.setCallback(tv);
606                 mDrawableSizeError = compoundRect.width();
607                 mDrawableHeightError = compoundRect.height();
608             } else {
609                 mDrawableSizeError = mDrawableHeightError = 0;
610             }
611         }
612 
613         private void applyErrorDrawableIfNeeded(int layoutDirection) {
614             // first restore the initial state if needed
615             switch (mDrawableSaved) {
616                 case DRAWABLE_LEFT:
617                     mShowing[Drawables.LEFT] = mDrawableTemp;
618                     mDrawableSizeLeft = mDrawableSizeTemp;
619                     mDrawableHeightLeft = mDrawableHeightTemp;
620                     break;
621                 case DRAWABLE_RIGHT:
622                     mShowing[Drawables.RIGHT] = mDrawableTemp;
623                     mDrawableSizeRight = mDrawableSizeTemp;
624                     mDrawableHeightRight = mDrawableHeightTemp;
625                     break;
626                 case DRAWABLE_NONE:
627                 default:
628             }
629             // then, if needed, assign the Error drawable to the correct location
630             if (mDrawableError != null) {
631                 switch(layoutDirection) {
632                     case LAYOUT_DIRECTION_RTL:
633                         mDrawableSaved = DRAWABLE_LEFT;
634 
635                         mDrawableTemp = mShowing[Drawables.LEFT];
636                         mDrawableSizeTemp = mDrawableSizeLeft;
637                         mDrawableHeightTemp = mDrawableHeightLeft;
638 
639                         mShowing[Drawables.LEFT] = mDrawableError;
640                         mDrawableSizeLeft = mDrawableSizeError;
641                         mDrawableHeightLeft = mDrawableHeightError;
642                         break;
643                     case LAYOUT_DIRECTION_LTR:
644                     default:
645                         mDrawableSaved = DRAWABLE_RIGHT;
646 
647                         mDrawableTemp = mShowing[Drawables.RIGHT];
648                         mDrawableSizeTemp = mDrawableSizeRight;
649                         mDrawableHeightTemp = mDrawableHeightRight;
650 
651                         mShowing[Drawables.RIGHT] = mDrawableError;
652                         mDrawableSizeRight = mDrawableSizeError;
653                         mDrawableHeightRight = mDrawableHeightError;
654                         break;
655                 }
656             }
657         }
658     }
659 
660     @UnsupportedAppUsage
661     Drawables mDrawables;
662 
663     @UnsupportedAppUsage
664     private CharWrapper mCharWrapper;
665 
666     @UnsupportedAppUsage(trackingBug = 124050217)
667     private Marquee mMarquee;
668     @UnsupportedAppUsage
669     private boolean mRestartMarquee;
670 
671     private int mMarqueeRepeatLimit = 3;
672 
673     private int mLastLayoutDirection = -1;
674 
675     /**
676      * On some devices the fading edges add a performance penalty if used
677      * extensively in the same layout. This mode indicates how the marquee
678      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
679      */
680     @UnsupportedAppUsage
681     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
682 
683     /**
684      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
685      * the layout that should be used when the mode switches.
686      */
687     @UnsupportedAppUsage
688     private Layout mSavedMarqueeModeLayout;
689 
690     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
691     @ViewDebug.ExportedProperty(category = "text")
692     @UnsupportedAppUsage
693     private @Nullable CharSequence mText;
694     private @Nullable Spannable mSpannable;
695     private @Nullable PrecomputedText mPrecomputed;
696 
697     @UnsupportedAppUsage
698     private CharSequence mTransformed;
699     @UnsupportedAppUsage
700     private BufferType mBufferType = BufferType.NORMAL;
701 
702     private CharSequence mHint;
703     @UnsupportedAppUsage
704     private Layout mHintLayout;
705 
706     private MovementMethod mMovement;
707 
708     private TransformationMethod mTransformation;
709     @UnsupportedAppUsage
710     private boolean mAllowTransformationLengthChange;
711     @UnsupportedAppUsage
712     private ChangeWatcher mChangeWatcher;
713 
714     @UnsupportedAppUsage(trackingBug = 123769451)
715     private ArrayList<TextWatcher> mListeners;
716 
717     // display attributes
718     @UnsupportedAppUsage
719     private final TextPaint mTextPaint;
720     @UnsupportedAppUsage
721     private boolean mUserSetTextScaleX;
722     @UnsupportedAppUsage
723     private Layout mLayout;
724     private boolean mLocalesChanged = false;
725 
726     // True if setKeyListener() has been explicitly called
727     private boolean mListenerChanged = false;
728     // True if internationalized input should be used for numbers and date and time.
729     private final boolean mUseInternationalizedInput;
730     // True if fallback fonts that end up getting used should be allowed to affect line spacing.
731     /* package */ boolean mUseFallbackLineSpacing;
732 
733     @ViewDebug.ExportedProperty(category = "text")
734     @UnsupportedAppUsage
735     private int mGravity = Gravity.TOP | Gravity.START;
736     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
737     private boolean mHorizontallyScrolling;
738 
739     private int mAutoLinkMask;
740     private boolean mLinksClickable = true;
741 
742     @UnsupportedAppUsage
743     private float mSpacingMult = 1.0f;
744     @UnsupportedAppUsage
745     private float mSpacingAdd = 0.0f;
746 
747     private int mBreakStrategy;
748     private int mHyphenationFrequency;
749     private int mJustificationMode;
750 
751     @UnsupportedAppUsage
752     private int mMaximum = Integer.MAX_VALUE;
753     @UnsupportedAppUsage
754     private int mMaxMode = LINES;
755     @UnsupportedAppUsage
756     private int mMinimum = 0;
757     @UnsupportedAppUsage
758     private int mMinMode = LINES;
759 
760     @UnsupportedAppUsage
761     private int mOldMaximum = mMaximum;
762     @UnsupportedAppUsage
763     private int mOldMaxMode = mMaxMode;
764 
765     @UnsupportedAppUsage
766     private int mMaxWidth = Integer.MAX_VALUE;
767     @UnsupportedAppUsage
768     private int mMaxWidthMode = PIXELS;
769     @UnsupportedAppUsage
770     private int mMinWidth = 0;
771     @UnsupportedAppUsage
772     private int mMinWidthMode = PIXELS;
773 
774     @UnsupportedAppUsage
775     private boolean mSingleLine;
776     @UnsupportedAppUsage
777     private int mDesiredHeightAtMeasure = -1;
778     @UnsupportedAppUsage
779     private boolean mIncludePad = true;
780     private int mDeferScroll = -1;
781 
782     // tmp primitives, so we don't alloc them on each draw
783     private Rect mTempRect;
784     private long mLastScroll;
785     private Scroller mScroller;
786     private TextPaint mTempTextPaint;
787 
788     @UnsupportedAppUsage
789     private BoringLayout.Metrics mBoring;
790     @UnsupportedAppUsage
791     private BoringLayout.Metrics mHintBoring;
792     @UnsupportedAppUsage
793     private BoringLayout mSavedLayout;
794     @UnsupportedAppUsage
795     private BoringLayout mSavedHintLayout;
796 
797     @UnsupportedAppUsage
798     private TextDirectionHeuristic mTextDir;
799 
800     private InputFilter[] mFilters = NO_FILTERS;
801 
802     /**
803      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
804      * the same as {@link Process#myUserHandle()}.
805      *
806      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
807      * other apps may need to set this so that the system can use right user's resources and
808      * services such as input methods and spell checkers.</p>
809      *
810      * @see #setTextOperationUser(UserHandle)
811      */
812     @Nullable
813     private UserHandle mTextOperationUser;
814 
815     private volatile Locale mCurrentSpellCheckerLocaleCache;
816 
817     // It is possible to have a selection even when mEditor is null (programmatically set, like when
818     // a link is pressed). These highlight-related fields do not go in mEditor.
819     @UnsupportedAppUsage
820     int mHighlightColor = 0x6633B5E5;
821     private Path mHighlightPath;
822     @UnsupportedAppUsage
823     private final Paint mHighlightPaint;
824     @UnsupportedAppUsage
825     private boolean mHighlightPathBogus = true;
826 
827     // Although these fields are specific to editable text, they are not added to Editor because
828     // they are defined by the TextView's style and are theme-dependent.
829     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
830     int mCursorDrawableRes;
831     private Drawable mCursorDrawable;
832     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
833     // by removing it, but we would break apps targeting <= P that use it by reflection.
834     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
835     int mTextSelectHandleLeftRes;
836     private Drawable mTextSelectHandleLeft;
837     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
838     // by removing it, but we would break apps targeting <= P that use it by reflection.
839     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
840     int mTextSelectHandleRightRes;
841     private Drawable mTextSelectHandleRight;
842     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
843     // by removing it, but we would break apps targeting <= P that use it by reflection.
844     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
845     int mTextSelectHandleRes;
846     private Drawable mTextSelectHandle;
847     int mTextEditSuggestionItemLayout;
848     int mTextEditSuggestionContainerLayout;
849     int mTextEditSuggestionHighlightStyle;
850 
851     /**
852      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
853      * See {@link #createEditorIfNeeded()}.
854      */
855     @UnsupportedAppUsage
856     private Editor mEditor;
857 
858     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
859     private static final int DEVICE_PROVISIONED_NO = 1;
860     private static final int DEVICE_PROVISIONED_YES = 2;
861 
862     /**
863      * Some special options such as sharing selected text should only be shown if the device
864      * is provisioned. Only check the provisioned state once for a given view instance.
865      */
866     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
867 
868     /**
869      * The TextView does not auto-size text (default).
870      */
871     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
872 
873     /**
874      * The TextView scales text size both horizontally and vertically to fit within the
875      * container.
876      */
877     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
878 
879     /** @hide */
880     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
881             AUTO_SIZE_TEXT_TYPE_NONE,
882             AUTO_SIZE_TEXT_TYPE_UNIFORM
883     })
884     @Retention(RetentionPolicy.SOURCE)
885     public @interface AutoSizeTextType {}
886     // Default minimum size for auto-sizing text in scaled pixels.
887     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
888     // Default maximum size for auto-sizing text in scaled pixels.
889     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
890     // Default value for the step size in pixels.
891     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
892     // Use this to specify that any of the auto-size configuration int values have not been set.
893     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
894     // Auto-size text type.
895     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
896     // Specify if auto-size text is needed.
897     private boolean mNeedsAutoSizeText = false;
898     // Step size for auto-sizing in pixels.
899     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
900     // Minimum text size for auto-sizing in pixels.
901     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
902     // Maximum text size for auto-sizing in pixels.
903     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
904     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
905     // when auto-sizing text.
906     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
907     // Specifies whether auto-size should use the provided auto size steps set or if it should
908     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
909     // mAutoSizeStepGranularityInPx.
910     private boolean mHasPresetAutoSizeValues = false;
911 
912     // Autofill-related attributes
913     //
914     // Indicates whether the text was set statically or dynamically, so it can be used to
915     // sanitize autofill requests.
916     private boolean mTextSetFromXmlOrResourceId = false;
917     // Resource id used to set the text.
918     private @StringRes int mTextId = Resources.ID_NULL;
919     //
920     // End of autofill-related attributes
921 
922     /**
923      * Kick-start the font cache for the zygote process (to pay the cost of
924      * initializing freetype for our default font only once).
925      * @hide
926      */
927     public static void preloadFontCache() {
928         Paint p = new Paint();
929         p.setAntiAlias(true);
930         // Ensure that the Typeface is loaded here.
931         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
932         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
933         // since Paint.measureText can not be called without Typeface static initializer.
934         p.setTypeface(Typeface.DEFAULT);
935         // We don't care about the result, just the side-effect of measuring.
936         p.measureText("H");
937     }
938 
939     /**
940      * Interface definition for a callback to be invoked when an action is
941      * performed on the editor.
942      */
943     public interface OnEditorActionListener {
944         /**
945          * Called when an action is being performed.
946          *
947          * @param v The view that was clicked.
948          * @param actionId Identifier of the action.  This will be either the
949          * identifier you supplied, or {@link EditorInfo#IME_NULL
950          * EditorInfo.IME_NULL} if being called due to the enter key
951          * being pressed.
952          * @param event If triggered by an enter key, this is the event;
953          * otherwise, this is null.
954          * @return Return true if you have consumed the action, else false.
955          */
956         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
957     }
958 
959     public TextView(Context context) {
960         this(context, null);
961     }
962 
963     public TextView(Context context, @Nullable AttributeSet attrs) {
964         this(context, attrs, com.android.internal.R.attr.textViewStyle);
965     }
966 
967     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
968         this(context, attrs, defStyleAttr, 0);
969     }
970 
971     @SuppressWarnings("deprecation")
972     public TextView(
973             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
974         super(context, attrs, defStyleAttr, defStyleRes);
975 
976         // TextView is important by default, unless app developer overrode attribute.
977         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
978             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
979         }
980 
981         setTextInternal("");
982 
983         final Resources res = getResources();
984         final CompatibilityInfo compat = res.getCompatibilityInfo();
985 
986         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
987         mTextPaint.density = res.getDisplayMetrics().density;
988         mTextPaint.setCompatibilityScaling(compat.applicationScale);
989 
990         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
991         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
992 
993         mMovement = getDefaultMovementMethod();
994 
995         mTransformation = null;
996 
997         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
998         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
999         attributes.mTextSize = 15;
1000         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1001         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1002         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1003 
1004         final Resources.Theme theme = context.getTheme();
1005 
1006         /*
1007          * Look the appearance up without checking first if it exists because
1008          * almost every TextView has one and it greatly simplifies the logic
1009          * to be able to parse the appearance first and then let specific tags
1010          * for this View override it.
1011          */
1012         TypedArray a = theme.obtainStyledAttributes(attrs,
1013                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1014         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1015                 attrs, a, defStyleAttr, defStyleRes);
1016         TypedArray appearance = null;
1017         int ap = a.getResourceId(
1018                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1019         a.recycle();
1020         if (ap != -1) {
1021             appearance = theme.obtainStyledAttributes(
1022                     ap, com.android.internal.R.styleable.TextAppearance);
1023             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1024                     null, appearance, 0, ap);
1025         }
1026         if (appearance != null) {
1027             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1028             attributes.mFontFamilyExplicit = false;
1029             appearance.recycle();
1030         }
1031 
1032         boolean editable = getDefaultEditable();
1033         CharSequence inputMethod = null;
1034         int numeric = 0;
1035         CharSequence digits = null;
1036         boolean phone = false;
1037         boolean autotext = false;
1038         int autocap = -1;
1039         int buffertype = 0;
1040         boolean selectallonfocus = false;
1041         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1042                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1043         ColorStateList drawableTint = null;
1044         BlendMode drawableTintMode = null;
1045         int drawablePadding = 0;
1046         int ellipsize = ELLIPSIZE_NOT_SET;
1047         boolean singleLine = false;
1048         int maxlength = -1;
1049         CharSequence text = "";
1050         CharSequence hint = null;
1051         boolean password = false;
1052         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1053         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1054         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1055         int inputType = EditorInfo.TYPE_NULL;
1056         a = theme.obtainStyledAttributes(
1057                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1058         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1059                 defStyleAttr, defStyleRes);
1060         int firstBaselineToTopHeight = -1;
1061         int lastBaselineToBottomHeight = -1;
1062         int lineHeight = -1;
1063 
1064         readTextAppearance(context, a, attributes, true /* styleArray */);
1065 
1066         int n = a.getIndexCount();
1067 
1068         // Must set id in a temporary variable because it will be reset by setText()
1069         boolean textIsSetFromXml = false;
1070         for (int i = 0; i < n; i++) {
1071             int attr = a.getIndex(i);
1072 
1073             switch (attr) {
1074                 case com.android.internal.R.styleable.TextView_editable:
1075                     editable = a.getBoolean(attr, editable);
1076                     break;
1077 
1078                 case com.android.internal.R.styleable.TextView_inputMethod:
1079                     inputMethod = a.getText(attr);
1080                     break;
1081 
1082                 case com.android.internal.R.styleable.TextView_numeric:
1083                     numeric = a.getInt(attr, numeric);
1084                     break;
1085 
1086                 case com.android.internal.R.styleable.TextView_digits:
1087                     digits = a.getText(attr);
1088                     break;
1089 
1090                 case com.android.internal.R.styleable.TextView_phoneNumber:
1091                     phone = a.getBoolean(attr, phone);
1092                     break;
1093 
1094                 case com.android.internal.R.styleable.TextView_autoText:
1095                     autotext = a.getBoolean(attr, autotext);
1096                     break;
1097 
1098                 case com.android.internal.R.styleable.TextView_capitalize:
1099                     autocap = a.getInt(attr, autocap);
1100                     break;
1101 
1102                 case com.android.internal.R.styleable.TextView_bufferType:
1103                     buffertype = a.getInt(attr, buffertype);
1104                     break;
1105 
1106                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1107                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1108                     break;
1109 
1110                 case com.android.internal.R.styleable.TextView_autoLink:
1111                     mAutoLinkMask = a.getInt(attr, 0);
1112                     break;
1113 
1114                 case com.android.internal.R.styleable.TextView_linksClickable:
1115                     mLinksClickable = a.getBoolean(attr, true);
1116                     break;
1117 
1118                 case com.android.internal.R.styleable.TextView_drawableLeft:
1119                     drawableLeft = a.getDrawable(attr);
1120                     break;
1121 
1122                 case com.android.internal.R.styleable.TextView_drawableTop:
1123                     drawableTop = a.getDrawable(attr);
1124                     break;
1125 
1126                 case com.android.internal.R.styleable.TextView_drawableRight:
1127                     drawableRight = a.getDrawable(attr);
1128                     break;
1129 
1130                 case com.android.internal.R.styleable.TextView_drawableBottom:
1131                     drawableBottom = a.getDrawable(attr);
1132                     break;
1133 
1134                 case com.android.internal.R.styleable.TextView_drawableStart:
1135                     drawableStart = a.getDrawable(attr);
1136                     break;
1137 
1138                 case com.android.internal.R.styleable.TextView_drawableEnd:
1139                     drawableEnd = a.getDrawable(attr);
1140                     break;
1141 
1142                 case com.android.internal.R.styleable.TextView_drawableTint:
1143                     drawableTint = a.getColorStateList(attr);
1144                     break;
1145 
1146                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1147                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1148                             drawableTintMode);
1149                     break;
1150 
1151                 case com.android.internal.R.styleable.TextView_drawablePadding:
1152                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1153                     break;
1154 
1155                 case com.android.internal.R.styleable.TextView_maxLines:
1156                     setMaxLines(a.getInt(attr, -1));
1157                     break;
1158 
1159                 case com.android.internal.R.styleable.TextView_maxHeight:
1160                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1161                     break;
1162 
1163                 case com.android.internal.R.styleable.TextView_lines:
1164                     setLines(a.getInt(attr, -1));
1165                     break;
1166 
1167                 case com.android.internal.R.styleable.TextView_height:
1168                     setHeight(a.getDimensionPixelSize(attr, -1));
1169                     break;
1170 
1171                 case com.android.internal.R.styleable.TextView_minLines:
1172                     setMinLines(a.getInt(attr, -1));
1173                     break;
1174 
1175                 case com.android.internal.R.styleable.TextView_minHeight:
1176                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1177                     break;
1178 
1179                 case com.android.internal.R.styleable.TextView_maxEms:
1180                     setMaxEms(a.getInt(attr, -1));
1181                     break;
1182 
1183                 case com.android.internal.R.styleable.TextView_maxWidth:
1184                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1185                     break;
1186 
1187                 case com.android.internal.R.styleable.TextView_ems:
1188                     setEms(a.getInt(attr, -1));
1189                     break;
1190 
1191                 case com.android.internal.R.styleable.TextView_width:
1192                     setWidth(a.getDimensionPixelSize(attr, -1));
1193                     break;
1194 
1195                 case com.android.internal.R.styleable.TextView_minEms:
1196                     setMinEms(a.getInt(attr, -1));
1197                     break;
1198 
1199                 case com.android.internal.R.styleable.TextView_minWidth:
1200                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1201                     break;
1202 
1203                 case com.android.internal.R.styleable.TextView_gravity:
1204                     setGravity(a.getInt(attr, -1));
1205                     break;
1206 
1207                 case com.android.internal.R.styleable.TextView_hint:
1208                     hint = a.getText(attr);
1209                     break;
1210 
1211                 case com.android.internal.R.styleable.TextView_text:
1212                     textIsSetFromXml = true;
1213                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1214                     text = a.getText(attr);
1215                     break;
1216 
1217                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1218                     if (a.getBoolean(attr, false)) {
1219                         setHorizontallyScrolling(true);
1220                     }
1221                     break;
1222 
1223                 case com.android.internal.R.styleable.TextView_singleLine:
1224                     singleLine = a.getBoolean(attr, singleLine);
1225                     break;
1226 
1227                 case com.android.internal.R.styleable.TextView_ellipsize:
1228                     ellipsize = a.getInt(attr, ellipsize);
1229                     break;
1230 
1231                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1232                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1233                     break;
1234 
1235                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1236                     if (!a.getBoolean(attr, true)) {
1237                         setIncludeFontPadding(false);
1238                     }
1239                     break;
1240 
1241                 case com.android.internal.R.styleable.TextView_cursorVisible:
1242                     if (!a.getBoolean(attr, true)) {
1243                         setCursorVisible(false);
1244                     }
1245                     break;
1246 
1247                 case com.android.internal.R.styleable.TextView_maxLength:
1248                     maxlength = a.getInt(attr, -1);
1249                     break;
1250 
1251                 case com.android.internal.R.styleable.TextView_textScaleX:
1252                     setTextScaleX(a.getFloat(attr, 1.0f));
1253                     break;
1254 
1255                 case com.android.internal.R.styleable.TextView_freezesText:
1256                     mFreezesText = a.getBoolean(attr, false);
1257                     break;
1258 
1259                 case com.android.internal.R.styleable.TextView_enabled:
1260                     setEnabled(a.getBoolean(attr, isEnabled()));
1261                     break;
1262 
1263                 case com.android.internal.R.styleable.TextView_password:
1264                     password = a.getBoolean(attr, password);
1265                     break;
1266 
1267                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1268                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1269                     break;
1270 
1271                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1272                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1273                     break;
1274 
1275                 case com.android.internal.R.styleable.TextView_inputType:
1276                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1277                     break;
1278 
1279                 case com.android.internal.R.styleable.TextView_allowUndo:
1280                     createEditorIfNeeded();
1281                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1282                     break;
1283 
1284                 case com.android.internal.R.styleable.TextView_imeOptions:
1285                     createEditorIfNeeded();
1286                     mEditor.createInputContentTypeIfNeeded();
1287                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1288                             mEditor.mInputContentType.imeOptions);
1289                     break;
1290 
1291                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1292                     createEditorIfNeeded();
1293                     mEditor.createInputContentTypeIfNeeded();
1294                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1295                     break;
1296 
1297                 case com.android.internal.R.styleable.TextView_imeActionId:
1298                     createEditorIfNeeded();
1299                     mEditor.createInputContentTypeIfNeeded();
1300                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1301                             mEditor.mInputContentType.imeActionId);
1302                     break;
1303 
1304                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1305                     setPrivateImeOptions(a.getString(attr));
1306                     break;
1307 
1308                 case com.android.internal.R.styleable.TextView_editorExtras:
1309                     try {
1310                         setInputExtras(a.getResourceId(attr, 0));
1311                     } catch (XmlPullParserException e) {
1312                         Log.w(LOG_TAG, "Failure reading input extras", e);
1313                     } catch (IOException e) {
1314                         Log.w(LOG_TAG, "Failure reading input extras", e);
1315                     }
1316                     break;
1317 
1318                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1319                     mCursorDrawableRes = a.getResourceId(attr, 0);
1320                     break;
1321 
1322                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1323                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1324                     break;
1325 
1326                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1327                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1328                     break;
1329 
1330                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1331                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1332                     break;
1333 
1334                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1335                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1336                     break;
1337 
1338                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1339                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1340                     break;
1341 
1342                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1343                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1344                     break;
1345 
1346                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1347                     setTextIsSelectable(a.getBoolean(attr, false));
1348                     break;
1349 
1350                 case com.android.internal.R.styleable.TextView_breakStrategy:
1351                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1352                     break;
1353 
1354                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1355                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1356                     break;
1357 
1358                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1359                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1360                     break;
1361 
1362                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1363                     autoSizeStepGranularityInPx = a.getDimension(attr,
1364                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1365                     break;
1366 
1367                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1368                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1369                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1370                     break;
1371 
1372                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1373                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1374                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1375                     break;
1376 
1377                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1378                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1379                     if (autoSizeStepSizeArrayResId > 0) {
1380                         final TypedArray autoSizePresetTextSizes = a.getResources()
1381                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1382                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1383                         autoSizePresetTextSizes.recycle();
1384                     }
1385                     break;
1386                 case com.android.internal.R.styleable.TextView_justificationMode:
1387                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1388                     break;
1389 
1390                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1391                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1392                     break;
1393 
1394                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1395                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1396                     break;
1397 
1398                 case com.android.internal.R.styleable.TextView_lineHeight:
1399                     lineHeight = a.getDimensionPixelSize(attr, -1);
1400                     break;
1401             }
1402         }
1403 
1404         a.recycle();
1405 
1406         BufferType bufferType = BufferType.EDITABLE;
1407 
1408         final int variation =
1409                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1410         final boolean passwordInputType = variation
1411                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1412         final boolean webPasswordInputType = variation
1413                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1414         final boolean numberPasswordInputType = variation
1415                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1416 
1417         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1418         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1419         mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
1420 
1421         if (inputMethod != null) {
1422             Class<?> c;
1423 
1424             try {
1425                 c = Class.forName(inputMethod.toString());
1426             } catch (ClassNotFoundException ex) {
1427                 throw new RuntimeException(ex);
1428             }
1429 
1430             try {
1431                 createEditorIfNeeded();
1432                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1433             } catch (InstantiationException ex) {
1434                 throw new RuntimeException(ex);
1435             } catch (IllegalAccessException ex) {
1436                 throw new RuntimeException(ex);
1437             }
1438             try {
1439                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1440                         ? inputType
1441                         : mEditor.mKeyListener.getInputType();
1442             } catch (IncompatibleClassChangeError e) {
1443                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1444             }
1445         } else if (digits != null) {
1446             createEditorIfNeeded();
1447             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1448             // If no input type was specified, we will default to generic
1449             // text, since we can't tell the IME about the set of digits
1450             // that was selected.
1451             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1452                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1453         } else if (inputType != EditorInfo.TYPE_NULL) {
1454             setInputType(inputType, true);
1455             // If set, the input type overrides what was set using the deprecated singleLine flag.
1456             singleLine = !isMultilineInputType(inputType);
1457         } else if (phone) {
1458             createEditorIfNeeded();
1459             mEditor.mKeyListener = DialerKeyListener.getInstance();
1460             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1461         } else if (numeric != 0) {
1462             createEditorIfNeeded();
1463             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1464                     null,  // locale
1465                     (numeric & SIGNED) != 0,
1466                     (numeric & DECIMAL) != 0);
1467             inputType = mEditor.mKeyListener.getInputType();
1468             mEditor.mInputType = inputType;
1469         } else if (autotext || autocap != -1) {
1470             TextKeyListener.Capitalize cap;
1471 
1472             inputType = EditorInfo.TYPE_CLASS_TEXT;
1473 
1474             switch (autocap) {
1475                 case 1:
1476                     cap = TextKeyListener.Capitalize.SENTENCES;
1477                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1478                     break;
1479 
1480                 case 2:
1481                     cap = TextKeyListener.Capitalize.WORDS;
1482                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1483                     break;
1484 
1485                 case 3:
1486                     cap = TextKeyListener.Capitalize.CHARACTERS;
1487                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1488                     break;
1489 
1490                 default:
1491                     cap = TextKeyListener.Capitalize.NONE;
1492                     break;
1493             }
1494 
1495             createEditorIfNeeded();
1496             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1497             mEditor.mInputType = inputType;
1498         } else if (editable) {
1499             createEditorIfNeeded();
1500             mEditor.mKeyListener = TextKeyListener.getInstance();
1501             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1502         } else if (isTextSelectable()) {
1503             // Prevent text changes from keyboard.
1504             if (mEditor != null) {
1505                 mEditor.mKeyListener = null;
1506                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1507             }
1508             bufferType = BufferType.SPANNABLE;
1509             // So that selection can be changed using arrow keys and touch is handled.
1510             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1511         } else {
1512             if (mEditor != null) mEditor.mKeyListener = null;
1513 
1514             switch (buffertype) {
1515                 case 0:
1516                     bufferType = BufferType.NORMAL;
1517                     break;
1518                 case 1:
1519                     bufferType = BufferType.SPANNABLE;
1520                     break;
1521                 case 2:
1522                     bufferType = BufferType.EDITABLE;
1523                     break;
1524             }
1525         }
1526 
1527         if (mEditor != null) {
1528             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1529                     numberPasswordInputType);
1530         }
1531 
1532         if (selectallonfocus) {
1533             createEditorIfNeeded();
1534             mEditor.mSelectAllOnFocus = true;
1535 
1536             if (bufferType == BufferType.NORMAL) {
1537                 bufferType = BufferType.SPANNABLE;
1538             }
1539         }
1540 
1541         // Set up the tint (if needed) before setting the drawables so that it
1542         // gets applied correctly.
1543         if (drawableTint != null || drawableTintMode != null) {
1544             if (mDrawables == null) {
1545                 mDrawables = new Drawables(context);
1546             }
1547             if (drawableTint != null) {
1548                 mDrawables.mTintList = drawableTint;
1549                 mDrawables.mHasTint = true;
1550             }
1551             if (drawableTintMode != null) {
1552                 mDrawables.mBlendMode = drawableTintMode;
1553                 mDrawables.mHasTintMode = true;
1554             }
1555         }
1556 
1557         // This call will save the initial left/right drawables
1558         setCompoundDrawablesWithIntrinsicBounds(
1559                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1560         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1561         setCompoundDrawablePadding(drawablePadding);
1562 
1563         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1564         // of lines of height are unchanged for multi-line TextViews.
1565         setInputTypeSingleLine(singleLine);
1566         applySingleLine(singleLine, singleLine, singleLine);
1567 
1568         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1569             ellipsize = ELLIPSIZE_END;
1570         }
1571 
1572         switch (ellipsize) {
1573             case ELLIPSIZE_START:
1574                 setEllipsize(TextUtils.TruncateAt.START);
1575                 break;
1576             case ELLIPSIZE_MIDDLE:
1577                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1578                 break;
1579             case ELLIPSIZE_END:
1580                 setEllipsize(TextUtils.TruncateAt.END);
1581                 break;
1582             case ELLIPSIZE_MARQUEE:
1583                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1584                     setHorizontalFadingEdgeEnabled(true);
1585                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1586                 } else {
1587                     setHorizontalFadingEdgeEnabled(false);
1588                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1589                 }
1590                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1591                 break;
1592         }
1593 
1594         final boolean isPassword = password || passwordInputType || webPasswordInputType
1595                 || numberPasswordInputType;
1596         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1597                 && (mEditor.mInputType
1598                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1599                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1600         if (isMonospaceEnforced) {
1601             attributes.mTypefaceIndex = MONOSPACE;
1602         }
1603 
1604         applyTextAppearance(attributes);
1605 
1606         if (isPassword) {
1607             setTransformationMethod(PasswordTransformationMethod.getInstance());
1608         }
1609 
1610         if (maxlength >= 0) {
1611             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1612         } else {
1613             setFilters(NO_FILTERS);
1614         }
1615 
1616         setText(text, bufferType);
1617         if (mText == null) {
1618             mText = "";
1619         }
1620         if (mTransformed == null) {
1621             mTransformed = "";
1622         }
1623 
1624         if (textIsSetFromXml) {
1625             mTextSetFromXmlOrResourceId = true;
1626         }
1627 
1628         if (hint != null) setHint(hint);
1629 
1630         /*
1631          * Views are not normally clickable unless specified to be.
1632          * However, TextViews that have input or movement methods *are*
1633          * clickable by default. By setting clickable here, we implicitly set focusable as well
1634          * if not overridden by the developer.
1635          */
1636         a = context.obtainStyledAttributes(
1637                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1638         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1639         boolean clickable = canInputOrMove || isClickable();
1640         boolean longClickable = canInputOrMove || isLongClickable();
1641         int focusable = getFocusable();
1642 
1643         n = a.getIndexCount();
1644         for (int i = 0; i < n; i++) {
1645             int attr = a.getIndex(i);
1646 
1647             switch (attr) {
1648                 case com.android.internal.R.styleable.View_focusable:
1649                     TypedValue val = new TypedValue();
1650                     if (a.getValue(attr, val)) {
1651                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1652                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1653                                 : val.data;
1654                     }
1655                     break;
1656 
1657                 case com.android.internal.R.styleable.View_clickable:
1658                     clickable = a.getBoolean(attr, clickable);
1659                     break;
1660 
1661                 case com.android.internal.R.styleable.View_longClickable:
1662                     longClickable = a.getBoolean(attr, longClickable);
1663                     break;
1664             }
1665         }
1666         a.recycle();
1667 
1668         // Some apps were relying on the undefined behavior of focusable winning over
1669         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1670         // when starting with EditText and setting only focusable=false). To keep those apps from
1671         // breaking, re-apply the focusable attribute here.
1672         if (focusable != getFocusable()) {
1673             setFocusable(focusable);
1674         }
1675         setClickable(clickable);
1676         setLongClickable(longClickable);
1677 
1678         if (mEditor != null) mEditor.prepareCursorControllers();
1679 
1680         // If not explicitly specified this view is important for accessibility.
1681         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1682             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1683         }
1684 
1685         if (supportsAutoSizeText()) {
1686             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1687                 // If uniform auto-size has been specified but preset values have not been set then
1688                 // replace the auto-size configuration values that have not been specified with the
1689                 // defaults.
1690                 if (!mHasPresetAutoSizeValues) {
1691                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1692 
1693                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1694                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1695                                 TypedValue.COMPLEX_UNIT_SP,
1696                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1697                                 displayMetrics);
1698                     }
1699 
1700                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1701                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1702                                 TypedValue.COMPLEX_UNIT_SP,
1703                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1704                                 displayMetrics);
1705                     }
1706 
1707                     if (autoSizeStepGranularityInPx
1708                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1709                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1710                     }
1711 
1712                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1713                             autoSizeMaxTextSizeInPx,
1714                             autoSizeStepGranularityInPx);
1715                 }
1716 
1717                 setupAutoSizeText();
1718             }
1719         } else {
1720             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1721         }
1722 
1723         if (firstBaselineToTopHeight >= 0) {
1724             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1725         }
1726         if (lastBaselineToBottomHeight >= 0) {
1727             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1728         }
1729         if (lineHeight >= 0) {
1730             setLineHeight(lineHeight);
1731         }
1732     }
1733 
1734     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)1735     private void setTextInternal(@Nullable CharSequence text) {
1736         mText = text;
1737         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
1738         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
1739     }
1740 
1741     /**
1742      * Specify whether this widget should automatically scale the text to try to perfectly fit
1743      * within the layout bounds by using the default auto-size configuration.
1744      *
1745      * @param autoSizeTextType the type of auto-size. Must be one of
1746      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1747      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1748      *
1749      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1750      *
1751      * @attr ref android.R.styleable#TextView_autoSizeTextType
1752      *
1753      * @see #getAutoSizeTextType()
1754      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1755     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1756         if (supportsAutoSizeText()) {
1757             switch (autoSizeTextType) {
1758                 case AUTO_SIZE_TEXT_TYPE_NONE:
1759                     clearAutoSizeConfiguration();
1760                     break;
1761                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1762                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1763                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1764                             TypedValue.COMPLEX_UNIT_SP,
1765                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1766                             displayMetrics);
1767                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1768                             TypedValue.COMPLEX_UNIT_SP,
1769                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1770                             displayMetrics);
1771 
1772                     validateAndSetAutoSizeTextTypeUniformConfiguration(
1773                             autoSizeMinTextSizeInPx,
1774                             autoSizeMaxTextSizeInPx,
1775                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1776                     if (setupAutoSizeText()) {
1777                         autoSizeText();
1778                         invalidate();
1779                     }
1780                     break;
1781                 default:
1782                     throw new IllegalArgumentException(
1783                             "Unknown auto-size text type: " + autoSizeTextType);
1784             }
1785         }
1786     }
1787 
1788     /**
1789      * Specify whether this widget should automatically scale the text to try to perfectly fit
1790      * within the layout bounds. If all the configuration params are valid the type of auto-size is
1791      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1792      *
1793      * @param autoSizeMinTextSize the minimum text size available for auto-size
1794      * @param autoSizeMaxTextSize the maximum text size available for auto-size
1795      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1796      *                                the minimum and maximum text size in order to build the set of
1797      *                                text sizes the system uses to choose from when auto-sizing
1798      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1799      *             possible dimension units
1800      *
1801      * @throws IllegalArgumentException if any of the configuration params are invalid.
1802      *
1803      * @attr ref android.R.styleable#TextView_autoSizeTextType
1804      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1805      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1806      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1807      *
1808      * @see #setAutoSizeTextTypeWithDefaults(int)
1809      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1810      * @see #getAutoSizeMinTextSize()
1811      * @see #getAutoSizeMaxTextSize()
1812      * @see #getAutoSizeStepGranularity()
1813      * @see #getAutoSizeTextAvailableSizes()
1814      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1815     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1816             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1817         if (supportsAutoSizeText()) {
1818             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1819             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1820                     unit, autoSizeMinTextSize, displayMetrics);
1821             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1822                     unit, autoSizeMaxTextSize, displayMetrics);
1823             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1824                     unit, autoSizeStepGranularity, displayMetrics);
1825 
1826             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1827                     autoSizeMaxTextSizeInPx,
1828                     autoSizeStepGranularityInPx);
1829 
1830             if (setupAutoSizeText()) {
1831                 autoSizeText();
1832                 invalidate();
1833             }
1834         }
1835     }
1836 
1837     /**
1838      * Specify whether this widget should automatically scale the text to try to perfectly fit
1839      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1840      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1841      *
1842      * @param presetSizes an {@code int} array of sizes in pixels
1843      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1844      *             the possible dimension units
1845      *
1846      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1847      *
1848      * @attr ref android.R.styleable#TextView_autoSizeTextType
1849      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1850      *
1851      * @see #setAutoSizeTextTypeWithDefaults(int)
1852      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1853      * @see #getAutoSizeMinTextSize()
1854      * @see #getAutoSizeMaxTextSize()
1855      * @see #getAutoSizeTextAvailableSizes()
1856      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1857     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1858         if (supportsAutoSizeText()) {
1859             final int presetSizesLength = presetSizes.length;
1860             if (presetSizesLength > 0) {
1861                 int[] presetSizesInPx = new int[presetSizesLength];
1862 
1863                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1864                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1865                 } else {
1866                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1867                     // Convert all to sizes to pixels.
1868                     for (int i = 0; i < presetSizesLength; i++) {
1869                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
1870                             presetSizes[i], displayMetrics));
1871                     }
1872                 }
1873 
1874                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1875                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
1876                     throw new IllegalArgumentException("None of the preset sizes is valid: "
1877                             + Arrays.toString(presetSizes));
1878                 }
1879             } else {
1880                 mHasPresetAutoSizeValues = false;
1881             }
1882 
1883             if (setupAutoSizeText()) {
1884                 autoSizeText();
1885                 invalidate();
1886             }
1887         }
1888     }
1889 
1890     /**
1891      * Returns the type of auto-size set for this widget.
1892      *
1893      * @return an {@code int} corresponding to one of the auto-size types:
1894      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1895      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1896      *
1897      * @attr ref android.R.styleable#TextView_autoSizeTextType
1898      *
1899      * @see #setAutoSizeTextTypeWithDefaults(int)
1900      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1901      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1902      */
1903     @InspectableProperty(enumMapping = {
1904             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
1905             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
1906     })
1907     @AutoSizeTextType
getAutoSizeTextType()1908     public int getAutoSizeTextType() {
1909         return mAutoSizeTextType;
1910     }
1911 
1912     /**
1913      * @return the current auto-size step granularity in pixels.
1914      *
1915      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1916      *
1917      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1918      */
1919     @InspectableProperty
getAutoSizeStepGranularity()1920     public int getAutoSizeStepGranularity() {
1921         return Math.round(mAutoSizeStepGranularityInPx);
1922     }
1923 
1924     /**
1925      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
1926      *         if auto-size has not been configured this function returns {@code -1}.
1927      *
1928      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1929      *
1930      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1931      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1932      */
1933     @InspectableProperty
getAutoSizeMinTextSize()1934     public int getAutoSizeMinTextSize() {
1935         return Math.round(mAutoSizeMinTextSizeInPx);
1936     }
1937 
1938     /**
1939      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
1940      *         if auto-size has not been configured this function returns {@code -1}.
1941      *
1942      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1943      *
1944      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1945      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1946      */
1947     @InspectableProperty
getAutoSizeMaxTextSize()1948     public int getAutoSizeMaxTextSize() {
1949         return Math.round(mAutoSizeMaxTextSizeInPx);
1950     }
1951 
1952     /**
1953      * @return the current auto-size {@code int} sizes array (in pixels).
1954      *
1955      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1956      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1957      */
getAutoSizeTextAvailableSizes()1958     public int[] getAutoSizeTextAvailableSizes() {
1959         return mAutoSizeTextSizesInPx;
1960     }
1961 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)1962     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
1963         final int textSizesLength = textSizes.length();
1964         final int[] parsedSizes = new int[textSizesLength];
1965 
1966         if (textSizesLength > 0) {
1967             for (int i = 0; i < textSizesLength; i++) {
1968                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
1969             }
1970             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
1971             setupAutoSizeUniformPresetSizesConfiguration();
1972         }
1973     }
1974 
setupAutoSizeUniformPresetSizesConfiguration()1975     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
1976         final int sizesLength = mAutoSizeTextSizesInPx.length;
1977         mHasPresetAutoSizeValues = sizesLength > 0;
1978         if (mHasPresetAutoSizeValues) {
1979             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1980             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
1981             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
1982             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1983         }
1984         return mHasPresetAutoSizeValues;
1985     }
1986 
1987     /**
1988      * If all params are valid then save the auto-size configuration.
1989      *
1990      * @throws IllegalArgumentException if any of the params are invalid
1991      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)1992     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
1993             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
1994         // First validate.
1995         if (autoSizeMinTextSizeInPx <= 0) {
1996             throw new IllegalArgumentException("Minimum auto-size text size ("
1997                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
1998         }
1999 
2000         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2001             throw new IllegalArgumentException("Maximum auto-size text size ("
2002                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2003                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2004         }
2005 
2006         if (autoSizeStepGranularityInPx <= 0) {
2007             throw new IllegalArgumentException("The auto-size step granularity ("
2008                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2009         }
2010 
2011         // All good, persist the configuration.
2012         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2013         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2014         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2015         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2016         mHasPresetAutoSizeValues = false;
2017     }
2018 
clearAutoSizeConfiguration()2019     private void clearAutoSizeConfiguration() {
2020         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2021         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2022         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2023         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2024         mAutoSizeTextSizesInPx = EmptyArray.INT;
2025         mNeedsAutoSizeText = false;
2026     }
2027 
2028     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)2029     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2030         final int presetValuesLength = presetValues.length;
2031         if (presetValuesLength == 0) {
2032             return presetValues;
2033         }
2034         Arrays.sort(presetValues);
2035 
2036         final IntArray uniqueValidSizes = new IntArray();
2037         for (int i = 0; i < presetValuesLength; i++) {
2038             final int currentPresetValue = presetValues[i];
2039 
2040             if (currentPresetValue > 0
2041                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2042                 uniqueValidSizes.add(currentPresetValue);
2043             }
2044         }
2045 
2046         return presetValuesLength == uniqueValidSizes.size()
2047             ? presetValues
2048             : uniqueValidSizes.toArray();
2049     }
2050 
setupAutoSizeText()2051     private boolean setupAutoSizeText() {
2052         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2053             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2054             // not have a predefined set of sizes or if the current sizes array is empty.
2055             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2056                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2057                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2058                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2059                 for (int i = 0; i < autoSizeValuesLength; i++) {
2060                     autoSizeTextSizesInPx[i] = Math.round(
2061                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2062                 }
2063                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2064             }
2065 
2066             mNeedsAutoSizeText = true;
2067         } else {
2068             mNeedsAutoSizeText = false;
2069         }
2070 
2071         return mNeedsAutoSizeText;
2072     }
2073 
parseDimensionArray(TypedArray dimens)2074     private int[] parseDimensionArray(TypedArray dimens) {
2075         if (dimens == null) {
2076             return null;
2077         }
2078         int[] result = new int[dimens.length()];
2079         for (int i = 0; i < result.length; i++) {
2080             result[i] = dimens.getDimensionPixelSize(i, 0);
2081         }
2082         return result;
2083     }
2084 
2085     /**
2086      * @hide
2087      */
2088     @Override
onActivityResult(int requestCode, int resultCode, Intent data)2089     public void onActivityResult(int requestCode, int resultCode, Intent data) {
2090         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2091             if (resultCode == Activity.RESULT_OK && data != null) {
2092                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2093                 if (result != null) {
2094                     if (isTextEditable()) {
2095                         replaceSelectionWithText(result);
2096                         if (mEditor != null) {
2097                             mEditor.refreshTextActionMode();
2098                         }
2099                     } else {
2100                         if (result.length() > 0) {
2101                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2102                                 .show();
2103                         }
2104                     }
2105                 }
2106             } else if (mSpannable != null) {
2107                 // Reset the selection.
2108                 Selection.setSelection(mSpannable, getSelectionEnd());
2109             }
2110         }
2111     }
2112 
2113     /**
2114      * Sets the Typeface taking into account the given attributes.
2115      *
2116      * @param typeface a typeface
2117      * @param familyName family name string, e.g. "serif"
2118      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2119      * @param style a typeface style
2120      * @param weight a weight value for the Typeface or -1 if not specified.
2121      */
setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2122     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2123             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2124             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2125         if (typeface == null && familyName != null) {
2126             // Lookup normal Typeface from system font map.
2127             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2128             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2129         } else if (typeface != null) {
2130             resolveStyleAndSetTypeface(typeface, style, weight);
2131         } else {  // both typeface and familyName is null.
2132             switch (typefaceIndex) {
2133                 case SANS:
2134                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2135                     break;
2136                 case SERIF:
2137                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2138                     break;
2139                 case MONOSPACE:
2140                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2141                     break;
2142                 case DEFAULT_TYPEFACE:
2143                 default:
2144                     resolveStyleAndSetTypeface(null, style, weight);
2145                     break;
2146             }
2147         }
2148     }
2149 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2150     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2151             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2152         if (weight >= 0) {
2153             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2154             final boolean italic = (style & Typeface.ITALIC) != 0;
2155             setTypeface(Typeface.create(typeface, weight, italic));
2156         } else {
2157             setTypeface(typeface, style);
2158         }
2159     }
2160 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2161     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2162         boolean hasRelativeDrawables = (start != null) || (end != null);
2163         if (hasRelativeDrawables) {
2164             Drawables dr = mDrawables;
2165             if (dr == null) {
2166                 mDrawables = dr = new Drawables(getContext());
2167             }
2168             mDrawables.mOverride = true;
2169             final Rect compoundRect = dr.mCompoundRect;
2170             int[] state = getDrawableState();
2171             if (start != null) {
2172                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2173                 start.setState(state);
2174                 start.copyBounds(compoundRect);
2175                 start.setCallback(this);
2176 
2177                 dr.mDrawableStart = start;
2178                 dr.mDrawableSizeStart = compoundRect.width();
2179                 dr.mDrawableHeightStart = compoundRect.height();
2180             } else {
2181                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2182             }
2183             if (end != null) {
2184                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2185                 end.setState(state);
2186                 end.copyBounds(compoundRect);
2187                 end.setCallback(this);
2188 
2189                 dr.mDrawableEnd = end;
2190                 dr.mDrawableSizeEnd = compoundRect.width();
2191                 dr.mDrawableHeightEnd = compoundRect.height();
2192             } else {
2193                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2194             }
2195             resetResolvedDrawables();
2196             resolveDrawables();
2197             applyCompoundDrawableTint();
2198         }
2199     }
2200 
2201     @android.view.RemotableViewMethod
2202     @Override
setEnabled(boolean enabled)2203     public void setEnabled(boolean enabled) {
2204         if (enabled == isEnabled()) {
2205             return;
2206         }
2207 
2208         if (!enabled) {
2209             // Hide the soft input if the currently active TextView is disabled
2210             InputMethodManager imm = getInputMethodManager();
2211             if (imm != null && imm.isActive(this)) {
2212                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2213             }
2214         }
2215 
2216         super.setEnabled(enabled);
2217 
2218         if (enabled) {
2219             // Make sure IME is updated with current editor info.
2220             InputMethodManager imm = getInputMethodManager();
2221             if (imm != null) imm.restartInput(this);
2222         }
2223 
2224         // Will change text color
2225         if (mEditor != null) {
2226             mEditor.invalidateTextDisplayList();
2227             mEditor.prepareCursorControllers();
2228 
2229             // start or stop the cursor blinking as appropriate
2230             mEditor.makeBlink();
2231         }
2232     }
2233 
2234     /**
2235      * Sets the typeface and style in which the text should be displayed,
2236      * and turns on the fake bold and italic bits in the Paint if the
2237      * Typeface that you provided does not have all the bits in the
2238      * style that you specified.
2239      *
2240      * @attr ref android.R.styleable#TextView_typeface
2241      * @attr ref android.R.styleable#TextView_textStyle
2242      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2243     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2244         if (style > 0) {
2245             if (tf == null) {
2246                 tf = Typeface.defaultFromStyle(style);
2247             } else {
2248                 tf = Typeface.create(tf, style);
2249             }
2250 
2251             setTypeface(tf);
2252             // now compute what (if any) algorithmic styling is needed
2253             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2254             int need = style & ~typefaceStyle;
2255             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2256             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2257         } else {
2258             mTextPaint.setFakeBoldText(false);
2259             mTextPaint.setTextSkewX(0);
2260             setTypeface(tf);
2261         }
2262     }
2263 
2264     /**
2265      * Subclasses override this to specify that they have a KeyListener
2266      * by default even if not specifically called for in the XML options.
2267      */
getDefaultEditable()2268     protected boolean getDefaultEditable() {
2269         return false;
2270     }
2271 
2272     /**
2273      * Subclasses override this to specify a default movement method.
2274      */
getDefaultMovementMethod()2275     protected MovementMethod getDefaultMovementMethod() {
2276         return null;
2277     }
2278 
2279     /**
2280      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2281      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2282      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2283      * the return value from this method to Spannable or Editable, respectively.
2284      *
2285      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2286      * should make your own copy first.</p>
2287      *
2288      * @return The text displayed by the text view.
2289      * @attr ref android.R.styleable#TextView_text
2290      */
2291     @ViewDebug.CapturedViewProperty
2292     @InspectableProperty
getText()2293     public CharSequence getText() {
2294         return mText;
2295     }
2296 
2297     /**
2298      * Returns the length, in characters, of the text managed by this TextView
2299      * @return The length of the text managed by the TextView in characters.
2300      */
length()2301     public int length() {
2302         return mText.length();
2303     }
2304 
2305     /**
2306      * Return the text that TextView is displaying as an Editable object. If the text is not
2307      * editable, null is returned.
2308      *
2309      * @see #getText
2310      */
getEditableText()2311     public Editable getEditableText() {
2312         return (mText instanceof Editable) ? (Editable) mText : null;
2313     }
2314 
2315     /**
2316      * @hide
2317      */
2318     @VisibleForTesting
getTransformed()2319     public CharSequence getTransformed() {
2320         return mTransformed;
2321     }
2322 
2323     /**
2324      * Gets the vertical distance between lines of text, in pixels.
2325      * Note that markup within the text can cause individual lines
2326      * to be taller or shorter than this height, and the layout may
2327      * contain additional first-or last-line padding.
2328      * @return The height of one standard line in pixels.
2329      */
2330     @InspectableProperty
getLineHeight()2331     public int getLineHeight() {
2332         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2333     }
2334 
2335     /**
2336      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2337      * This value can be null if the text or width has recently changed.
2338      * @return The Layout that is currently being used to display the text.
2339      */
getLayout()2340     public final Layout getLayout() {
2341         return mLayout;
2342     }
2343 
2344     /**
2345      * @return the {@link android.text.Layout} that is currently being used to
2346      * display the hint text. This can be null.
2347      */
2348     @UnsupportedAppUsage
getHintLayout()2349     final Layout getHintLayout() {
2350         return mHintLayout;
2351     }
2352 
2353     /**
2354      * Retrieve the {@link android.content.UndoManager} that is currently associated
2355      * with this TextView.  By default there is no associated UndoManager, so null
2356      * is returned.  One can be associated with the TextView through
2357      * {@link #setUndoManager(android.content.UndoManager, String)}
2358      *
2359      * @hide
2360      */
getUndoManager()2361     public final UndoManager getUndoManager() {
2362         // TODO: Consider supporting a global undo manager.
2363         throw new UnsupportedOperationException("not implemented");
2364     }
2365 
2366 
2367     /**
2368      * @hide
2369      */
2370     @VisibleForTesting
getEditorForTesting()2371     public final Editor getEditorForTesting() {
2372         return mEditor;
2373     }
2374 
2375     /**
2376      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2377      * done, all edit operations on the TextView will result in appropriate
2378      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2379      * stack.
2380      *
2381      * @param undoManager The {@link android.content.UndoManager} to associate with
2382      * this TextView, or null to clear any existing association.
2383      * @param tag String tag identifying this particular TextView owner in the
2384      * UndoManager.  This is used to keep the correct association with the
2385      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2386      *
2387      * @hide
2388      */
setUndoManager(UndoManager undoManager, String tag)2389     public final void setUndoManager(UndoManager undoManager, String tag) {
2390         // TODO: Consider supporting a global undo manager. An implementation will need to:
2391         // * createEditorIfNeeded()
2392         // * Promote to BufferType.EDITABLE if needed.
2393         // * Update the UndoManager and UndoOwner.
2394         // Likewise it will need to be able to restore the default UndoManager.
2395         throw new UnsupportedOperationException("not implemented");
2396     }
2397 
2398     /**
2399      * Gets the current {@link KeyListener} for the TextView.
2400      * This will frequently be null for non-EditText TextViews.
2401      * @return the current key listener for this TextView.
2402      *
2403      * @attr ref android.R.styleable#TextView_numeric
2404      * @attr ref android.R.styleable#TextView_digits
2405      * @attr ref android.R.styleable#TextView_phoneNumber
2406      * @attr ref android.R.styleable#TextView_inputMethod
2407      * @attr ref android.R.styleable#TextView_capitalize
2408      * @attr ref android.R.styleable#TextView_autoText
2409      */
getKeyListener()2410     public final KeyListener getKeyListener() {
2411         return mEditor == null ? null : mEditor.mKeyListener;
2412     }
2413 
2414     /**
2415      * Sets the key listener to be used with this TextView.  This can be null
2416      * to disallow user input.  Note that this method has significant and
2417      * subtle interactions with soft keyboards and other input method:
2418      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2419      * for important details.  Calling this method will replace the current
2420      * content type of the text view with the content type returned by the
2421      * key listener.
2422      * <p>
2423      * Be warned that if you want a TextView with a key listener or movement
2424      * method not to be focusable, or if you want a TextView without a
2425      * key listener or movement method to be focusable, you must call
2426      * {@link #setFocusable} again after calling this to get the focusability
2427      * back the way you want it.
2428      *
2429      * @attr ref android.R.styleable#TextView_numeric
2430      * @attr ref android.R.styleable#TextView_digits
2431      * @attr ref android.R.styleable#TextView_phoneNumber
2432      * @attr ref android.R.styleable#TextView_inputMethod
2433      * @attr ref android.R.styleable#TextView_capitalize
2434      * @attr ref android.R.styleable#TextView_autoText
2435      */
setKeyListener(KeyListener input)2436     public void setKeyListener(KeyListener input) {
2437         mListenerChanged = true;
2438         setKeyListenerOnly(input);
2439         fixFocusableAndClickableSettings();
2440 
2441         if (input != null) {
2442             createEditorIfNeeded();
2443             setInputTypeFromEditor();
2444         } else {
2445             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2446         }
2447 
2448         InputMethodManager imm = getInputMethodManager();
2449         if (imm != null) imm.restartInput(this);
2450     }
2451 
setInputTypeFromEditor()2452     private void setInputTypeFromEditor() {
2453         try {
2454             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2455         } catch (IncompatibleClassChangeError e) {
2456             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2457         }
2458         // Change inputType, without affecting transformation.
2459         // No need to applySingleLine since mSingleLine is unchanged.
2460         setInputTypeSingleLine(mSingleLine);
2461     }
2462 
setKeyListenerOnly(KeyListener input)2463     private void setKeyListenerOnly(KeyListener input) {
2464         if (mEditor == null && input == null) return; // null is the default value
2465 
2466         createEditorIfNeeded();
2467         if (mEditor.mKeyListener != input) {
2468             mEditor.mKeyListener = input;
2469             if (input != null && !(mText instanceof Editable)) {
2470                 setText(mText);
2471             }
2472 
2473             setFilters((Editable) mText, mFilters);
2474         }
2475     }
2476 
2477     /**
2478      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2479      * which provides positioning, scrolling, and text selection functionality.
2480      * This will frequently be null for non-EditText TextViews.
2481      * @return the movement method being used for this TextView.
2482      * @see android.text.method.MovementMethod
2483      */
getMovementMethod()2484     public final MovementMethod getMovementMethod() {
2485         return mMovement;
2486     }
2487 
2488     /**
2489      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2490      * for this TextView. This can be null to disallow using the arrow keys to move the
2491      * cursor or scroll the view.
2492      * <p>
2493      * Be warned that if you want a TextView with a key listener or movement
2494      * method not to be focusable, or if you want a TextView without a
2495      * key listener or movement method to be focusable, you must call
2496      * {@link #setFocusable} again after calling this to get the focusability
2497      * back the way you want it.
2498      */
setMovementMethod(MovementMethod movement)2499     public final void setMovementMethod(MovementMethod movement) {
2500         if (mMovement != movement) {
2501             mMovement = movement;
2502 
2503             if (movement != null && mSpannable == null) {
2504                 setText(mText);
2505             }
2506 
2507             fixFocusableAndClickableSettings();
2508 
2509             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2510             // mMovement
2511             if (mEditor != null) mEditor.prepareCursorControllers();
2512         }
2513     }
2514 
fixFocusableAndClickableSettings()2515     private void fixFocusableAndClickableSettings() {
2516         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2517             setFocusable(FOCUSABLE);
2518             setClickable(true);
2519             setLongClickable(true);
2520         } else {
2521             setFocusable(FOCUSABLE_AUTO);
2522             setClickable(false);
2523             setLongClickable(false);
2524         }
2525     }
2526 
2527     /**
2528      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2529      * This is frequently null, except for single-line and password fields.
2530      * @return the current transformation method for this TextView.
2531      *
2532      * @attr ref android.R.styleable#TextView_password
2533      * @attr ref android.R.styleable#TextView_singleLine
2534      */
getTransformationMethod()2535     public final TransformationMethod getTransformationMethod() {
2536         return mTransformation;
2537     }
2538 
2539     /**
2540      * Sets the transformation that is applied to the text that this
2541      * TextView is displaying.
2542      *
2543      * @attr ref android.R.styleable#TextView_password
2544      * @attr ref android.R.styleable#TextView_singleLine
2545      */
setTransformationMethod(TransformationMethod method)2546     public final void setTransformationMethod(TransformationMethod method) {
2547         if (method == mTransformation) {
2548             // Avoid the setText() below if the transformation is
2549             // the same.
2550             return;
2551         }
2552         if (mTransformation != null) {
2553             if (mSpannable != null) {
2554                 mSpannable.removeSpan(mTransformation);
2555             }
2556         }
2557 
2558         mTransformation = method;
2559 
2560         if (method instanceof TransformationMethod2) {
2561             TransformationMethod2 method2 = (TransformationMethod2) method;
2562             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2563             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2564         } else {
2565             mAllowTransformationLengthChange = false;
2566         }
2567 
2568         setText(mText);
2569 
2570         if (hasPasswordTransformationMethod()) {
2571             notifyViewAccessibilityStateChangedIfNeeded(
2572                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2573         }
2574 
2575         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2576         // getTextDirectionHeuristic, needs reset
2577         mTextDir = getTextDirectionHeuristic();
2578     }
2579 
2580     /**
2581      * Returns the top padding of the view, plus space for the top
2582      * Drawable if any.
2583      */
getCompoundPaddingTop()2584     public int getCompoundPaddingTop() {
2585         final Drawables dr = mDrawables;
2586         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2587             return mPaddingTop;
2588         } else {
2589             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2590         }
2591     }
2592 
2593     /**
2594      * Returns the bottom padding of the view, plus space for the bottom
2595      * Drawable if any.
2596      */
getCompoundPaddingBottom()2597     public int getCompoundPaddingBottom() {
2598         final Drawables dr = mDrawables;
2599         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2600             return mPaddingBottom;
2601         } else {
2602             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2603         }
2604     }
2605 
2606     /**
2607      * Returns the left padding of the view, plus space for the left
2608      * Drawable if any.
2609      */
getCompoundPaddingLeft()2610     public int getCompoundPaddingLeft() {
2611         final Drawables dr = mDrawables;
2612         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2613             return mPaddingLeft;
2614         } else {
2615             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2616         }
2617     }
2618 
2619     /**
2620      * Returns the right padding of the view, plus space for the right
2621      * Drawable if any.
2622      */
getCompoundPaddingRight()2623     public int getCompoundPaddingRight() {
2624         final Drawables dr = mDrawables;
2625         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2626             return mPaddingRight;
2627         } else {
2628             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2629         }
2630     }
2631 
2632     /**
2633      * Returns the start padding of the view, plus space for the start
2634      * Drawable if any.
2635      */
getCompoundPaddingStart()2636     public int getCompoundPaddingStart() {
2637         resolveDrawables();
2638         switch(getLayoutDirection()) {
2639             default:
2640             case LAYOUT_DIRECTION_LTR:
2641                 return getCompoundPaddingLeft();
2642             case LAYOUT_DIRECTION_RTL:
2643                 return getCompoundPaddingRight();
2644         }
2645     }
2646 
2647     /**
2648      * Returns the end padding of the view, plus space for the end
2649      * Drawable if any.
2650      */
getCompoundPaddingEnd()2651     public int getCompoundPaddingEnd() {
2652         resolveDrawables();
2653         switch(getLayoutDirection()) {
2654             default:
2655             case LAYOUT_DIRECTION_LTR:
2656                 return getCompoundPaddingRight();
2657             case LAYOUT_DIRECTION_RTL:
2658                 return getCompoundPaddingLeft();
2659         }
2660     }
2661 
2662     /**
2663      * Returns the extended top padding of the view, including both the
2664      * top Drawable if any and any extra space to keep more than maxLines
2665      * of text from showing.  It is only valid to call this after measuring.
2666      */
getExtendedPaddingTop()2667     public int getExtendedPaddingTop() {
2668         if (mMaxMode != LINES) {
2669             return getCompoundPaddingTop();
2670         }
2671 
2672         if (mLayout == null) {
2673             assumeLayout();
2674         }
2675 
2676         if (mLayout.getLineCount() <= mMaximum) {
2677             return getCompoundPaddingTop();
2678         }
2679 
2680         int top = getCompoundPaddingTop();
2681         int bottom = getCompoundPaddingBottom();
2682         int viewht = getHeight() - top - bottom;
2683         int layoutht = mLayout.getLineTop(mMaximum);
2684 
2685         if (layoutht >= viewht) {
2686             return top;
2687         }
2688 
2689         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2690         if (gravity == Gravity.TOP) {
2691             return top;
2692         } else if (gravity == Gravity.BOTTOM) {
2693             return top + viewht - layoutht;
2694         } else { // (gravity == Gravity.CENTER_VERTICAL)
2695             return top + (viewht - layoutht) / 2;
2696         }
2697     }
2698 
2699     /**
2700      * Returns the extended bottom padding of the view, including both the
2701      * bottom Drawable if any and any extra space to keep more than maxLines
2702      * of text from showing.  It is only valid to call this after measuring.
2703      */
getExtendedPaddingBottom()2704     public int getExtendedPaddingBottom() {
2705         if (mMaxMode != LINES) {
2706             return getCompoundPaddingBottom();
2707         }
2708 
2709         if (mLayout == null) {
2710             assumeLayout();
2711         }
2712 
2713         if (mLayout.getLineCount() <= mMaximum) {
2714             return getCompoundPaddingBottom();
2715         }
2716 
2717         int top = getCompoundPaddingTop();
2718         int bottom = getCompoundPaddingBottom();
2719         int viewht = getHeight() - top - bottom;
2720         int layoutht = mLayout.getLineTop(mMaximum);
2721 
2722         if (layoutht >= viewht) {
2723             return bottom;
2724         }
2725 
2726         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2727         if (gravity == Gravity.TOP) {
2728             return bottom + viewht - layoutht;
2729         } else if (gravity == Gravity.BOTTOM) {
2730             return bottom;
2731         } else { // (gravity == Gravity.CENTER_VERTICAL)
2732             return bottom + (viewht - layoutht) / 2;
2733         }
2734     }
2735 
2736     /**
2737      * Returns the total left padding of the view, including the left
2738      * Drawable if any.
2739      */
getTotalPaddingLeft()2740     public int getTotalPaddingLeft() {
2741         return getCompoundPaddingLeft();
2742     }
2743 
2744     /**
2745      * Returns the total right padding of the view, including the right
2746      * Drawable if any.
2747      */
getTotalPaddingRight()2748     public int getTotalPaddingRight() {
2749         return getCompoundPaddingRight();
2750     }
2751 
2752     /**
2753      * Returns the total start padding of the view, including the start
2754      * Drawable if any.
2755      */
getTotalPaddingStart()2756     public int getTotalPaddingStart() {
2757         return getCompoundPaddingStart();
2758     }
2759 
2760     /**
2761      * Returns the total end padding of the view, including the end
2762      * Drawable if any.
2763      */
getTotalPaddingEnd()2764     public int getTotalPaddingEnd() {
2765         return getCompoundPaddingEnd();
2766     }
2767 
2768     /**
2769      * Returns the total top padding of the view, including the top
2770      * Drawable if any, the extra space to keep more than maxLines
2771      * from showing, and the vertical offset for gravity, if any.
2772      */
getTotalPaddingTop()2773     public int getTotalPaddingTop() {
2774         return getExtendedPaddingTop() + getVerticalOffset(true);
2775     }
2776 
2777     /**
2778      * Returns the total bottom padding of the view, including the bottom
2779      * Drawable if any, the extra space to keep more than maxLines
2780      * from showing, and the vertical offset for gravity, if any.
2781      */
getTotalPaddingBottom()2782     public int getTotalPaddingBottom() {
2783         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2784     }
2785 
2786     /**
2787      * Sets the Drawables (if any) to appear to the left of, above, to the
2788      * right of, and below the text. Use {@code null} if you do not want a
2789      * Drawable there. The Drawables must already have had
2790      * {@link Drawable#setBounds} called.
2791      * <p>
2792      * Calling this method will overwrite any Drawables previously set using
2793      * {@link #setCompoundDrawablesRelative} or related methods.
2794      *
2795      * @attr ref android.R.styleable#TextView_drawableLeft
2796      * @attr ref android.R.styleable#TextView_drawableTop
2797      * @attr ref android.R.styleable#TextView_drawableRight
2798      * @attr ref android.R.styleable#TextView_drawableBottom
2799      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2800     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2801             @Nullable Drawable right, @Nullable Drawable bottom) {
2802         Drawables dr = mDrawables;
2803 
2804         // We're switching to absolute, discard relative.
2805         if (dr != null) {
2806             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2807             dr.mDrawableStart = null;
2808             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2809             dr.mDrawableEnd = null;
2810             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2811             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2812         }
2813 
2814         final boolean drawables = left != null || top != null || right != null || bottom != null;
2815         if (!drawables) {
2816             // Clearing drawables...  can we free the data structure?
2817             if (dr != null) {
2818                 if (!dr.hasMetadata()) {
2819                     mDrawables = null;
2820                 } else {
2821                     // We need to retain the last set padding, so just clear
2822                     // out all of the fields in the existing structure.
2823                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2824                         if (dr.mShowing[i] != null) {
2825                             dr.mShowing[i].setCallback(null);
2826                         }
2827                         dr.mShowing[i] = null;
2828                     }
2829                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2830                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2831                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2832                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2833                 }
2834             }
2835         } else {
2836             if (dr == null) {
2837                 mDrawables = dr = new Drawables(getContext());
2838             }
2839 
2840             mDrawables.mOverride = false;
2841 
2842             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2843                 dr.mShowing[Drawables.LEFT].setCallback(null);
2844             }
2845             dr.mShowing[Drawables.LEFT] = left;
2846 
2847             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2848                 dr.mShowing[Drawables.TOP].setCallback(null);
2849             }
2850             dr.mShowing[Drawables.TOP] = top;
2851 
2852             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2853                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2854             }
2855             dr.mShowing[Drawables.RIGHT] = right;
2856 
2857             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2858                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2859             }
2860             dr.mShowing[Drawables.BOTTOM] = bottom;
2861 
2862             final Rect compoundRect = dr.mCompoundRect;
2863             int[] state;
2864 
2865             state = getDrawableState();
2866 
2867             if (left != null) {
2868                 left.setState(state);
2869                 left.copyBounds(compoundRect);
2870                 left.setCallback(this);
2871                 dr.mDrawableSizeLeft = compoundRect.width();
2872                 dr.mDrawableHeightLeft = compoundRect.height();
2873             } else {
2874                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2875             }
2876 
2877             if (right != null) {
2878                 right.setState(state);
2879                 right.copyBounds(compoundRect);
2880                 right.setCallback(this);
2881                 dr.mDrawableSizeRight = compoundRect.width();
2882                 dr.mDrawableHeightRight = compoundRect.height();
2883             } else {
2884                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2885             }
2886 
2887             if (top != null) {
2888                 top.setState(state);
2889                 top.copyBounds(compoundRect);
2890                 top.setCallback(this);
2891                 dr.mDrawableSizeTop = compoundRect.height();
2892                 dr.mDrawableWidthTop = compoundRect.width();
2893             } else {
2894                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2895             }
2896 
2897             if (bottom != null) {
2898                 bottom.setState(state);
2899                 bottom.copyBounds(compoundRect);
2900                 bottom.setCallback(this);
2901                 dr.mDrawableSizeBottom = compoundRect.height();
2902                 dr.mDrawableWidthBottom = compoundRect.width();
2903             } else {
2904                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2905             }
2906         }
2907 
2908         // Save initial left/right drawables
2909         if (dr != null) {
2910             dr.mDrawableLeftInitial = left;
2911             dr.mDrawableRightInitial = right;
2912         }
2913 
2914         resetResolvedDrawables();
2915         resolveDrawables();
2916         applyCompoundDrawableTint();
2917         invalidate();
2918         requestLayout();
2919     }
2920 
2921     /**
2922      * Sets the Drawables (if any) to appear to the left of, above, to the
2923      * right of, and below the text. Use 0 if you do not want a Drawable there.
2924      * The Drawables' bounds will be set to their intrinsic bounds.
2925      * <p>
2926      * Calling this method will overwrite any Drawables previously set using
2927      * {@link #setCompoundDrawablesRelative} or related methods.
2928      *
2929      * @param left Resource identifier of the left Drawable.
2930      * @param top Resource identifier of the top Drawable.
2931      * @param right Resource identifier of the right Drawable.
2932      * @param bottom Resource identifier of the bottom Drawable.
2933      *
2934      * @attr ref android.R.styleable#TextView_drawableLeft
2935      * @attr ref android.R.styleable#TextView_drawableTop
2936      * @attr ref android.R.styleable#TextView_drawableRight
2937      * @attr ref android.R.styleable#TextView_drawableBottom
2938      */
2939     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)2940     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2941             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2942         final Context context = getContext();
2943         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2944                 top != 0 ? context.getDrawable(top) : null,
2945                 right != 0 ? context.getDrawable(right) : null,
2946                 bottom != 0 ? context.getDrawable(bottom) : null);
2947     }
2948 
2949     /**
2950      * Sets the Drawables (if any) to appear to the left of, above, to the
2951      * right of, and below the text. Use {@code null} if you do not want a
2952      * Drawable there. The Drawables' bounds will be set to their intrinsic
2953      * bounds.
2954      * <p>
2955      * Calling this method will overwrite any Drawables previously set using
2956      * {@link #setCompoundDrawablesRelative} or related methods.
2957      *
2958      * @attr ref android.R.styleable#TextView_drawableLeft
2959      * @attr ref android.R.styleable#TextView_drawableTop
2960      * @attr ref android.R.styleable#TextView_drawableRight
2961      * @attr ref android.R.styleable#TextView_drawableBottom
2962      */
2963     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2964     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2965             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2966 
2967         if (left != null) {
2968             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2969         }
2970         if (right != null) {
2971             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2972         }
2973         if (top != null) {
2974             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2975         }
2976         if (bottom != null) {
2977             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2978         }
2979         setCompoundDrawables(left, top, right, bottom);
2980     }
2981 
2982     /**
2983      * Sets the Drawables (if any) to appear to the start of, above, to the end
2984      * of, and below the text. Use {@code null} if you do not want a Drawable
2985      * there. The Drawables must already have had {@link Drawable#setBounds}
2986      * called.
2987      * <p>
2988      * Calling this method will overwrite any Drawables previously set using
2989      * {@link #setCompoundDrawables} or related methods.
2990      *
2991      * @attr ref android.R.styleable#TextView_drawableStart
2992      * @attr ref android.R.styleable#TextView_drawableTop
2993      * @attr ref android.R.styleable#TextView_drawableEnd
2994      * @attr ref android.R.styleable#TextView_drawableBottom
2995      */
2996     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)2997     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2998             @Nullable Drawable end, @Nullable Drawable bottom) {
2999         Drawables dr = mDrawables;
3000 
3001         // We're switching to relative, discard absolute.
3002         if (dr != null) {
3003             if (dr.mShowing[Drawables.LEFT] != null) {
3004                 dr.mShowing[Drawables.LEFT].setCallback(null);
3005             }
3006             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3007             if (dr.mShowing[Drawables.RIGHT] != null) {
3008                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3009             }
3010             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3011             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3012             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3013         }
3014 
3015         final boolean drawables = start != null || top != null
3016                 || end != null || bottom != null;
3017 
3018         if (!drawables) {
3019             // Clearing drawables...  can we free the data structure?
3020             if (dr != null) {
3021                 if (!dr.hasMetadata()) {
3022                     mDrawables = null;
3023                 } else {
3024                     // We need to retain the last set padding, so just clear
3025                     // out all of the fields in the existing structure.
3026                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3027                     dr.mDrawableStart = null;
3028                     if (dr.mShowing[Drawables.TOP] != null) {
3029                         dr.mShowing[Drawables.TOP].setCallback(null);
3030                     }
3031                     dr.mShowing[Drawables.TOP] = null;
3032                     if (dr.mDrawableEnd != null) {
3033                         dr.mDrawableEnd.setCallback(null);
3034                     }
3035                     dr.mDrawableEnd = null;
3036                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3037                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3038                     }
3039                     dr.mShowing[Drawables.BOTTOM] = null;
3040                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3041                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3042                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3043                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3044                 }
3045             }
3046         } else {
3047             if (dr == null) {
3048                 mDrawables = dr = new Drawables(getContext());
3049             }
3050 
3051             mDrawables.mOverride = true;
3052 
3053             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3054                 dr.mDrawableStart.setCallback(null);
3055             }
3056             dr.mDrawableStart = start;
3057 
3058             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3059                 dr.mShowing[Drawables.TOP].setCallback(null);
3060             }
3061             dr.mShowing[Drawables.TOP] = top;
3062 
3063             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3064                 dr.mDrawableEnd.setCallback(null);
3065             }
3066             dr.mDrawableEnd = end;
3067 
3068             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3069                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3070             }
3071             dr.mShowing[Drawables.BOTTOM] = bottom;
3072 
3073             final Rect compoundRect = dr.mCompoundRect;
3074             int[] state;
3075 
3076             state = getDrawableState();
3077 
3078             if (start != null) {
3079                 start.setState(state);
3080                 start.copyBounds(compoundRect);
3081                 start.setCallback(this);
3082                 dr.mDrawableSizeStart = compoundRect.width();
3083                 dr.mDrawableHeightStart = compoundRect.height();
3084             } else {
3085                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3086             }
3087 
3088             if (end != null) {
3089                 end.setState(state);
3090                 end.copyBounds(compoundRect);
3091                 end.setCallback(this);
3092                 dr.mDrawableSizeEnd = compoundRect.width();
3093                 dr.mDrawableHeightEnd = compoundRect.height();
3094             } else {
3095                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3096             }
3097 
3098             if (top != null) {
3099                 top.setState(state);
3100                 top.copyBounds(compoundRect);
3101                 top.setCallback(this);
3102                 dr.mDrawableSizeTop = compoundRect.height();
3103                 dr.mDrawableWidthTop = compoundRect.width();
3104             } else {
3105                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3106             }
3107 
3108             if (bottom != null) {
3109                 bottom.setState(state);
3110                 bottom.copyBounds(compoundRect);
3111                 bottom.setCallback(this);
3112                 dr.mDrawableSizeBottom = compoundRect.height();
3113                 dr.mDrawableWidthBottom = compoundRect.width();
3114             } else {
3115                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3116             }
3117         }
3118 
3119         resetResolvedDrawables();
3120         resolveDrawables();
3121         invalidate();
3122         requestLayout();
3123     }
3124 
3125     /**
3126      * Sets the Drawables (if any) to appear to the start of, above, to the end
3127      * of, and below the text. Use 0 if you do not want a Drawable there. The
3128      * Drawables' bounds will be set to their intrinsic bounds.
3129      * <p>
3130      * Calling this method will overwrite any Drawables previously set using
3131      * {@link #setCompoundDrawables} or related methods.
3132      *
3133      * @param start Resource identifier of the start Drawable.
3134      * @param top Resource identifier of the top Drawable.
3135      * @param end Resource identifier of the end Drawable.
3136      * @param bottom Resource identifier of the bottom Drawable.
3137      *
3138      * @attr ref android.R.styleable#TextView_drawableStart
3139      * @attr ref android.R.styleable#TextView_drawableTop
3140      * @attr ref android.R.styleable#TextView_drawableEnd
3141      * @attr ref android.R.styleable#TextView_drawableBottom
3142      */
3143     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3144     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3145             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3146         final Context context = getContext();
3147         setCompoundDrawablesRelativeWithIntrinsicBounds(
3148                 start != 0 ? context.getDrawable(start) : null,
3149                 top != 0 ? context.getDrawable(top) : null,
3150                 end != 0 ? context.getDrawable(end) : null,
3151                 bottom != 0 ? context.getDrawable(bottom) : null);
3152     }
3153 
3154     /**
3155      * Sets the Drawables (if any) to appear to the start of, above, to the end
3156      * of, and below the text. Use {@code null} if you do not want a Drawable
3157      * there. The Drawables' bounds will be set to their intrinsic bounds.
3158      * <p>
3159      * Calling this method will overwrite any Drawables previously set using
3160      * {@link #setCompoundDrawables} or related methods.
3161      *
3162      * @attr ref android.R.styleable#TextView_drawableStart
3163      * @attr ref android.R.styleable#TextView_drawableTop
3164      * @attr ref android.R.styleable#TextView_drawableEnd
3165      * @attr ref android.R.styleable#TextView_drawableBottom
3166      */
3167     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3168     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3169             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3170 
3171         if (start != null) {
3172             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3173         }
3174         if (end != null) {
3175             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3176         }
3177         if (top != null) {
3178             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3179         }
3180         if (bottom != null) {
3181             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3182         }
3183         setCompoundDrawablesRelative(start, top, end, bottom);
3184     }
3185 
3186     /**
3187      * Returns drawables for the left, top, right, and bottom borders.
3188      *
3189      * @attr ref android.R.styleable#TextView_drawableLeft
3190      * @attr ref android.R.styleable#TextView_drawableTop
3191      * @attr ref android.R.styleable#TextView_drawableRight
3192      * @attr ref android.R.styleable#TextView_drawableBottom
3193      */
3194     @NonNull
getCompoundDrawables()3195     public Drawable[] getCompoundDrawables() {
3196         final Drawables dr = mDrawables;
3197         if (dr != null) {
3198             return dr.mShowing.clone();
3199         } else {
3200             return new Drawable[] { null, null, null, null };
3201         }
3202     }
3203 
3204     /**
3205      * Returns drawables for the start, top, end, and bottom borders.
3206      *
3207      * @attr ref android.R.styleable#TextView_drawableStart
3208      * @attr ref android.R.styleable#TextView_drawableTop
3209      * @attr ref android.R.styleable#TextView_drawableEnd
3210      * @attr ref android.R.styleable#TextView_drawableBottom
3211      */
3212     @NonNull
getCompoundDrawablesRelative()3213     public Drawable[] getCompoundDrawablesRelative() {
3214         final Drawables dr = mDrawables;
3215         if (dr != null) {
3216             return new Drawable[] {
3217                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3218                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3219             };
3220         } else {
3221             return new Drawable[] { null, null, null, null };
3222         }
3223     }
3224 
3225     /**
3226      * Sets the size of the padding between the compound drawables and
3227      * the text.
3228      *
3229      * @attr ref android.R.styleable#TextView_drawablePadding
3230      */
3231     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3232     public void setCompoundDrawablePadding(int pad) {
3233         Drawables dr = mDrawables;
3234         if (pad == 0) {
3235             if (dr != null) {
3236                 dr.mDrawablePadding = pad;
3237             }
3238         } else {
3239             if (dr == null) {
3240                 mDrawables = dr = new Drawables(getContext());
3241             }
3242             dr.mDrawablePadding = pad;
3243         }
3244 
3245         invalidate();
3246         requestLayout();
3247     }
3248 
3249     /**
3250      * Returns the padding between the compound drawables and the text.
3251      *
3252      * @attr ref android.R.styleable#TextView_drawablePadding
3253      */
3254     @InspectableProperty(name = "drawablePadding")
getCompoundDrawablePadding()3255     public int getCompoundDrawablePadding() {
3256         final Drawables dr = mDrawables;
3257         return dr != null ? dr.mDrawablePadding : 0;
3258     }
3259 
3260     /**
3261      * Applies a tint to the compound drawables. Does not modify the
3262      * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
3263      * <p>
3264      * Subsequent calls to
3265      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3266      * and related methods will automatically mutate the drawables and apply
3267      * the specified tint and tint mode using
3268      * {@link Drawable#setTintList(ColorStateList)}.
3269      *
3270      * @param tint the tint to apply, may be {@code null} to clear tint
3271      *
3272      * @attr ref android.R.styleable#TextView_drawableTint
3273      * @see #getCompoundDrawableTintList()
3274      * @see Drawable#setTintList(ColorStateList)
3275      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3276     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3277         if (mDrawables == null) {
3278             mDrawables = new Drawables(getContext());
3279         }
3280         mDrawables.mTintList = tint;
3281         mDrawables.mHasTint = true;
3282 
3283         applyCompoundDrawableTint();
3284     }
3285 
3286     /**
3287      * @return the tint applied to the compound drawables
3288      * @attr ref android.R.styleable#TextView_drawableTint
3289      * @see #setCompoundDrawableTintList(ColorStateList)
3290      */
3291     @InspectableProperty(name = "drawableTint")
getCompoundDrawableTintList()3292     public ColorStateList getCompoundDrawableTintList() {
3293         return mDrawables != null ? mDrawables.mTintList : null;
3294     }
3295 
3296     /**
3297      * Specifies the blending mode used to apply the tint specified by
3298      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3299      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3300      *
3301      * @param tintMode the blending mode used to apply the tint, may be
3302      *                 {@code null} to clear tint
3303      * @attr ref android.R.styleable#TextView_drawableTintMode
3304      * @see #setCompoundDrawableTintList(ColorStateList)
3305      * @see Drawable#setTintMode(PorterDuff.Mode)
3306      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3307     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3308         setCompoundDrawableTintBlendMode(tintMode != null
3309                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3310     }
3311 
3312     /**
3313      * Specifies the blending mode used to apply the tint specified by
3314      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3315      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3316      *
3317      * @param blendMode the blending mode used to apply the tint, may be
3318      *                 {@code null} to clear tint
3319      * @attr ref android.R.styleable#TextView_drawableTintMode
3320      * @see #setCompoundDrawableTintList(ColorStateList)
3321      * @see Drawable#setTintBlendMode(BlendMode)
3322      */
setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3323     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3324         if (mDrawables == null) {
3325             mDrawables = new Drawables(getContext());
3326         }
3327         mDrawables.mBlendMode = blendMode;
3328         mDrawables.mHasTintMode = true;
3329 
3330         applyCompoundDrawableTint();
3331     }
3332 
3333     /**
3334      * Returns the blending mode used to apply the tint to the compound
3335      * drawables, if specified.
3336      *
3337      * @return the blending mode used to apply the tint to the compound
3338      *         drawables
3339      * @attr ref android.R.styleable#TextView_drawableTintMode
3340      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3341      *
3342      */
3343     @InspectableProperty(name = "drawableTintMode")
getCompoundDrawableTintMode()3344     public PorterDuff.Mode getCompoundDrawableTintMode() {
3345         BlendMode mode = getCompoundDrawableTintBlendMode();
3346         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3347     }
3348 
3349     /**
3350      * Returns the blending mode used to apply the tint to the compound
3351      * drawables, if specified.
3352      *
3353      * @return the blending mode used to apply the tint to the compound
3354      *         drawables
3355      * @attr ref android.R.styleable#TextView_drawableTintMode
3356      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3357      */
3358     @InspectableProperty(name = "drawableBlendMode",
3359             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
getCompoundDrawableTintBlendMode()3360     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3361         return mDrawables != null ? mDrawables.mBlendMode : null;
3362     }
3363 
applyCompoundDrawableTint()3364     private void applyCompoundDrawableTint() {
3365         if (mDrawables == null) {
3366             return;
3367         }
3368 
3369         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3370             final ColorStateList tintList = mDrawables.mTintList;
3371             final BlendMode blendMode = mDrawables.mBlendMode;
3372             final boolean hasTint = mDrawables.mHasTint;
3373             final boolean hasTintMode = mDrawables.mHasTintMode;
3374             final int[] state = getDrawableState();
3375 
3376             for (Drawable dr : mDrawables.mShowing) {
3377                 if (dr == null) {
3378                     continue;
3379                 }
3380 
3381                 if (dr == mDrawables.mDrawableError) {
3382                     // From a developer's perspective, the error drawable isn't
3383                     // a compound drawable. Don't apply the generic compound
3384                     // drawable tint to it.
3385                     continue;
3386                 }
3387 
3388                 dr.mutate();
3389 
3390                 if (hasTint) {
3391                     dr.setTintList(tintList);
3392                 }
3393 
3394                 if (hasTintMode) {
3395                     dr.setTintBlendMode(blendMode);
3396                 }
3397 
3398                 // The drawable (or one of its children) may not have been
3399                 // stateful before applying the tint, so let's try again.
3400                 if (dr.isStateful()) {
3401                     dr.setState(state);
3402                 }
3403             }
3404         }
3405     }
3406 
3407     /**
3408      * @inheritDoc
3409      *
3410      * @see #setFirstBaselineToTopHeight(int)
3411      * @see #setLastBaselineToBottomHeight(int)
3412      */
3413     @Override
setPadding(int left, int top, int right, int bottom)3414     public void setPadding(int left, int top, int right, int bottom) {
3415         if (left != mPaddingLeft
3416                 || right != mPaddingRight
3417                 || top != mPaddingTop
3418                 ||  bottom != mPaddingBottom) {
3419             nullLayouts();
3420         }
3421 
3422         // the super call will requestLayout()
3423         super.setPadding(left, top, right, bottom);
3424         invalidate();
3425     }
3426 
3427     /**
3428      * @inheritDoc
3429      *
3430      * @see #setFirstBaselineToTopHeight(int)
3431      * @see #setLastBaselineToBottomHeight(int)
3432      */
3433     @Override
setPaddingRelative(int start, int top, int end, int bottom)3434     public void setPaddingRelative(int start, int top, int end, int bottom) {
3435         if (start != getPaddingStart()
3436                 || end != getPaddingEnd()
3437                 || top != mPaddingTop
3438                 || bottom != mPaddingBottom) {
3439             nullLayouts();
3440         }
3441 
3442         // the super call will requestLayout()
3443         super.setPaddingRelative(start, top, end, bottom);
3444         invalidate();
3445     }
3446 
3447     /**
3448      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3449      * the distance between the top of the TextView and first line's baseline.
3450      * <p>
3451      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3452      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3453      *
3454      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3455      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3456      * Moreover since this function sets the top padding, if the height of the TextView is less than
3457      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3458      * down and bottom will be clipped.
3459      *
3460      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3461      *      in pixels
3462      *
3463      * @see #getFirstBaselineToTopHeight()
3464      * @see #setLastBaselineToBottomHeight(int)
3465      * @see #setPadding(int, int, int, int)
3466      * @see #setPaddingRelative(int, int, int, int)
3467      *
3468      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3469      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3470     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3471         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3472 
3473         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3474         final int fontMetricsTop;
3475         if (getIncludeFontPadding()) {
3476             fontMetricsTop = fontMetrics.top;
3477         } else {
3478             fontMetricsTop = fontMetrics.ascent;
3479         }
3480 
3481         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3482         // in settings). At the moment, we don't.
3483 
3484         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3485             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3486             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3487         }
3488     }
3489 
3490     /**
3491      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3492      * the distance between the bottom of the TextView and the last line's baseline.
3493      * <p>
3494      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3495      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3496      *
3497      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3498      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3499      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3500      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3501      * clipped.
3502      *
3503      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3504      *      in pixels
3505      *
3506      * @see #getLastBaselineToBottomHeight()
3507      * @see #setFirstBaselineToTopHeight(int)
3508      * @see #setPadding(int, int, int, int)
3509      * @see #setPaddingRelative(int, int, int, int)
3510      *
3511      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3512      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3513     public void setLastBaselineToBottomHeight(
3514             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3515         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3516 
3517         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3518         final int fontMetricsBottom;
3519         if (getIncludeFontPadding()) {
3520             fontMetricsBottom = fontMetrics.bottom;
3521         } else {
3522             fontMetricsBottom = fontMetrics.descent;
3523         }
3524 
3525         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3526         // in settings). At the moment, we don't.
3527 
3528         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3529             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3530             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3531         }
3532     }
3533 
3534     /**
3535      * Returns the distance between the first text baseline and the top of this TextView.
3536      *
3537      * @see #setFirstBaselineToTopHeight(int)
3538      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3539      */
3540     @InspectableProperty
getFirstBaselineToTopHeight()3541     public int getFirstBaselineToTopHeight() {
3542         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3543     }
3544 
3545     /**
3546      * Returns the distance between the last text baseline and the bottom of this TextView.
3547      *
3548      * @see #setLastBaselineToBottomHeight(int)
3549      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3550      */
3551     @InspectableProperty
getLastBaselineToBottomHeight()3552     public int getLastBaselineToBottomHeight() {
3553         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3554     }
3555 
3556     /**
3557      * Gets the autolink mask of the text.
3558      *
3559      * See {@link Linkify#ALL} and peers for possible values.
3560      *
3561      * @attr ref android.R.styleable#TextView_autoLink
3562      */
3563     @InspectableProperty(name = "autoLink", flagMapping = {
3564             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3565             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3566             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3567             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3568     })
getAutoLinkMask()3569     public final int getAutoLinkMask() {
3570         return mAutoLinkMask;
3571     }
3572 
3573     /**
3574      * Sets the Drawable corresponding to the selection handle used for
3575      * positioning the cursor within text. The Drawable defaults to the value
3576      * of the textSelectHandle attribute.
3577      * Note that any change applied to the handle Drawable will not be visible
3578      * until the handle is hidden and then drawn again.
3579      *
3580      * @see #setTextSelectHandle(int)
3581      * @attr ref android.R.styleable#TextView_textSelectHandle
3582      */
3583     @android.view.RemotableViewMethod
setTextSelectHandle(@onNull Drawable textSelectHandle)3584     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3585         Preconditions.checkNotNull(textSelectHandle,
3586                 "The text select handle should not be null.");
3587         mTextSelectHandle = textSelectHandle;
3588         mTextSelectHandleRes = 0;
3589         if (mEditor != null) {
3590             mEditor.loadHandleDrawables(true /* overwrite */);
3591         }
3592     }
3593 
3594     /**
3595      * Sets the Drawable corresponding to the selection handle used for
3596      * positioning the cursor within text. The Drawable defaults to the value
3597      * of the textSelectHandle attribute.
3598      * Note that any change applied to the handle Drawable will not be visible
3599      * until the handle is hidden and then drawn again.
3600      *
3601      * @see #setTextSelectHandle(Drawable)
3602      * @attr ref android.R.styleable#TextView_textSelectHandle
3603      */
3604     @android.view.RemotableViewMethod
setTextSelectHandle(@rawableRes int textSelectHandle)3605     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3606         Preconditions.checkArgument(textSelectHandle != 0,
3607                 "The text select handle should be a valid drawable resource id.");
3608         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3609     }
3610 
3611     /**
3612      * Returns the Drawable corresponding to the selection handle used
3613      * for positioning the cursor within text.
3614      * Note that any change applied to the handle Drawable will not be visible
3615      * until the handle is hidden and then drawn again.
3616      *
3617      * @return the text select handle drawable
3618      *
3619      * @see #setTextSelectHandle(Drawable)
3620      * @see #setTextSelectHandle(int)
3621      * @attr ref android.R.styleable#TextView_textSelectHandle
3622      */
getTextSelectHandle()3623     @Nullable public Drawable getTextSelectHandle() {
3624         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3625             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3626         }
3627         return mTextSelectHandle;
3628     }
3629 
3630     /**
3631      * Sets the Drawable corresponding to the left handle used
3632      * for selecting text. The Drawable defaults to the value of the
3633      * textSelectHandleLeft attribute.
3634      * Note that any change applied to the handle Drawable will not be visible
3635      * until the handle is hidden and then drawn again.
3636      *
3637      * @see #setTextSelectHandleLeft(int)
3638      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3639      */
3640     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3641     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3642         Preconditions.checkNotNull(textSelectHandleLeft,
3643                 "The left text select handle should not be null.");
3644         mTextSelectHandleLeft = textSelectHandleLeft;
3645         mTextSelectHandleLeftRes = 0;
3646         if (mEditor != null) {
3647             mEditor.loadHandleDrawables(true /* overwrite */);
3648         }
3649     }
3650 
3651     /**
3652      * Sets the Drawable corresponding to the left handle used
3653      * for selecting text. The Drawable defaults to the value of the
3654      * textSelectHandleLeft attribute.
3655      * Note that any change applied to the handle Drawable will not be visible
3656      * until the handle is hidden and then drawn again.
3657      *
3658      * @see #setTextSelectHandleLeft(Drawable)
3659      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3660      */
3661     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3662     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
3663         Preconditions.checkArgument(textSelectHandleLeft != 0,
3664                 "The text select left handle should be a valid drawable resource id.");
3665         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
3666     }
3667 
3668     /**
3669      * Returns the Drawable corresponding to the left handle used
3670      * for selecting text.
3671      * Note that any change applied to the handle Drawable will not be visible
3672      * until the handle is hidden and then drawn again.
3673      *
3674      * @return the left text selection handle drawable
3675      *
3676      * @see #setTextSelectHandleLeft(Drawable)
3677      * @see #setTextSelectHandleLeft(int)
3678      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3679      */
getTextSelectHandleLeft()3680     @Nullable public Drawable getTextSelectHandleLeft() {
3681         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
3682             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
3683         }
3684         return mTextSelectHandleLeft;
3685     }
3686 
3687     /**
3688      * Sets the Drawable corresponding to the right handle used
3689      * for selecting text. The Drawable defaults to the value of the
3690      * textSelectHandleRight attribute.
3691      * Note that any change applied to the handle Drawable will not be visible
3692      * until the handle is hidden and then drawn again.
3693      *
3694      * @see #setTextSelectHandleRight(int)
3695      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3696      */
3697     @android.view.RemotableViewMethod
setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3698     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
3699         Preconditions.checkNotNull(textSelectHandleRight,
3700                 "The right text select handle should not be null.");
3701         mTextSelectHandleRight = textSelectHandleRight;
3702         mTextSelectHandleRightRes = 0;
3703         if (mEditor != null) {
3704             mEditor.loadHandleDrawables(true /* overwrite */);
3705         }
3706     }
3707 
3708     /**
3709      * Sets the Drawable corresponding to the right handle used
3710      * for selecting text. The Drawable defaults to the value of the
3711      * textSelectHandleRight attribute.
3712      * Note that any change applied to the handle Drawable will not be visible
3713      * until the handle is hidden and then drawn again.
3714      *
3715      * @see #setTextSelectHandleRight(Drawable)
3716      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3717      */
3718     @android.view.RemotableViewMethod
setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3719     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
3720         Preconditions.checkArgument(textSelectHandleRight != 0,
3721                 "The text select right handle should be a valid drawable resource id.");
3722         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
3723     }
3724 
3725     /**
3726      * Returns the Drawable corresponding to the right handle used
3727      * for selecting text.
3728      * Note that any change applied to the handle Drawable will not be visible
3729      * until the handle is hidden and then drawn again.
3730      *
3731      * @return the right text selection handle drawable
3732      *
3733      * @see #setTextSelectHandleRight(Drawable)
3734      * @see #setTextSelectHandleRight(int)
3735      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3736      */
getTextSelectHandleRight()3737     @Nullable public Drawable getTextSelectHandleRight() {
3738         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
3739             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
3740         }
3741         return mTextSelectHandleRight;
3742     }
3743 
3744     /**
3745      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3746      * value of the textCursorDrawable attribute.
3747      * Note that any change applied to the cursor Drawable will not be visible
3748      * until the cursor is hidden and then drawn again.
3749      *
3750      * @see #setTextCursorDrawable(int)
3751      * @attr ref android.R.styleable#TextView_textCursorDrawable
3752      */
setTextCursorDrawable(@ullable Drawable textCursorDrawable)3753     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
3754         mCursorDrawable = textCursorDrawable;
3755         mCursorDrawableRes = 0;
3756         if (mEditor != null) {
3757             mEditor.loadCursorDrawable();
3758         }
3759     }
3760 
3761     /**
3762      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3763      * value of the textCursorDrawable attribute.
3764      * Note that any change applied to the cursor Drawable will not be visible
3765      * until the cursor is hidden and then drawn again.
3766      *
3767      * @see #setTextCursorDrawable(Drawable)
3768      * @attr ref android.R.styleable#TextView_textCursorDrawable
3769      */
setTextCursorDrawable(@rawableRes int textCursorDrawable)3770     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
3771         setTextCursorDrawable(
3772                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
3773     }
3774 
3775     /**
3776      * Returns the Drawable corresponding to the text cursor.
3777      * Note that any change applied to the cursor Drawable will not be visible
3778      * until the cursor is hidden and then drawn again.
3779      *
3780      * @return the text cursor drawable
3781      *
3782      * @see #setTextCursorDrawable(Drawable)
3783      * @see #setTextCursorDrawable(int)
3784      * @attr ref android.R.styleable#TextView_textCursorDrawable
3785      */
getTextCursorDrawable()3786     @Nullable public Drawable getTextCursorDrawable() {
3787         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
3788             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
3789         }
3790         return mCursorDrawable;
3791     }
3792 
3793     /**
3794      * Sets the text appearance from the specified style resource.
3795      * <p>
3796      * Use a framework-defined {@code TextAppearance} style like
3797      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3798      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3799      * set of attributes that can be used in a custom style.
3800      *
3801      * @param resId the resource identifier of the style to apply
3802      * @attr ref android.R.styleable#TextView_textAppearance
3803      */
3804     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)3805     public void setTextAppearance(@StyleRes int resId) {
3806         setTextAppearance(mContext, resId);
3807     }
3808 
3809     /**
3810      * Sets the text color, size, style, hint color, and highlight color
3811      * from the specified TextAppearance resource.
3812      *
3813      * @deprecated Use {@link #setTextAppearance(int)} instead.
3814      */
3815     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)3816     public void setTextAppearance(Context context, @StyleRes int resId) {
3817         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3818         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
3819         readTextAppearance(context, ta, attributes, false /* styleArray */);
3820         ta.recycle();
3821         applyTextAppearance(attributes);
3822     }
3823 
3824     /**
3825      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
3826      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
3827      */
3828     private static class TextAppearanceAttributes {
3829         int mTextColorHighlight = 0;
3830         ColorStateList mTextColor = null;
3831         ColorStateList mTextColorHint = null;
3832         ColorStateList mTextColorLink = null;
3833         int mTextSize = -1;
3834         LocaleList mTextLocales = null;
3835         String mFontFamily = null;
3836         Typeface mFontTypeface = null;
3837         boolean mFontFamilyExplicit = false;
3838         int mTypefaceIndex = -1;
3839         int mTextStyle = 0;
3840         int mFontWeight = -1;
3841         boolean mAllCaps = false;
3842         int mShadowColor = 0;
3843         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
3844         boolean mHasElegant = false;
3845         boolean mElegant = false;
3846         boolean mHasFallbackLineSpacing = false;
3847         boolean mFallbackLineSpacing = false;
3848         boolean mHasLetterSpacing = false;
3849         float mLetterSpacing = 0;
3850         String mFontFeatureSettings = null;
3851         String mFontVariationSettings = null;
3852 
3853         @Override
toString()3854         public String toString() {
3855             return "TextAppearanceAttributes {\n"
3856                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
3857                     + "    mTextColor:" + mTextColor + "\n"
3858                     + "    mTextColorHint:" + mTextColorHint + "\n"
3859                     + "    mTextColorLink:" + mTextColorLink + "\n"
3860                     + "    mTextSize:" + mTextSize + "\n"
3861                     + "    mTextLocales:" + mTextLocales + "\n"
3862                     + "    mFontFamily:" + mFontFamily + "\n"
3863                     + "    mFontTypeface:" + mFontTypeface + "\n"
3864                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
3865                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
3866                     + "    mTextStyle:" + mTextStyle + "\n"
3867                     + "    mFontWeight:" + mFontWeight + "\n"
3868                     + "    mAllCaps:" + mAllCaps + "\n"
3869                     + "    mShadowColor:" + mShadowColor + "\n"
3870                     + "    mShadowDx:" + mShadowDx + "\n"
3871                     + "    mShadowDy:" + mShadowDy + "\n"
3872                     + "    mShadowRadius:" + mShadowRadius + "\n"
3873                     + "    mHasElegant:" + mHasElegant + "\n"
3874                     + "    mElegant:" + mElegant + "\n"
3875                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
3876                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
3877                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
3878                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
3879                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
3880                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
3881                     + "}";
3882         }
3883     }
3884 
3885     // Maps styleable attributes that exist both in TextView style and TextAppearance.
3886     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
3887     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3888         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
3889                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3890         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
3891                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3892         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
3893                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3894         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
3895                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3896         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
3897                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3898         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
3899                 com.android.internal.R.styleable.TextAppearance_textLocale);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3900         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
3901                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3902         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
3903                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3904         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
3905                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)3906         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
3907                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)3908         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
3909                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)3910         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
3911                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)3912         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
3913                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)3914         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
3915                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)3916         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
3917                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)3918         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
3919                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)3920         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
3921                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)3922         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
3923                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)3924         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
3925                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)3926         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
3927                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
3928     }
3929 
3930     /**
3931      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
3932      * set. If the TypedArray contains a value that was already set in the given attributes, that
3933      * will be overridden.
3934      *
3935      * @param context The Context to be used
3936      * @param appearance The TypedArray to read properties from
3937      * @param attributes the TextAppearanceAttributes to fill in
3938      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
3939      *                   what attribute indexes will be used to read the properties.
3940      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)3941     private void readTextAppearance(Context context, TypedArray appearance,
3942             TextAppearanceAttributes attributes, boolean styleArray) {
3943         final int n = appearance.getIndexCount();
3944         for (int i = 0; i < n; i++) {
3945             final int attr = appearance.getIndex(i);
3946             int index = attr;
3947             // Translate style array index ids to TextAppearance ids.
3948             if (styleArray) {
3949                 index = sAppearanceValues.get(attr, -1);
3950                 if (index == -1) {
3951                     // This value is not part of a Text Appearance and should be ignored.
3952                     continue;
3953                 }
3954             }
3955             switch (index) {
3956                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
3957                     attributes.mTextColorHighlight =
3958                             appearance.getColor(attr, attributes.mTextColorHighlight);
3959                     break;
3960                 case com.android.internal.R.styleable.TextAppearance_textColor:
3961                     attributes.mTextColor = appearance.getColorStateList(attr);
3962                     break;
3963                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
3964                     attributes.mTextColorHint = appearance.getColorStateList(attr);
3965                     break;
3966                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
3967                     attributes.mTextColorLink = appearance.getColorStateList(attr);
3968                     break;
3969                 case com.android.internal.R.styleable.TextAppearance_textSize:
3970                     attributes.mTextSize =
3971                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
3972                     break;
3973                 case com.android.internal.R.styleable.TextAppearance_textLocale:
3974                     final String localeString = appearance.getString(attr);
3975                     if (localeString != null) {
3976                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
3977                         if (!localeList.isEmpty()) {
3978                             attributes.mTextLocales = localeList;
3979                         }
3980                     }
3981                     break;
3982                 case com.android.internal.R.styleable.TextAppearance_typeface:
3983                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
3984                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
3985                         attributes.mFontFamily = null;
3986                     }
3987                     break;
3988                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
3989                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
3990                         try {
3991                             attributes.mFontTypeface = appearance.getFont(attr);
3992                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
3993                             // Expected if it is not a font resource.
3994                         }
3995                     }
3996                     if (attributes.mFontTypeface == null) {
3997                         attributes.mFontFamily = appearance.getString(attr);
3998                     }
3999                     attributes.mFontFamilyExplicit = true;
4000                     break;
4001                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4002                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4003                     break;
4004                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4005                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4006                     break;
4007                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4008                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4009                     break;
4010                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4011                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4012                     break;
4013                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4014                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4015                     break;
4016                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4017                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4018                     break;
4019                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4020                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4021                     break;
4022                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4023                     attributes.mHasElegant = true;
4024                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4025                     break;
4026                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4027                     attributes.mHasFallbackLineSpacing = true;
4028                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4029                             attributes.mFallbackLineSpacing);
4030                     break;
4031                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4032                     attributes.mHasLetterSpacing = true;
4033                     attributes.mLetterSpacing =
4034                             appearance.getFloat(attr, attributes.mLetterSpacing);
4035                     break;
4036                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4037                     attributes.mFontFeatureSettings = appearance.getString(attr);
4038                     break;
4039                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4040                     attributes.mFontVariationSettings = appearance.getString(attr);
4041                     break;
4042                 default:
4043             }
4044         }
4045     }
4046 
applyTextAppearance(TextAppearanceAttributes attributes)4047     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4048         if (attributes.mTextColor != null) {
4049             setTextColor(attributes.mTextColor);
4050         }
4051 
4052         if (attributes.mTextColorHint != null) {
4053             setHintTextColor(attributes.mTextColorHint);
4054         }
4055 
4056         if (attributes.mTextColorLink != null) {
4057             setLinkTextColor(attributes.mTextColorLink);
4058         }
4059 
4060         if (attributes.mTextColorHighlight != 0) {
4061             setHighlightColor(attributes.mTextColorHighlight);
4062         }
4063 
4064         if (attributes.mTextSize != -1) {
4065             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4066         }
4067 
4068         if (attributes.mTextLocales != null) {
4069             setTextLocales(attributes.mTextLocales);
4070         }
4071 
4072         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4073             attributes.mFontFamily = null;
4074         }
4075         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4076                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4077 
4078         if (attributes.mShadowColor != 0) {
4079             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4080                     attributes.mShadowColor);
4081         }
4082 
4083         if (attributes.mAllCaps) {
4084             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4085         }
4086 
4087         if (attributes.mHasElegant) {
4088             setElegantTextHeight(attributes.mElegant);
4089         }
4090 
4091         if (attributes.mHasFallbackLineSpacing) {
4092             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4093         }
4094 
4095         if (attributes.mHasLetterSpacing) {
4096             setLetterSpacing(attributes.mLetterSpacing);
4097         }
4098 
4099         if (attributes.mFontFeatureSettings != null) {
4100             setFontFeatureSettings(attributes.mFontFeatureSettings);
4101         }
4102 
4103         if (attributes.mFontVariationSettings != null) {
4104             setFontVariationSettings(attributes.mFontVariationSettings);
4105         }
4106     }
4107 
4108     /**
4109      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4110      * the first member of {@link #getTextLocales()}.
4111      * @return the default primary {@link Locale} of the text in this TextView.
4112      */
4113     @NonNull
getTextLocale()4114     public Locale getTextLocale() {
4115         return mTextPaint.getTextLocale();
4116     }
4117 
4118     /**
4119      * Get the default {@link LocaleList} of the text in this TextView.
4120      * @return the default {@link LocaleList} of the text in this TextView.
4121      */
4122     @NonNull @Size(min = 1)
getTextLocales()4123     public LocaleList getTextLocales() {
4124         return mTextPaint.getTextLocales();
4125     }
4126 
changeListenerLocaleTo(@ullable Locale locale)4127     private void changeListenerLocaleTo(@Nullable Locale locale) {
4128         if (mListenerChanged) {
4129             // If a listener has been explicitly set, don't change it. We may break something.
4130             return;
4131         }
4132         // The following null check is not absolutely necessary since all calling points of
4133         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4134         // here in case others would want to call this method in the future.
4135         if (mEditor != null) {
4136             KeyListener listener = mEditor.mKeyListener;
4137             if (listener instanceof DigitsKeyListener) {
4138                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4139             } else if (listener instanceof DateKeyListener) {
4140                 listener = DateKeyListener.getInstance(locale);
4141             } else if (listener instanceof TimeKeyListener) {
4142                 listener = TimeKeyListener.getInstance(locale);
4143             } else if (listener instanceof DateTimeKeyListener) {
4144                 listener = DateTimeKeyListener.getInstance(locale);
4145             } else {
4146                 return;
4147             }
4148             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4149             setKeyListenerOnly(listener);
4150             setInputTypeFromEditor();
4151             if (wasPasswordType) {
4152                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4153                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4154                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4155                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4156                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4157                 }
4158             }
4159         }
4160     }
4161 
4162     /**
4163      * Set the default {@link Locale} of the text in this TextView to a one-member
4164      * {@link LocaleList} containing just the given Locale.
4165      *
4166      * @param locale the {@link Locale} for drawing text, must not be null.
4167      *
4168      * @see #setTextLocales
4169      */
setTextLocale(@onNull Locale locale)4170     public void setTextLocale(@NonNull Locale locale) {
4171         mLocalesChanged = true;
4172         mTextPaint.setTextLocale(locale);
4173         if (mLayout != null) {
4174             nullLayouts();
4175             requestLayout();
4176             invalidate();
4177         }
4178     }
4179 
4180     /**
4181      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4182      *
4183      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4184      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4185      * other aspects of text display, including line breaking.
4186      *
4187      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4188      *
4189      * @see Paint#setTextLocales
4190      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)4191     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4192         mLocalesChanged = true;
4193         mTextPaint.setTextLocales(locales);
4194         if (mLayout != null) {
4195             nullLayouts();
4196             requestLayout();
4197             invalidate();
4198         }
4199     }
4200 
4201     @Override
onConfigurationChanged(Configuration newConfig)4202     protected void onConfigurationChanged(Configuration newConfig) {
4203         super.onConfigurationChanged(newConfig);
4204         if (!mLocalesChanged) {
4205             mTextPaint.setTextLocales(LocaleList.getDefault());
4206             if (mLayout != null) {
4207                 nullLayouts();
4208                 requestLayout();
4209                 invalidate();
4210             }
4211         }
4212     }
4213 
4214     /**
4215      * @return the size (in pixels) of the default text size in this TextView.
4216      */
4217     @InspectableProperty
4218     @ViewDebug.ExportedProperty(category = "text")
getTextSize()4219     public float getTextSize() {
4220         return mTextPaint.getTextSize();
4221     }
4222 
4223     /**
4224      * @return the size (in scaled pixels) of the default text size in this TextView.
4225      * @hide
4226      */
4227     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()4228     public float getScaledTextSize() {
4229         return mTextPaint.getTextSize() / mTextPaint.density;
4230     }
4231 
4232     /** @hide */
4233     @ViewDebug.ExportedProperty(category = "text", mapping = {
4234             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4235             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4236             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4237             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4238     })
getTypefaceStyle()4239     public int getTypefaceStyle() {
4240         Typeface typeface = mTextPaint.getTypeface();
4241         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4242     }
4243 
4244     /**
4245      * Set the default text size to the given value, interpreted as "scaled
4246      * pixel" units.  This size is adjusted based on the current density and
4247      * user font size preference.
4248      *
4249      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4250      *
4251      * @param size The scaled pixel size.
4252      *
4253      * @attr ref android.R.styleable#TextView_textSize
4254      */
4255     @android.view.RemotableViewMethod
setTextSize(float size)4256     public void setTextSize(float size) {
4257         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4258     }
4259 
4260     /**
4261      * Set the default text size to a given unit and value. See {@link
4262      * TypedValue} for the possible dimension units.
4263      *
4264      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4265      *
4266      * @param unit The desired dimension unit.
4267      * @param size The desired size in the given units.
4268      *
4269      * @attr ref android.R.styleable#TextView_textSize
4270      */
setTextSize(int unit, float size)4271     public void setTextSize(int unit, float size) {
4272         if (!isAutoSizeEnabled()) {
4273             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4274         }
4275     }
4276 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4277     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4278         Context c = getContext();
4279         Resources r;
4280 
4281         if (c == null) {
4282             r = Resources.getSystem();
4283         } else {
4284             r = c.getResources();
4285         }
4286 
4287         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
4288                 shouldRequestLayout);
4289     }
4290 
4291     @UnsupportedAppUsage
setRawTextSize(float size, boolean shouldRequestLayout)4292     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4293         if (size != mTextPaint.getTextSize()) {
4294             mTextPaint.setTextSize(size);
4295 
4296             if (shouldRequestLayout && mLayout != null) {
4297                 // Do not auto-size right after setting the text size.
4298                 mNeedsAutoSizeText = false;
4299                 nullLayouts();
4300                 requestLayout();
4301                 invalidate();
4302             }
4303         }
4304     }
4305 
4306     /**
4307      * Gets the extent by which text should be stretched horizontally.
4308      * This will usually be 1.0.
4309      * @return The horizontal scale factor.
4310      */
4311     @InspectableProperty
getTextScaleX()4312     public float getTextScaleX() {
4313         return mTextPaint.getTextScaleX();
4314     }
4315 
4316     /**
4317      * Sets the horizontal scale factor for text. The default value
4318      * is 1.0. Values greater than 1.0 stretch the text wider.
4319      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4320      * @param size The horizontal scale factor.
4321      * @attr ref android.R.styleable#TextView_textScaleX
4322      */
4323     @android.view.RemotableViewMethod
setTextScaleX(float size)4324     public void setTextScaleX(float size) {
4325         if (size != mTextPaint.getTextScaleX()) {
4326             mUserSetTextScaleX = true;
4327             mTextPaint.setTextScaleX(size);
4328 
4329             if (mLayout != null) {
4330                 nullLayouts();
4331                 requestLayout();
4332                 invalidate();
4333             }
4334         }
4335     }
4336 
4337     /**
4338      * Sets the typeface and style in which the text should be displayed.
4339      * Note that not all Typeface families actually have bold and italic
4340      * variants, so you may need to use
4341      * {@link #setTypeface(Typeface, int)} to get the appearance
4342      * that you actually want.
4343      *
4344      * @see #getTypeface()
4345      *
4346      * @attr ref android.R.styleable#TextView_fontFamily
4347      * @attr ref android.R.styleable#TextView_typeface
4348      * @attr ref android.R.styleable#TextView_textStyle
4349      */
setTypeface(@ullable Typeface tf)4350     public void setTypeface(@Nullable Typeface tf) {
4351         if (mTextPaint.getTypeface() != tf) {
4352             mTextPaint.setTypeface(tf);
4353 
4354             if (mLayout != null) {
4355                 nullLayouts();
4356                 requestLayout();
4357                 invalidate();
4358             }
4359         }
4360     }
4361 
4362     /**
4363      * Gets the current {@link Typeface} that is used to style the text.
4364      * @return The current Typeface.
4365      *
4366      * @see #setTypeface(Typeface)
4367      *
4368      * @attr ref android.R.styleable#TextView_fontFamily
4369      * @attr ref android.R.styleable#TextView_typeface
4370      * @attr ref android.R.styleable#TextView_textStyle
4371      */
4372     @InspectableProperty
getTypeface()4373     public Typeface getTypeface() {
4374         return mTextPaint.getTypeface();
4375     }
4376 
4377     /**
4378      * Set the TextView's elegant height metrics flag. This setting selects font
4379      * variants that have not been compacted to fit Latin-based vertical
4380      * metrics, and also increases top and bottom bounds to provide more space.
4381      *
4382      * @param elegant set the paint's elegant metrics flag.
4383      *
4384      * @see #isElegantTextHeight()
4385      * @see Paint#isElegantTextHeight()
4386      *
4387      * @attr ref android.R.styleable#TextView_elegantTextHeight
4388      */
setElegantTextHeight(boolean elegant)4389     public void setElegantTextHeight(boolean elegant) {
4390         if (elegant != mTextPaint.isElegantTextHeight()) {
4391             mTextPaint.setElegantTextHeight(elegant);
4392             if (mLayout != null) {
4393                 nullLayouts();
4394                 requestLayout();
4395                 invalidate();
4396             }
4397         }
4398     }
4399 
4400     /**
4401      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4402      * displaying the text (which is needed to avoid text from consecutive lines running into
4403      * each other). If set, fallback fonts that end up getting used can increase the ascent
4404      * and descent of the lines that they are used on.
4405      * <p/>
4406      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4407      * is typically much taller or deeper than Latin text.
4408      *
4409      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4410      *
4411      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4412      *
4413      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4414      */
setFallbackLineSpacing(boolean enabled)4415     public void setFallbackLineSpacing(boolean enabled) {
4416         if (mUseFallbackLineSpacing != enabled) {
4417             mUseFallbackLineSpacing = enabled;
4418             if (mLayout != null) {
4419                 nullLayouts();
4420                 requestLayout();
4421                 invalidate();
4422             }
4423         }
4424     }
4425 
4426     /**
4427      * @return whether fallback line spacing is enabled, {@code true} by default
4428      *
4429      * @see #setFallbackLineSpacing(boolean)
4430      *
4431      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4432      */
4433     @InspectableProperty
isFallbackLineSpacing()4434     public boolean isFallbackLineSpacing() {
4435         return mUseFallbackLineSpacing;
4436     }
4437 
4438     /**
4439      * Get the value of the TextView's elegant height metrics flag. This setting selects font
4440      * variants that have not been compacted to fit Latin-based vertical
4441      * metrics, and also increases top and bottom bounds to provide more space.
4442      * @return {@code true} if the elegant height metrics flag is set.
4443      *
4444      * @see #setElegantTextHeight(boolean)
4445      * @see Paint#setElegantTextHeight(boolean)
4446      */
4447     @InspectableProperty
isElegantTextHeight()4448     public boolean isElegantTextHeight() {
4449         return mTextPaint.isElegantTextHeight();
4450     }
4451 
4452     /**
4453      * Gets the text letter-space value, which determines the spacing between characters.
4454      * The value returned is in ems. Normally, this value is 0.0.
4455      * @return The text letter-space value in ems.
4456      *
4457      * @see #setLetterSpacing(float)
4458      * @see Paint#setLetterSpacing
4459      */
4460     @InspectableProperty
getLetterSpacing()4461     public float getLetterSpacing() {
4462         return mTextPaint.getLetterSpacing();
4463     }
4464 
4465     /**
4466      * Sets text letter-spacing in em units.  Typical values
4467      * for slight expansion will be around 0.05.  Negative values tighten text.
4468      *
4469      * @see #getLetterSpacing()
4470      * @see Paint#getLetterSpacing
4471      *
4472      * @param letterSpacing A text letter-space value in ems.
4473      * @attr ref android.R.styleable#TextView_letterSpacing
4474      */
4475     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)4476     public void setLetterSpacing(float letterSpacing) {
4477         if (letterSpacing != mTextPaint.getLetterSpacing()) {
4478             mTextPaint.setLetterSpacing(letterSpacing);
4479 
4480             if (mLayout != null) {
4481                 nullLayouts();
4482                 requestLayout();
4483                 invalidate();
4484             }
4485         }
4486     }
4487 
4488     /**
4489      * Returns the font feature settings. The format is the same as the CSS
4490      * font-feature-settings attribute:
4491      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4492      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4493      *
4494      * @return the currently set font feature settings.  Default is null.
4495      *
4496      * @see #setFontFeatureSettings(String)
4497      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
4498      */
4499     @InspectableProperty
4500     @Nullable
getFontFeatureSettings()4501     public String getFontFeatureSettings() {
4502         return mTextPaint.getFontFeatureSettings();
4503     }
4504 
4505     /**
4506      * Returns the font variation settings.
4507      *
4508      * @return the currently set font variation settings.  Returns null if no variation is
4509      * specified.
4510      *
4511      * @see #setFontVariationSettings(String)
4512      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
4513      */
4514     @Nullable
getFontVariationSettings()4515     public String getFontVariationSettings() {
4516         return mTextPaint.getFontVariationSettings();
4517     }
4518 
4519     /**
4520      * Sets the break strategy for breaking paragraphs into lines. The default value for
4521      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
4522      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
4523      * text "dancing" when being edited.
4524      * <p/>
4525      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4526      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4527      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4528      * improves the structure of text layout however has performance impact and requires more time
4529      * to do the text layout.
4530      *
4531      * @attr ref android.R.styleable#TextView_breakStrategy
4532      * @see #getBreakStrategy()
4533      * @see #setHyphenationFrequency(int)
4534      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4535     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
4536         mBreakStrategy = breakStrategy;
4537         if (mLayout != null) {
4538             nullLayouts();
4539             requestLayout();
4540             invalidate();
4541         }
4542     }
4543 
4544     /**
4545      * Gets the current strategy for breaking paragraphs into lines.
4546      * @return the current strategy for breaking paragraphs into lines.
4547      *
4548      * @attr ref android.R.styleable#TextView_breakStrategy
4549      * @see #setBreakStrategy(int)
4550      */
4551     @InspectableProperty(enumMapping = {
4552             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
4553             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
4554             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
4555     })
4556     @Layout.BreakStrategy
getBreakStrategy()4557     public int getBreakStrategy() {
4558         return mBreakStrategy;
4559     }
4560 
4561     /**
4562      * Sets the frequency of automatic hyphenation to use when determining word breaks.
4563      * The default value for both TextView and {@link EditText} is
4564      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
4565      * is set from the theme.
4566      * <p/>
4567      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4568      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4569      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4570      * improves the structure of text layout however has performance impact and requires more time
4571      * to do the text layout.
4572      * <p/>
4573      * Note: Before Android Q, in the theme hyphenation frequency is set to
4574      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
4575      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
4576      *
4577      * @param hyphenationFrequency the hyphenation frequency to use, one of
4578      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
4579      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
4580      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
4581      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4582      * @see #getHyphenationFrequency()
4583      * @see #getBreakStrategy()
4584      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4585     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
4586         mHyphenationFrequency = hyphenationFrequency;
4587         if (mLayout != null) {
4588             nullLayouts();
4589             requestLayout();
4590             invalidate();
4591         }
4592     }
4593 
4594     /**
4595      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
4596      * @return the current frequency of automatic hyphenation to be used when determining word
4597      * breaks.
4598      *
4599      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4600      * @see #setHyphenationFrequency(int)
4601      */
4602     @InspectableProperty(enumMapping = {
4603             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
4604             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
4605             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
4606     })
4607     @Layout.HyphenationFrequency
getHyphenationFrequency()4608     public int getHyphenationFrequency() {
4609         return mHyphenationFrequency;
4610     }
4611 
4612     /**
4613      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
4614      *
4615      * @return a current {@link PrecomputedText.Params}
4616      * @see PrecomputedText
4617      */
getTextMetricsParams()4618     public @NonNull PrecomputedText.Params getTextMetricsParams() {
4619         return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
4620                 mBreakStrategy, mHyphenationFrequency);
4621     }
4622 
4623     /**
4624      * Apply the text layout parameter.
4625      *
4626      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
4627      * @see PrecomputedText
4628      */
setTextMetricsParams(@onNull PrecomputedText.Params params)4629     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
4630         mTextPaint.set(params.getTextPaint());
4631         mUserSetTextScaleX = true;
4632         mTextDir = params.getTextDirection();
4633         mBreakStrategy = params.getBreakStrategy();
4634         mHyphenationFrequency = params.getHyphenationFrequency();
4635         if (mLayout != null) {
4636             nullLayouts();
4637             requestLayout();
4638             invalidate();
4639         }
4640     }
4641 
4642     /**
4643      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
4644      * last line is too short for justification, the last line will be displayed with the
4645      * alignment set by {@link android.view.View#setTextAlignment}.
4646      *
4647      * @see #getJustificationMode()
4648      */
4649     @Layout.JustificationMode
setJustificationMode(@ayout.JustificationMode int justificationMode)4650     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
4651         mJustificationMode = justificationMode;
4652         if (mLayout != null) {
4653             nullLayouts();
4654             requestLayout();
4655             invalidate();
4656         }
4657     }
4658 
4659     /**
4660      * @return true if currently paragraph justification mode.
4661      *
4662      * @see #setJustificationMode(int)
4663      */
4664     @InspectableProperty(enumMapping = {
4665             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
4666             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
4667     })
getJustificationMode()4668     public @Layout.JustificationMode int getJustificationMode() {
4669         return mJustificationMode;
4670     }
4671 
4672     /**
4673      * Sets font feature settings. The format is the same as the CSS
4674      * font-feature-settings attribute:
4675      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4676      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4677      *
4678      * @param fontFeatureSettings font feature settings represented as CSS compatible string
4679      *
4680      * @see #getFontFeatureSettings()
4681      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
4682      *
4683      * @attr ref android.R.styleable#TextView_fontFeatureSettings
4684      */
4685     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)4686     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
4687         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
4688             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
4689 
4690             if (mLayout != null) {
4691                 nullLayouts();
4692                 requestLayout();
4693                 invalidate();
4694             }
4695         }
4696     }
4697 
4698 
4699     /**
4700      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
4701      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
4702      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
4703      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
4704      * are invalid. If a specified axis name is not defined in the font, the settings will be
4705      * ignored.
4706      *
4707      * <p>
4708      * Examples,
4709      * <ul>
4710      * <li>Set font width to 150.
4711      * <pre>
4712      * <code>
4713      *   TextView textView = (TextView) findViewById(R.id.textView);
4714      *   textView.setFontVariationSettings("'wdth' 150");
4715      * </code>
4716      * </pre>
4717      * </li>
4718      *
4719      * <li>Set the font slant to 20 degrees and ask for italic style.
4720      * <pre>
4721      * <code>
4722      *   TextView textView = (TextView) findViewById(R.id.textView);
4723      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
4724      * </code>
4725      * </pre>
4726      * </p>
4727      * </li>
4728      * </ul>
4729      *
4730      * @param fontVariationSettings font variation settings. You can pass null or empty string as
4731      *                              no variation settings.
4732      * @return true if the given settings is effective to at least one font file underlying this
4733      *         TextView. This function also returns true for empty settings string. Otherwise
4734      *         returns false.
4735      *
4736      * @throws IllegalArgumentException If given string is not a valid font variation settings
4737      *                                  format.
4738      *
4739      * @see #getFontVariationSettings()
4740      * @see FontVariationAxis
4741      *
4742      * @attr ref android.R.styleable#TextView_fontVariationSettings
4743      */
setFontVariationSettings(@ullable String fontVariationSettings)4744     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
4745         final String existingSettings = mTextPaint.getFontVariationSettings();
4746         if (fontVariationSettings == existingSettings
4747                 || (fontVariationSettings != null
4748                         && fontVariationSettings.equals(existingSettings))) {
4749             return true;
4750         }
4751         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
4752 
4753         if (effective && mLayout != null) {
4754             nullLayouts();
4755             requestLayout();
4756             invalidate();
4757         }
4758         return effective;
4759     }
4760 
4761     /**
4762      * Sets the text color for all the states (normal, selected,
4763      * focused) to be this color.
4764      *
4765      * @param color A color value in the form 0xAARRGGBB.
4766      * Do not pass a resource ID. To get a color value from a resource ID, call
4767      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
4768      *
4769      * @see #setTextColor(ColorStateList)
4770      * @see #getTextColors()
4771      *
4772      * @attr ref android.R.styleable#TextView_textColor
4773      */
4774     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)4775     public void setTextColor(@ColorInt int color) {
4776         mTextColor = ColorStateList.valueOf(color);
4777         updateTextColors();
4778     }
4779 
4780     /**
4781      * Sets the text color.
4782      *
4783      * @see #setTextColor(int)
4784      * @see #getTextColors()
4785      * @see #setHintTextColor(ColorStateList)
4786      * @see #setLinkTextColor(ColorStateList)
4787      *
4788      * @attr ref android.R.styleable#TextView_textColor
4789      */
4790     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)4791     public void setTextColor(ColorStateList colors) {
4792         if (colors == null) {
4793             throw new NullPointerException();
4794         }
4795 
4796         mTextColor = colors;
4797         updateTextColors();
4798     }
4799 
4800     /**
4801      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
4802      *
4803      * @see #setTextColor(ColorStateList)
4804      * @see #setTextColor(int)
4805      *
4806      * @attr ref android.R.styleable#TextView_textColor
4807      */
4808     @InspectableProperty(name = "textColor")
getTextColors()4809     public final ColorStateList getTextColors() {
4810         return mTextColor;
4811     }
4812 
4813     /**
4814      * Return the current color selected for normal text.
4815      *
4816      * @return Returns the current text color.
4817      */
4818     @ColorInt
getCurrentTextColor()4819     public final int getCurrentTextColor() {
4820         return mCurTextColor;
4821     }
4822 
4823     /**
4824      * Sets the color used to display the selection highlight.
4825      *
4826      * @attr ref android.R.styleable#TextView_textColorHighlight
4827      */
4828     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)4829     public void setHighlightColor(@ColorInt int color) {
4830         if (mHighlightColor != color) {
4831             mHighlightColor = color;
4832             invalidate();
4833         }
4834     }
4835 
4836     /**
4837      * @return the color used to display the selection highlight
4838      *
4839      * @see #setHighlightColor(int)
4840      *
4841      * @attr ref android.R.styleable#TextView_textColorHighlight
4842      */
4843     @InspectableProperty(name = "textColorHighlight")
4844     @ColorInt
getHighlightColor()4845     public int getHighlightColor() {
4846         return mHighlightColor;
4847     }
4848 
4849     /**
4850      * Sets whether the soft input method will be made visible when this
4851      * TextView gets focused. The default is true.
4852      */
4853     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)4854     public final void setShowSoftInputOnFocus(boolean show) {
4855         createEditorIfNeeded();
4856         mEditor.mShowSoftInputOnFocus = show;
4857     }
4858 
4859     /**
4860      * Returns whether the soft input method will be made visible when this
4861      * TextView gets focused. The default is true.
4862      */
getShowSoftInputOnFocus()4863     public final boolean getShowSoftInputOnFocus() {
4864         // When there is no Editor, return default true value
4865         return mEditor == null || mEditor.mShowSoftInputOnFocus;
4866     }
4867 
4868     /**
4869      * Gives the text a shadow of the specified blur radius and color, the specified
4870      * distance from its drawn position.
4871      * <p>
4872      * The text shadow produced does not interact with the properties on view
4873      * that are responsible for real time shadows,
4874      * {@link View#getElevation() elevation} and
4875      * {@link View#getTranslationZ() translationZ}.
4876      *
4877      * @see Paint#setShadowLayer(float, float, float, int)
4878      *
4879      * @attr ref android.R.styleable#TextView_shadowColor
4880      * @attr ref android.R.styleable#TextView_shadowDx
4881      * @attr ref android.R.styleable#TextView_shadowDy
4882      * @attr ref android.R.styleable#TextView_shadowRadius
4883      */
setShadowLayer(float radius, float dx, float dy, int color)4884     public void setShadowLayer(float radius, float dx, float dy, int color) {
4885         mTextPaint.setShadowLayer(radius, dx, dy, color);
4886 
4887         mShadowRadius = radius;
4888         mShadowDx = dx;
4889         mShadowDy = dy;
4890         mShadowColor = color;
4891 
4892         // Will change text clip region
4893         if (mEditor != null) {
4894             mEditor.invalidateTextDisplayList();
4895             mEditor.invalidateHandlesAndActionMode();
4896         }
4897         invalidate();
4898     }
4899 
4900     /**
4901      * Gets the radius of the shadow layer.
4902      *
4903      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
4904      *
4905      * @see #setShadowLayer(float, float, float, int)
4906      *
4907      * @attr ref android.R.styleable#TextView_shadowRadius
4908      */
4909     @InspectableProperty
getShadowRadius()4910     public float getShadowRadius() {
4911         return mShadowRadius;
4912     }
4913 
4914     /**
4915      * @return the horizontal offset of the shadow layer
4916      *
4917      * @see #setShadowLayer(float, float, float, int)
4918      *
4919      * @attr ref android.R.styleable#TextView_shadowDx
4920      */
4921     @InspectableProperty
getShadowDx()4922     public float getShadowDx() {
4923         return mShadowDx;
4924     }
4925 
4926     /**
4927      * Gets the vertical offset of the shadow layer.
4928      * @return The vertical offset of the shadow layer.
4929      *
4930      * @see #setShadowLayer(float, float, float, int)
4931      *
4932      * @attr ref android.R.styleable#TextView_shadowDy
4933      */
4934     @InspectableProperty
getShadowDy()4935     public float getShadowDy() {
4936         return mShadowDy;
4937     }
4938 
4939     /**
4940      * Gets the color of the shadow layer.
4941      * @return the color of the shadow layer
4942      *
4943      * @see #setShadowLayer(float, float, float, int)
4944      *
4945      * @attr ref android.R.styleable#TextView_shadowColor
4946      */
4947     @InspectableProperty
4948     @ColorInt
getShadowColor()4949     public int getShadowColor() {
4950         return mShadowColor;
4951     }
4952 
4953     /**
4954      * Gets the {@link TextPaint} used for the text.
4955      * Use this only to consult the Paint's properties and not to change them.
4956      * @return The base paint used for the text.
4957      */
getPaint()4958     public TextPaint getPaint() {
4959         return mTextPaint;
4960     }
4961 
4962     /**
4963      * Sets the autolink mask of the text.  See {@link
4964      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
4965      * possible values.
4966      *
4967      * <p class="note"><b>Note:</b>
4968      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
4969      * is deprecated and should be avoided; see its documentation.
4970      *
4971      * @attr ref android.R.styleable#TextView_autoLink
4972      */
4973     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)4974     public final void setAutoLinkMask(int mask) {
4975         mAutoLinkMask = mask;
4976     }
4977 
4978     /**
4979      * Sets whether the movement method will automatically be set to
4980      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4981      * set to nonzero and links are detected in {@link #setText}.
4982      * The default is true.
4983      *
4984      * @attr ref android.R.styleable#TextView_linksClickable
4985      */
4986     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)4987     public final void setLinksClickable(boolean whether) {
4988         mLinksClickable = whether;
4989     }
4990 
4991     /**
4992      * Returns whether the movement method will automatically be set to
4993      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4994      * set to nonzero and links are detected in {@link #setText}.
4995      * The default is true.
4996      *
4997      * @attr ref android.R.styleable#TextView_linksClickable
4998      */
4999     @InspectableProperty
getLinksClickable()5000     public final boolean getLinksClickable() {
5001         return mLinksClickable;
5002     }
5003 
5004     /**
5005      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5006      * (by {@link Linkify} or otherwise) if any.  You can call
5007      * {@link URLSpan#getURL} on them to find where they link to
5008      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5009      * to find the region of the text they are attached to.
5010      */
getUrls()5011     public URLSpan[] getUrls() {
5012         if (mText instanceof Spanned) {
5013             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5014         } else {
5015             return new URLSpan[0];
5016         }
5017     }
5018 
5019     /**
5020      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5021      * TextView.
5022      *
5023      * @see #setHintTextColor(ColorStateList)
5024      * @see #getHintTextColors()
5025      * @see #setTextColor(int)
5026      *
5027      * @attr ref android.R.styleable#TextView_textColorHint
5028      */
5029     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)5030     public final void setHintTextColor(@ColorInt int color) {
5031         mHintTextColor = ColorStateList.valueOf(color);
5032         updateTextColors();
5033     }
5034 
5035     /**
5036      * Sets the color of the hint text.
5037      *
5038      * @see #getHintTextColors()
5039      * @see #setHintTextColor(int)
5040      * @see #setTextColor(ColorStateList)
5041      * @see #setLinkTextColor(ColorStateList)
5042      *
5043      * @attr ref android.R.styleable#TextView_textColorHint
5044      */
setHintTextColor(ColorStateList colors)5045     public final void setHintTextColor(ColorStateList colors) {
5046         mHintTextColor = colors;
5047         updateTextColors();
5048     }
5049 
5050     /**
5051      * @return the color of the hint text, for the different states of this TextView.
5052      *
5053      * @see #setHintTextColor(ColorStateList)
5054      * @see #setHintTextColor(int)
5055      * @see #setTextColor(ColorStateList)
5056      * @see #setLinkTextColor(ColorStateList)
5057      *
5058      * @attr ref android.R.styleable#TextView_textColorHint
5059      */
5060     @InspectableProperty(name = "textColorHint")
getHintTextColors()5061     public final ColorStateList getHintTextColors() {
5062         return mHintTextColor;
5063     }
5064 
5065     /**
5066      * <p>Return the current color selected to paint the hint text.</p>
5067      *
5068      * @return Returns the current hint text color.
5069      */
5070     @ColorInt
getCurrentHintTextColor()5071     public final int getCurrentHintTextColor() {
5072         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5073     }
5074 
5075     /**
5076      * Sets the color of links in the text.
5077      *
5078      * @see #setLinkTextColor(ColorStateList)
5079      * @see #getLinkTextColors()
5080      *
5081      * @attr ref android.R.styleable#TextView_textColorLink
5082      */
5083     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)5084     public final void setLinkTextColor(@ColorInt int color) {
5085         mLinkTextColor = ColorStateList.valueOf(color);
5086         updateTextColors();
5087     }
5088 
5089     /**
5090      * Sets the color of links in the text.
5091      *
5092      * @see #setLinkTextColor(int)
5093      * @see #getLinkTextColors()
5094      * @see #setTextColor(ColorStateList)
5095      * @see #setHintTextColor(ColorStateList)
5096      *
5097      * @attr ref android.R.styleable#TextView_textColorLink
5098      */
setLinkTextColor(ColorStateList colors)5099     public final void setLinkTextColor(ColorStateList colors) {
5100         mLinkTextColor = colors;
5101         updateTextColors();
5102     }
5103 
5104     /**
5105      * @return the list of colors used to paint the links in the text, for the different states of
5106      * this TextView
5107      *
5108      * @see #setLinkTextColor(ColorStateList)
5109      * @see #setLinkTextColor(int)
5110      *
5111      * @attr ref android.R.styleable#TextView_textColorLink
5112      */
5113     @InspectableProperty(name = "textColorLink")
getLinkTextColors()5114     public final ColorStateList getLinkTextColors() {
5115         return mLinkTextColor;
5116     }
5117 
5118     /**
5119      * Sets the horizontal alignment of the text and the
5120      * vertical gravity that will be used when there is extra space
5121      * in the TextView beyond what is required for the text itself.
5122      *
5123      * @see android.view.Gravity
5124      * @attr ref android.R.styleable#TextView_gravity
5125      */
setGravity(int gravity)5126     public void setGravity(int gravity) {
5127         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5128             gravity |= Gravity.START;
5129         }
5130         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5131             gravity |= Gravity.TOP;
5132         }
5133 
5134         boolean newLayout = false;
5135 
5136         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5137                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5138             newLayout = true;
5139         }
5140 
5141         if (gravity != mGravity) {
5142             invalidate();
5143         }
5144 
5145         mGravity = gravity;
5146 
5147         if (mLayout != null && newLayout) {
5148             // XXX this is heavy-handed because no actual content changes.
5149             int want = mLayout.getWidth();
5150             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5151 
5152             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5153                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5154         }
5155     }
5156 
5157     /**
5158      * Returns the horizontal and vertical alignment of this TextView.
5159      *
5160      * @see android.view.Gravity
5161      * @attr ref android.R.styleable#TextView_gravity
5162      */
5163     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()5164     public int getGravity() {
5165         return mGravity;
5166     }
5167 
5168     /**
5169      * Gets the flags on the Paint being used to display the text.
5170      * @return The flags on the Paint being used to display the text.
5171      * @see Paint#getFlags
5172      */
getPaintFlags()5173     public int getPaintFlags() {
5174         return mTextPaint.getFlags();
5175     }
5176 
5177     /**
5178      * Sets flags on the Paint being used to display the text and
5179      * reflows the text if they are different from the old flags.
5180      * @see Paint#setFlags
5181      */
5182     @android.view.RemotableViewMethod
setPaintFlags(int flags)5183     public void setPaintFlags(int flags) {
5184         if (mTextPaint.getFlags() != flags) {
5185             mTextPaint.setFlags(flags);
5186 
5187             if (mLayout != null) {
5188                 nullLayouts();
5189                 requestLayout();
5190                 invalidate();
5191             }
5192         }
5193     }
5194 
5195     /**
5196      * Sets whether the text should be allowed to be wider than the
5197      * View is.  If false, it will be wrapped to the width of the View.
5198      *
5199      * @attr ref android.R.styleable#TextView_scrollHorizontally
5200      */
setHorizontallyScrolling(boolean whether)5201     public void setHorizontallyScrolling(boolean whether) {
5202         if (mHorizontallyScrolling != whether) {
5203             mHorizontallyScrolling = whether;
5204 
5205             if (mLayout != null) {
5206                 nullLayouts();
5207                 requestLayout();
5208                 invalidate();
5209             }
5210         }
5211     }
5212 
5213     /**
5214      * Returns whether the text is allowed to be wider than the View.
5215      * If false, the text will be wrapped to the width of the View.
5216      *
5217      * @attr ref android.R.styleable#TextView_scrollHorizontally
5218      * @see #setHorizontallyScrolling(boolean)
5219      */
5220     @InspectableProperty(name = "scrollHorizontally")
isHorizontallyScrollable()5221     public final boolean isHorizontallyScrollable() {
5222         return mHorizontallyScrolling;
5223     }
5224 
5225     /**
5226      * Returns whether the text is allowed to be wider than the View.
5227      * If false, the text will be wrapped to the width of the View.
5228      *
5229      * @attr ref android.R.styleable#TextView_scrollHorizontally
5230      * @hide
5231      */
5232     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
getHorizontallyScrolling()5233     public boolean getHorizontallyScrolling() {
5234         return mHorizontallyScrolling;
5235     }
5236 
5237     /**
5238      * Sets the height of the TextView to be at least {@code minLines} tall.
5239      * <p>
5240      * This value is used for height calculation if LayoutParams does not force TextView to have an
5241      * exact height. Setting this value overrides other previous minimum height configurations such
5242      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
5243      * this value to 1.
5244      *
5245      * @param minLines the minimum height of TextView in terms of number of lines
5246      *
5247      * @see #getMinLines()
5248      * @see #setLines(int)
5249      *
5250      * @attr ref android.R.styleable#TextView_minLines
5251      */
5252     @android.view.RemotableViewMethod
setMinLines(int minLines)5253     public void setMinLines(int minLines) {
5254         mMinimum = minLines;
5255         mMinMode = LINES;
5256 
5257         requestLayout();
5258         invalidate();
5259     }
5260 
5261     /**
5262      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
5263      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
5264      *
5265      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
5266      *         height is not defined in lines
5267      *
5268      * @see #setMinLines(int)
5269      * @see #setLines(int)
5270      *
5271      * @attr ref android.R.styleable#TextView_minLines
5272      */
5273     @InspectableProperty
getMinLines()5274     public int getMinLines() {
5275         return mMinMode == LINES ? mMinimum : -1;
5276     }
5277 
5278     /**
5279      * Sets the height of the TextView to be at least {@code minPixels} tall.
5280      * <p>
5281      * This value is used for height calculation if LayoutParams does not force TextView to have an
5282      * exact height. Setting this value overrides previous minimum height configurations such as
5283      * {@link #setMinLines(int)} or {@link #setLines(int)}.
5284      * <p>
5285      * The value given here is different than {@link #setMinimumHeight(int)}. Between
5286      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
5287      * used to decide the final height.
5288      *
5289      * @param minPixels the minimum height of TextView in terms of pixels
5290      *
5291      * @see #getMinHeight()
5292      * @see #setHeight(int)
5293      *
5294      * @attr ref android.R.styleable#TextView_minHeight
5295      */
5296     @android.view.RemotableViewMethod
setMinHeight(int minPixels)5297     public void setMinHeight(int minPixels) {
5298         mMinimum = minPixels;
5299         mMinMode = PIXELS;
5300 
5301         requestLayout();
5302         invalidate();
5303     }
5304 
5305     /**
5306      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
5307      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
5308      *
5309      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
5310      *         defined in pixels
5311      *
5312      * @see #setMinHeight(int)
5313      * @see #setHeight(int)
5314      *
5315      * @attr ref android.R.styleable#TextView_minHeight
5316      */
getMinHeight()5317     public int getMinHeight() {
5318         return mMinMode == PIXELS ? mMinimum : -1;
5319     }
5320 
5321     /**
5322      * Sets the height of the TextView to be at most {@code maxLines} tall.
5323      * <p>
5324      * This value is used for height calculation if LayoutParams does not force TextView to have an
5325      * exact height. Setting this value overrides previous maximum height configurations such as
5326      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
5327      *
5328      * @param maxLines the maximum height of TextView in terms of number of lines
5329      *
5330      * @see #getMaxLines()
5331      * @see #setLines(int)
5332      *
5333      * @attr ref android.R.styleable#TextView_maxLines
5334      */
5335     @android.view.RemotableViewMethod
setMaxLines(int maxLines)5336     public void setMaxLines(int maxLines) {
5337         mMaximum = maxLines;
5338         mMaxMode = LINES;
5339 
5340         requestLayout();
5341         invalidate();
5342     }
5343 
5344     /**
5345      * Returns the maximum height of TextView in terms of number of lines or -1 if the
5346      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
5347      *
5348      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
5349      *         is not defined in lines.
5350      *
5351      * @see #setMaxLines(int)
5352      * @see #setLines(int)
5353      *
5354      * @attr ref android.R.styleable#TextView_maxLines
5355      */
5356     @InspectableProperty
getMaxLines()5357     public int getMaxLines() {
5358         return mMaxMode == LINES ? mMaximum : -1;
5359     }
5360 
5361     /**
5362      * Sets the height of the TextView to be at most {@code maxPixels} tall.
5363      * <p>
5364      * This value is used for height calculation if LayoutParams does not force TextView to have an
5365      * exact height. Setting this value overrides previous maximum height configurations such as
5366      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
5367      *
5368      * @param maxPixels the maximum height of TextView in terms of pixels
5369      *
5370      * @see #getMaxHeight()
5371      * @see #setHeight(int)
5372      *
5373      * @attr ref android.R.styleable#TextView_maxHeight
5374      */
5375     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)5376     public void setMaxHeight(int maxPixels) {
5377         mMaximum = maxPixels;
5378         mMaxMode = PIXELS;
5379 
5380         requestLayout();
5381         invalidate();
5382     }
5383 
5384     /**
5385      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
5386      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
5387      *
5388      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
5389      *         is not defined in pixels
5390      *
5391      * @see #setMaxHeight(int)
5392      * @see #setHeight(int)
5393      *
5394      * @attr ref android.R.styleable#TextView_maxHeight
5395      */
5396     @InspectableProperty
getMaxHeight()5397     public int getMaxHeight() {
5398         return mMaxMode == PIXELS ? mMaximum : -1;
5399     }
5400 
5401     /**
5402      * Sets the height of the TextView to be exactly {@code lines} tall.
5403      * <p>
5404      * This value is used for height calculation if LayoutParams does not force TextView to have an
5405      * exact height. Setting this value overrides previous minimum/maximum height configurations
5406      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
5407      * set this value to 1.
5408      *
5409      * @param lines the exact height of the TextView in terms of lines
5410      *
5411      * @see #setHeight(int)
5412      *
5413      * @attr ref android.R.styleable#TextView_lines
5414      */
5415     @android.view.RemotableViewMethod
setLines(int lines)5416     public void setLines(int lines) {
5417         mMaximum = mMinimum = lines;
5418         mMaxMode = mMinMode = LINES;
5419 
5420         requestLayout();
5421         invalidate();
5422     }
5423 
5424     /**
5425      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
5426      * <p>
5427      * This value is used for height calculation if LayoutParams does not force TextView to have an
5428      * exact height. Setting this value overrides previous minimum/maximum height configurations
5429      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
5430      *
5431      * @param pixels the exact height of the TextView in terms of pixels
5432      *
5433      * @see #setLines(int)
5434      *
5435      * @attr ref android.R.styleable#TextView_height
5436      */
5437     @android.view.RemotableViewMethod
setHeight(int pixels)5438     public void setHeight(int pixels) {
5439         mMaximum = mMinimum = pixels;
5440         mMaxMode = mMinMode = PIXELS;
5441 
5442         requestLayout();
5443         invalidate();
5444     }
5445 
5446     /**
5447      * Sets the width of the TextView to be at least {@code minEms} wide.
5448      * <p>
5449      * This value is used for width calculation if LayoutParams does not force TextView to have an
5450      * exact width. Setting this value overrides previous minimum width configurations such as
5451      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5452      *
5453      * @param minEms the minimum width of TextView in terms of ems
5454      *
5455      * @see #getMinEms()
5456      * @see #setEms(int)
5457      *
5458      * @attr ref android.R.styleable#TextView_minEms
5459      */
5460     @android.view.RemotableViewMethod
setMinEms(int minEms)5461     public void setMinEms(int minEms) {
5462         mMinWidth = minEms;
5463         mMinWidthMode = EMS;
5464 
5465         requestLayout();
5466         invalidate();
5467     }
5468 
5469     /**
5470      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
5471      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5472      *
5473      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
5474      *         defined in ems
5475      *
5476      * @see #setMinEms(int)
5477      * @see #setEms(int)
5478      *
5479      * @attr ref android.R.styleable#TextView_minEms
5480      */
5481     @InspectableProperty
getMinEms()5482     public int getMinEms() {
5483         return mMinWidthMode == EMS ? mMinWidth : -1;
5484     }
5485 
5486     /**
5487      * Sets the width of the TextView to be at least {@code minPixels} wide.
5488      * <p>
5489      * This value is used for width calculation if LayoutParams does not force TextView to have an
5490      * exact width. Setting this value overrides previous minimum width configurations such as
5491      * {@link #setMinEms(int)} or {@link #setEms(int)}.
5492      * <p>
5493      * The value given here is different than {@link #setMinimumWidth(int)}. Between
5494      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
5495      * to decide the final width.
5496      *
5497      * @param minPixels the minimum width of TextView in terms of pixels
5498      *
5499      * @see #getMinWidth()
5500      * @see #setWidth(int)
5501      *
5502      * @attr ref android.R.styleable#TextView_minWidth
5503      */
5504     @android.view.RemotableViewMethod
setMinWidth(int minPixels)5505     public void setMinWidth(int minPixels) {
5506         mMinWidth = minPixels;
5507         mMinWidthMode = PIXELS;
5508 
5509         requestLayout();
5510         invalidate();
5511     }
5512 
5513     /**
5514      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
5515      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
5516      *
5517      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
5518      *         defined in pixels
5519      *
5520      * @see #setMinWidth(int)
5521      * @see #setWidth(int)
5522      *
5523      * @attr ref android.R.styleable#TextView_minWidth
5524      */
5525     @InspectableProperty
getMinWidth()5526     public int getMinWidth() {
5527         return mMinWidthMode == PIXELS ? mMinWidth : -1;
5528     }
5529 
5530     /**
5531      * Sets the width of the TextView to be at most {@code maxEms} wide.
5532      * <p>
5533      * This value is used for width calculation if LayoutParams does not force TextView to have an
5534      * exact width. Setting this value overrides previous maximum width configurations such as
5535      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5536      *
5537      * @param maxEms the maximum width of TextView in terms of ems
5538      *
5539      * @see #getMaxEms()
5540      * @see #setEms(int)
5541      *
5542      * @attr ref android.R.styleable#TextView_maxEms
5543      */
5544     @android.view.RemotableViewMethod
setMaxEms(int maxEms)5545     public void setMaxEms(int maxEms) {
5546         mMaxWidth = maxEms;
5547         mMaxWidthMode = EMS;
5548 
5549         requestLayout();
5550         invalidate();
5551     }
5552 
5553     /**
5554      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
5555      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5556      *
5557      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
5558      *         defined in ems
5559      *
5560      * @see #setMaxEms(int)
5561      * @see #setEms(int)
5562      *
5563      * @attr ref android.R.styleable#TextView_maxEms
5564      */
5565     @InspectableProperty
getMaxEms()5566     public int getMaxEms() {
5567         return mMaxWidthMode == EMS ? mMaxWidth : -1;
5568     }
5569 
5570     /**
5571      * Sets the width of the TextView to be at most {@code maxPixels} wide.
5572      * <p>
5573      * This value is used for width calculation if LayoutParams does not force TextView to have an
5574      * exact width. Setting this value overrides previous maximum width configurations such as
5575      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
5576      *
5577      * @param maxPixels the maximum width of TextView in terms of pixels
5578      *
5579      * @see #getMaxWidth()
5580      * @see #setWidth(int)
5581      *
5582      * @attr ref android.R.styleable#TextView_maxWidth
5583      */
5584     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)5585     public void setMaxWidth(int maxPixels) {
5586         mMaxWidth = maxPixels;
5587         mMaxWidthMode = PIXELS;
5588 
5589         requestLayout();
5590         invalidate();
5591     }
5592 
5593     /**
5594      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
5595      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
5596      *
5597      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
5598      *         defined in pixels
5599      *
5600      * @see #setMaxWidth(int)
5601      * @see #setWidth(int)
5602      *
5603      * @attr ref android.R.styleable#TextView_maxWidth
5604      */
5605     @InspectableProperty
getMaxWidth()5606     public int getMaxWidth() {
5607         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
5608     }
5609 
5610     /**
5611      * Sets the width of the TextView to be exactly {@code ems} wide.
5612      *
5613      * This value is used for width calculation if LayoutParams does not force TextView to have an
5614      * exact width. Setting this value overrides previous minimum/maximum configurations such as
5615      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
5616      *
5617      * @param ems the exact width of the TextView in terms of ems
5618      *
5619      * @see #setWidth(int)
5620      *
5621      * @attr ref android.R.styleable#TextView_ems
5622      */
5623     @android.view.RemotableViewMethod
setEms(int ems)5624     public void setEms(int ems) {
5625         mMaxWidth = mMinWidth = ems;
5626         mMaxWidthMode = mMinWidthMode = EMS;
5627 
5628         requestLayout();
5629         invalidate();
5630     }
5631 
5632     /**
5633      * Sets the width of the TextView to be exactly {@code pixels} wide.
5634      * <p>
5635      * This value is used for width calculation if LayoutParams does not force TextView to have an
5636      * exact width. Setting this value overrides previous minimum/maximum width configurations
5637      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
5638      *
5639      * @param pixels the exact width of the TextView in terms of pixels
5640      *
5641      * @see #setEms(int)
5642      *
5643      * @attr ref android.R.styleable#TextView_width
5644      */
5645     @android.view.RemotableViewMethod
setWidth(int pixels)5646     public void setWidth(int pixels) {
5647         mMaxWidth = mMinWidth = pixels;
5648         mMaxWidthMode = mMinWidthMode = PIXELS;
5649 
5650         requestLayout();
5651         invalidate();
5652     }
5653 
5654     /**
5655      * Sets line spacing for this TextView.  Each line other than the last line will have its height
5656      * multiplied by {@code mult} and have {@code add} added to it.
5657      *
5658      * @param add The value in pixels that should be added to each line other than the last line.
5659      *            This will be applied after the multiplier
5660      * @param mult The value by which each line height other than the last line will be multiplied
5661      *             by
5662      *
5663      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5664      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5665      */
setLineSpacing(float add, float mult)5666     public void setLineSpacing(float add, float mult) {
5667         if (mSpacingAdd != add || mSpacingMult != mult) {
5668             mSpacingAdd = add;
5669             mSpacingMult = mult;
5670 
5671             if (mLayout != null) {
5672                 nullLayouts();
5673                 requestLayout();
5674                 invalidate();
5675             }
5676         }
5677     }
5678 
5679     /**
5680      * Gets the line spacing multiplier
5681      *
5682      * @return the value by which each line's height is multiplied to get its actual height.
5683      *
5684      * @see #setLineSpacing(float, float)
5685      * @see #getLineSpacingExtra()
5686      *
5687      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5688      */
5689     @InspectableProperty
getLineSpacingMultiplier()5690     public float getLineSpacingMultiplier() {
5691         return mSpacingMult;
5692     }
5693 
5694     /**
5695      * Gets the line spacing extra space
5696      *
5697      * @return the extra space that is added to the height of each lines of this TextView.
5698      *
5699      * @see #setLineSpacing(float, float)
5700      * @see #getLineSpacingMultiplier()
5701      *
5702      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5703      */
5704     @InspectableProperty
getLineSpacingExtra()5705     public float getLineSpacingExtra() {
5706         return mSpacingAdd;
5707     }
5708 
5709     /**
5710      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
5711      * between subsequent baselines in the TextView.
5712      *
5713      * @param lineHeight the line height in pixels
5714      *
5715      * @see #setLineSpacing(float, float)
5716      * @see #getLineSpacingExtra()
5717      *
5718      * @attr ref android.R.styleable#TextView_lineHeight
5719      */
setLineHeight(@x @ntRangefrom = 0) int lineHeight)5720     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
5721         Preconditions.checkArgumentNonnegative(lineHeight);
5722 
5723         final int fontHeight = getPaint().getFontMetricsInt(null);
5724         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
5725         if (lineHeight != fontHeight) {
5726             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
5727             setLineSpacing(lineHeight - fontHeight, 1f);
5728         }
5729     }
5730 
5731     /**
5732      * Convenience method to append the specified text to the TextView's
5733      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5734      * if it was not already editable.
5735      *
5736      * @param text text to be appended to the already displayed text
5737      */
append(CharSequence text)5738     public final void append(CharSequence text) {
5739         append(text, 0, text.length());
5740     }
5741 
5742     /**
5743      * Convenience method to append the specified text slice to the TextView's
5744      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5745      * if it was not already editable.
5746      *
5747      * @param text text to be appended to the already displayed text
5748      * @param start the index of the first character in the {@code text}
5749      * @param end the index of the character following the last character in the {@code text}
5750      *
5751      * @see Appendable#append(CharSequence, int, int)
5752      */
append(CharSequence text, int start, int end)5753     public void append(CharSequence text, int start, int end) {
5754         if (!(mText instanceof Editable)) {
5755             setText(mText, BufferType.EDITABLE);
5756         }
5757 
5758         ((Editable) mText).append(text, start, end);
5759 
5760         if (mAutoLinkMask != 0) {
5761             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
5762             // Do not change the movement method for text that support text selection as it
5763             // would prevent an arbitrary cursor displacement.
5764             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
5765                 setMovementMethod(LinkMovementMethod.getInstance());
5766             }
5767         }
5768     }
5769 
updateTextColors()5770     private void updateTextColors() {
5771         boolean inval = false;
5772         final int[] drawableState = getDrawableState();
5773         int color = mTextColor.getColorForState(drawableState, 0);
5774         if (color != mCurTextColor) {
5775             mCurTextColor = color;
5776             inval = true;
5777         }
5778         if (mLinkTextColor != null) {
5779             color = mLinkTextColor.getColorForState(drawableState, 0);
5780             if (color != mTextPaint.linkColor) {
5781                 mTextPaint.linkColor = color;
5782                 inval = true;
5783             }
5784         }
5785         if (mHintTextColor != null) {
5786             color = mHintTextColor.getColorForState(drawableState, 0);
5787             if (color != mCurHintTextColor) {
5788                 mCurHintTextColor = color;
5789                 if (mText.length() == 0) {
5790                     inval = true;
5791                 }
5792             }
5793         }
5794         if (inval) {
5795             // Text needs to be redrawn with the new color
5796             if (mEditor != null) mEditor.invalidateTextDisplayList();
5797             invalidate();
5798         }
5799     }
5800 
5801     @Override
drawableStateChanged()5802     protected void drawableStateChanged() {
5803         super.drawableStateChanged();
5804 
5805         if (mTextColor != null && mTextColor.isStateful()
5806                 || (mHintTextColor != null && mHintTextColor.isStateful())
5807                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
5808             updateTextColors();
5809         }
5810 
5811         if (mDrawables != null) {
5812             final int[] state = getDrawableState();
5813             for (Drawable dr : mDrawables.mShowing) {
5814                 if (dr != null && dr.isStateful() && dr.setState(state)) {
5815                     invalidateDrawable(dr);
5816                 }
5817             }
5818         }
5819     }
5820 
5821     @Override
drawableHotspotChanged(float x, float y)5822     public void drawableHotspotChanged(float x, float y) {
5823         super.drawableHotspotChanged(x, y);
5824 
5825         if (mDrawables != null) {
5826             for (Drawable dr : mDrawables.mShowing) {
5827                 if (dr != null) {
5828                     dr.setHotspot(x, y);
5829                 }
5830             }
5831         }
5832     }
5833 
5834     @Override
onSaveInstanceState()5835     public Parcelable onSaveInstanceState() {
5836         Parcelable superState = super.onSaveInstanceState();
5837 
5838         // Save state if we are forced to
5839         final boolean freezesText = getFreezesText();
5840         boolean hasSelection = false;
5841         int start = -1;
5842         int end = -1;
5843 
5844         if (mText != null) {
5845             start = getSelectionStart();
5846             end = getSelectionEnd();
5847             if (start >= 0 || end >= 0) {
5848                 // Or save state if there is a selection
5849                 hasSelection = true;
5850             }
5851         }
5852 
5853         if (freezesText || hasSelection) {
5854             SavedState ss = new SavedState(superState);
5855 
5856             if (freezesText) {
5857                 if (mText instanceof Spanned) {
5858                     final Spannable sp = new SpannableStringBuilder(mText);
5859 
5860                     if (mEditor != null) {
5861                         removeMisspelledSpans(sp);
5862                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
5863                     }
5864 
5865                     ss.text = sp;
5866                 } else {
5867                     ss.text = mText.toString();
5868                 }
5869             }
5870 
5871             if (hasSelection) {
5872                 // XXX Should also save the current scroll position!
5873                 ss.selStart = start;
5874                 ss.selEnd = end;
5875             }
5876 
5877             if (isFocused() && start >= 0 && end >= 0) {
5878                 ss.frozenWithFocus = true;
5879             }
5880 
5881             ss.error = getError();
5882 
5883             if (mEditor != null) {
5884                 ss.editorState = mEditor.saveInstanceState();
5885             }
5886             return ss;
5887         }
5888 
5889         return superState;
5890     }
5891 
removeMisspelledSpans(Spannable spannable)5892     void removeMisspelledSpans(Spannable spannable) {
5893         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
5894                 SuggestionSpan.class);
5895         for (int i = 0; i < suggestionSpans.length; i++) {
5896             int flags = suggestionSpans[i].getFlags();
5897             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
5898                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
5899                 spannable.removeSpan(suggestionSpans[i]);
5900             }
5901         }
5902     }
5903 
5904     @Override
onRestoreInstanceState(Parcelable state)5905     public void onRestoreInstanceState(Parcelable state) {
5906         if (!(state instanceof SavedState)) {
5907             super.onRestoreInstanceState(state);
5908             return;
5909         }
5910 
5911         SavedState ss = (SavedState) state;
5912         super.onRestoreInstanceState(ss.getSuperState());
5913 
5914         // XXX restore buffer type too, as well as lots of other stuff
5915         if (ss.text != null) {
5916             setText(ss.text);
5917         }
5918 
5919         if (ss.selStart >= 0 && ss.selEnd >= 0) {
5920             if (mSpannable != null) {
5921                 int len = mText.length();
5922 
5923                 if (ss.selStart > len || ss.selEnd > len) {
5924                     String restored = "";
5925 
5926                     if (ss.text != null) {
5927                         restored = "(restored) ";
5928                     }
5929 
5930                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
5931                             + " out of range for " + restored + "text " + mText);
5932                 } else {
5933                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
5934 
5935                     if (ss.frozenWithFocus) {
5936                         createEditorIfNeeded();
5937                         mEditor.mFrozenWithFocus = true;
5938                     }
5939                 }
5940             }
5941         }
5942 
5943         if (ss.error != null) {
5944             final CharSequence error = ss.error;
5945             // Display the error later, after the first layout pass
5946             post(new Runnable() {
5947                 public void run() {
5948                     if (mEditor == null || !mEditor.mErrorWasChanged) {
5949                         setError(error);
5950                     }
5951                 }
5952             });
5953         }
5954 
5955         if (ss.editorState != null) {
5956             createEditorIfNeeded();
5957             mEditor.restoreInstanceState(ss.editorState);
5958         }
5959     }
5960 
5961     /**
5962      * Control whether this text view saves its entire text contents when
5963      * freezing to an icicle, in addition to dynamic state such as cursor
5964      * position.  By default this is false, not saving the text.  Set to true
5965      * if the text in the text view is not being saved somewhere else in
5966      * persistent storage (such as in a content provider) so that if the
5967      * view is later thawed the user will not lose their data. For
5968      * {@link android.widget.EditText} it is always enabled, regardless of
5969      * the value of the attribute.
5970      *
5971      * @param freezesText Controls whether a frozen icicle should include the
5972      * entire text data: true to include it, false to not.
5973      *
5974      * @attr ref android.R.styleable#TextView_freezesText
5975      */
5976     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)5977     public void setFreezesText(boolean freezesText) {
5978         mFreezesText = freezesText;
5979     }
5980 
5981     /**
5982      * Return whether this text view is including its entire text contents
5983      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
5984      *
5985      * @return Returns true if text is included, false if it isn't.
5986      *
5987      * @see #setFreezesText
5988      */
5989     @InspectableProperty
getFreezesText()5990     public boolean getFreezesText() {
5991         return mFreezesText;
5992     }
5993 
5994     ///////////////////////////////////////////////////////////////////////////
5995 
5996     /**
5997      * Sets the Factory used to create new {@link Editable Editables}.
5998      *
5999      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
6000      *
6001      * @see android.text.Editable.Factory
6002      * @see android.widget.TextView.BufferType#EDITABLE
6003      */
setEditableFactory(Editable.Factory factory)6004     public final void setEditableFactory(Editable.Factory factory) {
6005         mEditableFactory = factory;
6006         setText(mText);
6007     }
6008 
6009     /**
6010      * Sets the Factory used to create new {@link Spannable Spannables}.
6011      *
6012      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
6013      *
6014      * @see android.text.Spannable.Factory
6015      * @see android.widget.TextView.BufferType#SPANNABLE
6016      */
setSpannableFactory(Spannable.Factory factory)6017     public final void setSpannableFactory(Spannable.Factory factory) {
6018         mSpannableFactory = factory;
6019         setText(mText);
6020     }
6021 
6022     /**
6023      * Sets the text to be displayed. TextView <em>does not</em> accept
6024      * HTML-like formatting, which you can do with text strings in XML resource files.
6025      * To style your strings, attach android.text.style.* objects to a
6026      * {@link android.text.SpannableString}, or see the
6027      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
6028      * Available Resource Types</a> documentation for an example of setting
6029      * formatted text in the XML resource file.
6030      * <p/>
6031      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6032      * intermediate {@link Spannable Spannables}. Likewise it will use
6033      * {@link android.text.Editable.Factory} to create final or intermediate
6034      * {@link Editable Editables}.
6035      *
6036      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
6037      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
6038      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
6039      *
6040      * @param text text to be displayed
6041      *
6042      * @attr ref android.R.styleable#TextView_text
6043      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
6044      *                                  parameters used to create the PrecomputedText mismatches
6045      *                                  with this TextView.
6046      */
6047     @android.view.RemotableViewMethod
setText(CharSequence text)6048     public final void setText(CharSequence text) {
6049         setText(text, mBufferType);
6050     }
6051 
6052     /**
6053      * Sets the text to be displayed but retains the cursor position. Same as
6054      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
6055      * new text.
6056      * <p/>
6057      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6058      * intermediate {@link Spannable Spannables}. Likewise it will use
6059      * {@link android.text.Editable.Factory} to create final or intermediate
6060      * {@link Editable Editables}.
6061      *
6062      * @param text text to be displayed
6063      *
6064      * @see #setText(CharSequence)
6065      */
6066     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)6067     public final void setTextKeepState(CharSequence text) {
6068         setTextKeepState(text, mBufferType);
6069     }
6070 
6071     /**
6072      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
6073      * <p/>
6074      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6075      * intermediate {@link Spannable Spannables}. Likewise it will use
6076      * {@link android.text.Editable.Factory} to create final or intermediate
6077      * {@link Editable Editables}.
6078      *
6079      * Subclasses overriding this method should ensure that the following post condition holds,
6080      * in order to guarantee the safety of the view's measurement and layout operations:
6081      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
6082      * will be different from {@code null}.
6083      *
6084      * @param text text to be displayed
6085      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6086      *              stored as a static text, styleable/spannable text, or editable text
6087      *
6088      * @see #setText(CharSequence)
6089      * @see android.widget.TextView.BufferType
6090      * @see #setSpannableFactory(Spannable.Factory)
6091      * @see #setEditableFactory(Editable.Factory)
6092      *
6093      * @attr ref android.R.styleable#TextView_text
6094      * @attr ref android.R.styleable#TextView_bufferType
6095      */
setText(CharSequence text, BufferType type)6096     public void setText(CharSequence text, BufferType type) {
6097         setText(text, type, true, 0);
6098 
6099         if (mCharWrapper != null) {
6100             mCharWrapper.mChars = null;
6101         }
6102     }
6103 
6104     @UnsupportedAppUsage
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6105     private void setText(CharSequence text, BufferType type,
6106                          boolean notifyBefore, int oldlen) {
6107         mTextSetFromXmlOrResourceId = false;
6108         if (text == null) {
6109             text = "";
6110         }
6111 
6112         // If suggestions are not enabled, remove the suggestion spans from the text
6113         if (!isSuggestionsEnabled()) {
6114             text = removeSuggestionSpans(text);
6115         }
6116 
6117         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
6118 
6119         if (text instanceof Spanned
6120                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
6121             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
6122                 setHorizontalFadingEdgeEnabled(true);
6123                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
6124             } else {
6125                 setHorizontalFadingEdgeEnabled(false);
6126                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
6127             }
6128             setEllipsize(TextUtils.TruncateAt.MARQUEE);
6129         }
6130 
6131         int n = mFilters.length;
6132         for (int i = 0; i < n; i++) {
6133             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
6134             if (out != null) {
6135                 text = out;
6136             }
6137         }
6138 
6139         if (notifyBefore) {
6140             if (mText != null) {
6141                 oldlen = mText.length();
6142                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
6143             } else {
6144                 sendBeforeTextChanged("", 0, 0, text.length());
6145             }
6146         }
6147 
6148         boolean needEditableForNotification = false;
6149 
6150         if (mListeners != null && mListeners.size() != 0) {
6151             needEditableForNotification = true;
6152         }
6153 
6154         PrecomputedText precomputed =
6155                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
6156         if (type == BufferType.EDITABLE || getKeyListener() != null
6157                 || needEditableForNotification) {
6158             createEditorIfNeeded();
6159             mEditor.forgetUndoRedo();
6160             Editable t = mEditableFactory.newEditable(text);
6161             text = t;
6162             setFilters(t, mFilters);
6163             InputMethodManager imm = getInputMethodManager();
6164             if (imm != null) imm.restartInput(this);
6165         } else if (precomputed != null) {
6166             if (mTextDir == null) {
6167                 mTextDir = getTextDirectionHeuristic();
6168             }
6169             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
6170                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
6171                             mHyphenationFrequency);
6172             switch (checkResult) {
6173                 case PrecomputedText.Params.UNUSABLE:
6174                     throw new IllegalArgumentException(
6175                         "PrecomputedText's Parameters don't match the parameters of this TextView."
6176                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
6177                         + "to override the settings of this TextView: "
6178                         + "PrecomputedText: " + precomputed.getParams()
6179                         + "TextView: " + getTextMetricsParams());
6180                 case PrecomputedText.Params.NEED_RECOMPUTE:
6181                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
6182                     break;
6183                 case PrecomputedText.Params.USABLE:
6184                     // pass through
6185             }
6186         } else if (type == BufferType.SPANNABLE || mMovement != null) {
6187             text = mSpannableFactory.newSpannable(text);
6188         } else if (!(text instanceof CharWrapper)) {
6189             text = TextUtils.stringOrSpannedString(text);
6190         }
6191 
6192         if (mAutoLinkMask != 0) {
6193             Spannable s2;
6194 
6195             if (type == BufferType.EDITABLE || text instanceof Spannable) {
6196                 s2 = (Spannable) text;
6197             } else {
6198                 s2 = mSpannableFactory.newSpannable(text);
6199             }
6200 
6201             if (Linkify.addLinks(s2, mAutoLinkMask)) {
6202                 text = s2;
6203                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
6204 
6205                 /*
6206                  * We must go ahead and set the text before changing the
6207                  * movement method, because setMovementMethod() may call
6208                  * setText() again to try to upgrade the buffer type.
6209                  */
6210                 setTextInternal(text);
6211 
6212                 // Do not change the movement method for text that support text selection as it
6213                 // would prevent an arbitrary cursor displacement.
6214                 if (mLinksClickable && !textCanBeSelected()) {
6215                     setMovementMethod(LinkMovementMethod.getInstance());
6216                 }
6217             }
6218         }
6219 
6220         mBufferType = type;
6221         setTextInternal(text);
6222 
6223         if (mTransformation == null) {
6224             mTransformed = text;
6225         } else {
6226             mTransformed = mTransformation.getTransformation(text, this);
6227         }
6228         if (mTransformed == null) {
6229             // Should not happen if the transformation method follows the non-null postcondition.
6230             mTransformed = "";
6231         }
6232 
6233         final int textLength = text.length();
6234 
6235         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
6236             Spannable sp = (Spannable) text;
6237 
6238             // Remove any ChangeWatchers that might have come from other TextViews.
6239             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
6240             final int count = watchers.length;
6241             for (int i = 0; i < count; i++) {
6242                 sp.removeSpan(watchers[i]);
6243             }
6244 
6245             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
6246 
6247             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
6248                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
6249 
6250             if (mEditor != null) mEditor.addSpanWatchers(sp);
6251 
6252             if (mTransformation != null) {
6253                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
6254             }
6255 
6256             if (mMovement != null) {
6257                 mMovement.initialize(this, (Spannable) text);
6258 
6259                 /*
6260                  * Initializing the movement method will have set the
6261                  * selection, so reset mSelectionMoved to keep that from
6262                  * interfering with the normal on-focus selection-setting.
6263                  */
6264                 if (mEditor != null) mEditor.mSelectionMoved = false;
6265             }
6266         }
6267 
6268         if (mLayout != null) {
6269             checkForRelayout();
6270         }
6271 
6272         sendOnTextChanged(text, 0, oldlen, textLength);
6273         onTextChanged(text, 0, oldlen, textLength);
6274 
6275         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
6276 
6277         if (needEditableForNotification) {
6278             sendAfterTextChanged((Editable) text);
6279         } else {
6280             notifyListeningManagersAfterTextChanged();
6281         }
6282 
6283         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
6284         if (mEditor != null) mEditor.prepareCursorControllers();
6285     }
6286 
6287     /**
6288      * Sets the TextView to display the specified slice of the specified
6289      * char array. You must promise that you will not change the contents
6290      * of the array except for right before another call to setText(),
6291      * since the TextView has no way to know that the text
6292      * has changed and that it needs to invalidate and re-layout.
6293      *
6294      * @param text char array to be displayed
6295      * @param start start index in the char array
6296      * @param len length of char count after {@code start}
6297      */
setText(char[] text, int start, int len)6298     public final void setText(char[] text, int start, int len) {
6299         int oldlen = 0;
6300 
6301         if (start < 0 || len < 0 || start + len > text.length) {
6302             throw new IndexOutOfBoundsException(start + ", " + len);
6303         }
6304 
6305         /*
6306          * We must do the before-notification here ourselves because if
6307          * the old text is a CharWrapper we destroy it before calling
6308          * into the normal path.
6309          */
6310         if (mText != null) {
6311             oldlen = mText.length();
6312             sendBeforeTextChanged(mText, 0, oldlen, len);
6313         } else {
6314             sendBeforeTextChanged("", 0, 0, len);
6315         }
6316 
6317         if (mCharWrapper == null) {
6318             mCharWrapper = new CharWrapper(text, start, len);
6319         } else {
6320             mCharWrapper.set(text, start, len);
6321         }
6322 
6323         setText(mCharWrapper, mBufferType, false, oldlen);
6324     }
6325 
6326     /**
6327      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
6328      * the cursor position. Same as
6329      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
6330      * position (if any) is retained in the new text.
6331      * <p/>
6332      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6333      * intermediate {@link Spannable Spannables}. Likewise it will use
6334      * {@link android.text.Editable.Factory} to create final or intermediate
6335      * {@link Editable Editables}.
6336      *
6337      * @param text text to be displayed
6338      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6339      *              stored as a static text, styleable/spannable text, or editable text
6340      *
6341      * @see #setText(CharSequence, android.widget.TextView.BufferType)
6342      */
setTextKeepState(CharSequence text, BufferType type)6343     public final void setTextKeepState(CharSequence text, BufferType type) {
6344         int start = getSelectionStart();
6345         int end = getSelectionEnd();
6346         int len = text.length();
6347 
6348         setText(text, type);
6349 
6350         if (start >= 0 || end >= 0) {
6351             if (mSpannable != null) {
6352                 Selection.setSelection(mSpannable,
6353                                        Math.max(0, Math.min(start, len)),
6354                                        Math.max(0, Math.min(end, len)));
6355             }
6356         }
6357     }
6358 
6359     /**
6360      * Sets the text to be displayed using a string resource identifier.
6361      *
6362      * @param resid the resource identifier of the string resource to be displayed
6363      *
6364      * @see #setText(CharSequence)
6365      *
6366      * @attr ref android.R.styleable#TextView_text
6367      */
6368     @android.view.RemotableViewMethod
setText(@tringRes int resid)6369     public final void setText(@StringRes int resid) {
6370         setText(getContext().getResources().getText(resid));
6371         mTextSetFromXmlOrResourceId = true;
6372         mTextId = resid;
6373     }
6374 
6375     /**
6376      * Sets the text to be displayed using a string resource identifier and the
6377      * {@link android.widget.TextView.BufferType}.
6378      * <p/>
6379      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6380      * intermediate {@link Spannable Spannables}. Likewise it will use
6381      * {@link android.text.Editable.Factory} to create final or intermediate
6382      * {@link Editable Editables}.
6383      *
6384      * @param resid the resource identifier of the string resource to be displayed
6385      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6386      *              stored as a static text, styleable/spannable text, or editable text
6387      *
6388      * @see #setText(int)
6389      * @see #setText(CharSequence)
6390      * @see android.widget.TextView.BufferType
6391      * @see #setSpannableFactory(Spannable.Factory)
6392      * @see #setEditableFactory(Editable.Factory)
6393      *
6394      * @attr ref android.R.styleable#TextView_text
6395      * @attr ref android.R.styleable#TextView_bufferType
6396      */
setText(@tringRes int resid, BufferType type)6397     public final void setText(@StringRes int resid, BufferType type) {
6398         setText(getContext().getResources().getText(resid), type);
6399         mTextSetFromXmlOrResourceId = true;
6400         mTextId = resid;
6401     }
6402 
6403     /**
6404      * Sets the text to be displayed when the text of the TextView is empty.
6405      * Null means to use the normal empty text. The hint does not currently
6406      * participate in determining the size of the view.
6407      *
6408      * @attr ref android.R.styleable#TextView_hint
6409      */
6410     @android.view.RemotableViewMethod
setHint(CharSequence hint)6411     public final void setHint(CharSequence hint) {
6412         setHintInternal(hint);
6413 
6414         if (mEditor != null && isInputMethodTarget()) {
6415             mEditor.reportExtractedText();
6416         }
6417     }
6418 
setHintInternal(CharSequence hint)6419     private void setHintInternal(CharSequence hint) {
6420         mHint = TextUtils.stringOrSpannedString(hint);
6421 
6422         if (mLayout != null) {
6423             checkForRelayout();
6424         }
6425 
6426         if (mText.length() == 0) {
6427             invalidate();
6428         }
6429 
6430         // Invalidate display list if hint is currently used
6431         if (mEditor != null && mText.length() == 0 && mHint != null) {
6432             mEditor.invalidateTextDisplayList();
6433         }
6434     }
6435 
6436     /**
6437      * Sets the text to be displayed when the text of the TextView is empty,
6438      * from a resource.
6439      *
6440      * @attr ref android.R.styleable#TextView_hint
6441      */
6442     @android.view.RemotableViewMethod
setHint(@tringRes int resid)6443     public final void setHint(@StringRes int resid) {
6444         setHint(getContext().getResources().getText(resid));
6445     }
6446 
6447     /**
6448      * Returns the hint that is displayed when the text of the TextView
6449      * is empty.
6450      *
6451      * @attr ref android.R.styleable#TextView_hint
6452      */
6453     @InspectableProperty
6454     @ViewDebug.CapturedViewProperty
getHint()6455     public CharSequence getHint() {
6456         return mHint;
6457     }
6458 
6459     /**
6460      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
6461      * line characters instead of letting it wrap onto multiple lines.
6462      *
6463      * @attr ref android.R.styleable#TextView_singleLine
6464      */
6465     @InspectableProperty
isSingleLine()6466     public boolean isSingleLine() {
6467         return mSingleLine;
6468     }
6469 
isMultilineInputType(int type)6470     private static boolean isMultilineInputType(int type) {
6471         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
6472                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
6473     }
6474 
6475     /**
6476      * Removes the suggestion spans.
6477      */
removeSuggestionSpans(CharSequence text)6478     CharSequence removeSuggestionSpans(CharSequence text) {
6479         if (text instanceof Spanned) {
6480             Spannable spannable;
6481             if (text instanceof Spannable) {
6482                 spannable = (Spannable) text;
6483             } else {
6484                 spannable = mSpannableFactory.newSpannable(text);
6485             }
6486 
6487             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
6488             if (spans.length == 0) {
6489                 return text;
6490             } else {
6491                 text = spannable;
6492             }
6493 
6494             for (int i = 0; i < spans.length; i++) {
6495                 spannable.removeSpan(spans[i]);
6496             }
6497         }
6498         return text;
6499     }
6500 
6501     /**
6502      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
6503      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
6504      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
6505      * then a soft keyboard will not be displayed for this text view.
6506      *
6507      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
6508      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
6509      * type.
6510      *
6511      * @see #getInputType()
6512      * @see #setRawInputType(int)
6513      * @see android.text.InputType
6514      * @attr ref android.R.styleable#TextView_inputType
6515      */
setInputType(int type)6516     public void setInputType(int type) {
6517         final boolean wasPassword = isPasswordInputType(getInputType());
6518         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
6519         setInputType(type, false);
6520         final boolean isPassword = isPasswordInputType(type);
6521         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
6522         boolean forceUpdate = false;
6523         if (isPassword) {
6524             setTransformationMethod(PasswordTransformationMethod.getInstance());
6525             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6526                     Typeface.NORMAL, -1 /* weight, not specifeid */);
6527         } else if (isVisiblePassword) {
6528             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6529                 forceUpdate = true;
6530             }
6531             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6532                     Typeface.NORMAL, -1 /* weight, not specified */);
6533         } else if (wasPassword || wasVisiblePassword) {
6534             // not in password mode, clean up typeface and transformation
6535             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
6536                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
6537                     -1 /* weight, not specified */);
6538             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6539                 forceUpdate = true;
6540             }
6541         }
6542 
6543         boolean singleLine = !isMultilineInputType(type);
6544 
6545         // We need to update the single line mode if it has changed or we
6546         // were previously in password mode.
6547         if (mSingleLine != singleLine || forceUpdate) {
6548             // Change single line mode, but only change the transformation if
6549             // we are not in password mode.
6550             applySingleLine(singleLine, !isPassword, true);
6551         }
6552 
6553         if (!isSuggestionsEnabled()) {
6554             setTextInternal(removeSuggestionSpans(mText));
6555         }
6556 
6557         InputMethodManager imm = getInputMethodManager();
6558         if (imm != null) imm.restartInput(this);
6559     }
6560 
6561     /**
6562      * It would be better to rely on the input type for everything. A password inputType should have
6563      * a password transformation. We should hence use isPasswordInputType instead of this method.
6564      *
6565      * We should:
6566      * - Call setInputType in setKeyListener instead of changing the input type directly (which
6567      * would install the correct transformation).
6568      * - Refuse the installation of a non-password transformation in setTransformation if the input
6569      * type is password.
6570      *
6571      * However, this is like this for legacy reasons and we cannot break existing apps. This method
6572      * is useful since it matches what the user can see (obfuscated text or not).
6573      *
6574      * @return true if the current transformation method is of the password type.
6575      */
hasPasswordTransformationMethod()6576     boolean hasPasswordTransformationMethod() {
6577         return mTransformation instanceof PasswordTransformationMethod;
6578     }
6579 
6580     /**
6581      * Returns true if the current inputType is any type of password.
6582      *
6583      * @hide
6584      */
isAnyPasswordInputType()6585     public boolean isAnyPasswordInputType() {
6586         final int inputType = getInputType();
6587         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
6588     }
6589 
isPasswordInputType(int inputType)6590     static boolean isPasswordInputType(int inputType) {
6591         final int variation =
6592                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6593         return variation
6594                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
6595                 || variation
6596                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
6597                 || variation
6598                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
6599     }
6600 
isVisiblePasswordInputType(int inputType)6601     private static boolean isVisiblePasswordInputType(int inputType) {
6602         final int variation =
6603                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6604         return variation
6605                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
6606     }
6607 
6608     /**
6609      * Directly change the content type integer of the text view, without
6610      * modifying any other state.
6611      * @see #setInputType(int)
6612      * @see android.text.InputType
6613      * @attr ref android.R.styleable#TextView_inputType
6614      */
setRawInputType(int type)6615     public void setRawInputType(int type) {
6616         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
6617         createEditorIfNeeded();
6618         mEditor.mInputType = type;
6619     }
6620 
6621     /**
6622      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
6623      *         a {@code Locale} object that can be used to customize key various listeners.
6624      * @see DateKeyListener#getInstance(Locale)
6625      * @see DateTimeKeyListener#getInstance(Locale)
6626      * @see DigitsKeyListener#getInstance(Locale)
6627      * @see TimeKeyListener#getInstance(Locale)
6628      */
6629     @Nullable
getCustomLocaleForKeyListenerOrNull()6630     private Locale getCustomLocaleForKeyListenerOrNull() {
6631         if (!mUseInternationalizedInput) {
6632             // If the application does not target O, stick to the previous behavior.
6633             return null;
6634         }
6635         final LocaleList locales = getImeHintLocales();
6636         if (locales == null) {
6637             // If the application does not explicitly specify IME hint locale, also stick to the
6638             // previous behavior.
6639             return null;
6640         }
6641         return locales.get(0);
6642     }
6643 
6644     @UnsupportedAppUsage
setInputType(int type, boolean direct)6645     private void setInputType(int type, boolean direct) {
6646         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
6647         KeyListener input;
6648         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
6649             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
6650             TextKeyListener.Capitalize cap;
6651             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
6652                 cap = TextKeyListener.Capitalize.CHARACTERS;
6653             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
6654                 cap = TextKeyListener.Capitalize.WORDS;
6655             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
6656                 cap = TextKeyListener.Capitalize.SENTENCES;
6657             } else {
6658                 cap = TextKeyListener.Capitalize.NONE;
6659             }
6660             input = TextKeyListener.getInstance(autotext, cap);
6661         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
6662             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6663             input = DigitsKeyListener.getInstance(
6664                     locale,
6665                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
6666                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
6667             if (locale != null) {
6668                 // Override type, if necessary for i18n.
6669                 int newType = input.getInputType();
6670                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
6671                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
6672                     // The class is different from the original class. So we need to override
6673                     // 'type'. But we want to keep the password flag if it's there.
6674                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
6675                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
6676                     }
6677                     type = newType;
6678                 }
6679             }
6680         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
6681             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6682             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
6683                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
6684                     input = DateKeyListener.getInstance(locale);
6685                     break;
6686                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
6687                     input = TimeKeyListener.getInstance(locale);
6688                     break;
6689                 default:
6690                     input = DateTimeKeyListener.getInstance(locale);
6691                     break;
6692             }
6693             if (mUseInternationalizedInput) {
6694                 type = input.getInputType(); // Override type, if necessary for i18n.
6695             }
6696         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
6697             input = DialerKeyListener.getInstance();
6698         } else {
6699             input = TextKeyListener.getInstance();
6700         }
6701         setRawInputType(type);
6702         mListenerChanged = false;
6703         if (direct) {
6704             createEditorIfNeeded();
6705             mEditor.mKeyListener = input;
6706         } else {
6707             setKeyListenerOnly(input);
6708         }
6709     }
6710 
6711     /**
6712      * Get the type of the editable content.
6713      *
6714      * @see #setInputType(int)
6715      * @see android.text.InputType
6716      */
6717     @InspectableProperty(flagMapping = {
6718             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
6719             @FlagEntry(
6720                     name = "text",
6721                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6722                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
6723             @FlagEntry(
6724                     name = "textUri",
6725                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6726                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
6727             @FlagEntry(
6728                     name = "textEmailAddress",
6729                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6730                     target = InputType.TYPE_CLASS_TEXT
6731                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
6732             @FlagEntry(
6733                     name = "textEmailSubject",
6734                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6735                     target = InputType.TYPE_CLASS_TEXT
6736                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
6737             @FlagEntry(
6738                     name = "textShortMessage",
6739                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6740                     target = InputType.TYPE_CLASS_TEXT
6741                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
6742             @FlagEntry(
6743                     name = "textLongMessage",
6744                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6745                     target = InputType.TYPE_CLASS_TEXT
6746                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
6747             @FlagEntry(
6748                     name = "textPersonName",
6749                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6750                     target = InputType.TYPE_CLASS_TEXT
6751                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
6752             @FlagEntry(
6753                     name = "textPostalAddress",
6754                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6755                     target = InputType.TYPE_CLASS_TEXT
6756                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
6757             @FlagEntry(
6758                     name = "textPassword",
6759                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6760                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
6761             @FlagEntry(
6762                     name = "textVisiblePassword",
6763                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6764                     target = InputType.TYPE_CLASS_TEXT
6765                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
6766             @FlagEntry(
6767                     name = "textWebEditText",
6768                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6769                     target = InputType.TYPE_CLASS_TEXT
6770                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
6771             @FlagEntry(
6772                     name = "textFilter",
6773                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6774                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
6775             @FlagEntry(
6776                     name = "textPhonetic",
6777                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6778                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
6779             @FlagEntry(
6780                     name = "textWebEmailAddress",
6781                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6782                     target = InputType.TYPE_CLASS_TEXT
6783                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
6784             @FlagEntry(
6785                     name = "textWebPassword",
6786                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6787                     target = InputType.TYPE_CLASS_TEXT
6788                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
6789             @FlagEntry(
6790                     name = "number",
6791                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6792                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
6793             @FlagEntry(
6794                     name = "numberPassword",
6795                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6796                     target = InputType.TYPE_CLASS_NUMBER
6797                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
6798             @FlagEntry(
6799                     name = "phone",
6800                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6801                     target = InputType.TYPE_CLASS_PHONE),
6802             @FlagEntry(
6803                     name = "datetime",
6804                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6805                     target = InputType.TYPE_CLASS_DATETIME
6806                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
6807             @FlagEntry(
6808                     name = "date",
6809                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6810                     target = InputType.TYPE_CLASS_DATETIME
6811                             | InputType.TYPE_DATETIME_VARIATION_DATE),
6812             @FlagEntry(
6813                     name = "time",
6814                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6815                     target = InputType.TYPE_CLASS_DATETIME
6816                             | InputType.TYPE_DATETIME_VARIATION_TIME),
6817             @FlagEntry(
6818                     name = "textCapCharacters",
6819                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6820                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
6821             @FlagEntry(
6822                     name = "textCapWords",
6823                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6824                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
6825             @FlagEntry(
6826                     name = "textCapSentences",
6827                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6828                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
6829             @FlagEntry(
6830                     name = "textAutoCorrect",
6831                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6832                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
6833             @FlagEntry(
6834                     name = "textAutoComplete",
6835                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6836                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
6837             @FlagEntry(
6838                     name = "textMultiLine",
6839                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6840                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
6841             @FlagEntry(
6842                     name = "textImeMultiLine",
6843                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6844                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
6845             @FlagEntry(
6846                     name = "textNoSuggestions",
6847                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6848                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
6849             @FlagEntry(
6850                     name = "numberSigned",
6851                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6852                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
6853             @FlagEntry(
6854                     name = "numberDecimal",
6855                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6856                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
6857     })
getInputType()6858     public int getInputType() {
6859         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
6860     }
6861 
6862     /**
6863      * Change the editor type integer associated with the text view, which
6864      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
6865      * when it has focus.
6866      * @see #getImeOptions
6867      * @see android.view.inputmethod.EditorInfo
6868      * @attr ref android.R.styleable#TextView_imeOptions
6869      */
setImeOptions(int imeOptions)6870     public void setImeOptions(int imeOptions) {
6871         createEditorIfNeeded();
6872         mEditor.createInputContentTypeIfNeeded();
6873         mEditor.mInputContentType.imeOptions = imeOptions;
6874     }
6875 
6876     /**
6877      * Get the type of the Input Method Editor (IME).
6878      * @return the type of the IME
6879      * @see #setImeOptions(int)
6880      * @see EditorInfo
6881      */
6882     @InspectableProperty(flagMapping = {
6883             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
6884             @FlagEntry(
6885                     name = "actionUnspecified",
6886                     mask = EditorInfo.IME_MASK_ACTION,
6887                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
6888             @FlagEntry(
6889                     name = "actionNone",
6890                     mask = EditorInfo.IME_MASK_ACTION,
6891                     target = EditorInfo.IME_ACTION_NONE),
6892             @FlagEntry(
6893                     name = "actionGo",
6894                     mask = EditorInfo.IME_MASK_ACTION,
6895                     target = EditorInfo.IME_ACTION_GO),
6896             @FlagEntry(
6897                     name = "actionSearch",
6898                     mask = EditorInfo.IME_MASK_ACTION,
6899                     target = EditorInfo.IME_ACTION_SEARCH),
6900             @FlagEntry(
6901                     name = "actionSend",
6902                     mask = EditorInfo.IME_MASK_ACTION,
6903                     target = EditorInfo.IME_ACTION_SEND),
6904             @FlagEntry(
6905                     name = "actionNext",
6906                     mask = EditorInfo.IME_MASK_ACTION,
6907                     target = EditorInfo.IME_ACTION_NEXT),
6908             @FlagEntry(
6909                     name = "actionDone",
6910                     mask = EditorInfo.IME_MASK_ACTION,
6911                     target = EditorInfo.IME_ACTION_DONE),
6912             @FlagEntry(
6913                     name = "actionPrevious",
6914                     mask = EditorInfo.IME_MASK_ACTION,
6915                     target = EditorInfo.IME_ACTION_PREVIOUS),
6916             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
6917             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
6918             @FlagEntry(
6919                     name = "flagNavigatePrevious",
6920                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
6921             @FlagEntry(
6922                     name = "flagNoAccessoryAction",
6923                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
6924             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
6925             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
6926             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
6927             @FlagEntry(
6928                     name = "flagNoPersonalizedLearning",
6929                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
6930     })
getImeOptions()6931     public int getImeOptions() {
6932         return mEditor != null && mEditor.mInputContentType != null
6933                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
6934     }
6935 
6936     /**
6937      * Change the custom IME action associated with the text view, which
6938      * will be reported to an IME with {@link EditorInfo#actionLabel}
6939      * and {@link EditorInfo#actionId} when it has focus.
6940      * @see #getImeActionLabel
6941      * @see #getImeActionId
6942      * @see android.view.inputmethod.EditorInfo
6943      * @attr ref android.R.styleable#TextView_imeActionLabel
6944      * @attr ref android.R.styleable#TextView_imeActionId
6945      */
setImeActionLabel(CharSequence label, int actionId)6946     public void setImeActionLabel(CharSequence label, int actionId) {
6947         createEditorIfNeeded();
6948         mEditor.createInputContentTypeIfNeeded();
6949         mEditor.mInputContentType.imeActionLabel = label;
6950         mEditor.mInputContentType.imeActionId = actionId;
6951     }
6952 
6953     /**
6954      * Get the IME action label previous set with {@link #setImeActionLabel}.
6955      *
6956      * @see #setImeActionLabel
6957      * @see android.view.inputmethod.EditorInfo
6958      */
6959     @InspectableProperty
getImeActionLabel()6960     public CharSequence getImeActionLabel() {
6961         return mEditor != null && mEditor.mInputContentType != null
6962                 ? mEditor.mInputContentType.imeActionLabel : null;
6963     }
6964 
6965     /**
6966      * Get the IME action ID previous set with {@link #setImeActionLabel}.
6967      *
6968      * @see #setImeActionLabel
6969      * @see android.view.inputmethod.EditorInfo
6970      */
6971     @InspectableProperty
getImeActionId()6972     public int getImeActionId() {
6973         return mEditor != null && mEditor.mInputContentType != null
6974                 ? mEditor.mInputContentType.imeActionId : 0;
6975     }
6976 
6977     /**
6978      * Set a special listener to be called when an action is performed
6979      * on the text view.  This will be called when the enter key is pressed,
6980      * or when an action supplied to the IME is selected by the user.  Setting
6981      * this means that the normal hard key event will not insert a newline
6982      * into the text view, even if it is multi-line; holding down the ALT
6983      * modifier will, however, allow the user to insert a newline character.
6984      */
setOnEditorActionListener(OnEditorActionListener l)6985     public void setOnEditorActionListener(OnEditorActionListener l) {
6986         createEditorIfNeeded();
6987         mEditor.createInputContentTypeIfNeeded();
6988         mEditor.mInputContentType.onEditorActionListener = l;
6989     }
6990 
6991     /**
6992      * Called when an attached input method calls
6993      * {@link InputConnection#performEditorAction(int)
6994      * InputConnection.performEditorAction()}
6995      * for this text view.  The default implementation will call your action
6996      * listener supplied to {@link #setOnEditorActionListener}, or perform
6997      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
6998      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
6999      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
7000      * EditorInfo.IME_ACTION_DONE}.
7001      *
7002      * <p>For backwards compatibility, if no IME options have been set and the
7003      * text view would not normally advance focus on enter, then
7004      * the NEXT and DONE actions received here will be turned into an enter
7005      * key down/up pair to go through the normal key handling.
7006      *
7007      * @param actionCode The code of the action being performed.
7008      *
7009      * @see #setOnEditorActionListener
7010      */
onEditorAction(int actionCode)7011     public void onEditorAction(int actionCode) {
7012         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
7013         if (ict != null) {
7014             if (ict.onEditorActionListener != null) {
7015                 if (ict.onEditorActionListener.onEditorAction(this,
7016                         actionCode, null)) {
7017                     return;
7018                 }
7019             }
7020 
7021             // This is the handling for some default action.
7022             // Note that for backwards compatibility we don't do this
7023             // default handling if explicit ime options have not been given,
7024             // instead turning this into the normal enter key codes that an
7025             // app may be expecting.
7026             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
7027                 View v = focusSearch(FOCUS_FORWARD);
7028                 if (v != null) {
7029                     if (!v.requestFocus(FOCUS_FORWARD)) {
7030                         throw new IllegalStateException("focus search returned a view "
7031                                 + "that wasn't able to take focus!");
7032                     }
7033                 }
7034                 return;
7035 
7036             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
7037                 View v = focusSearch(FOCUS_BACKWARD);
7038                 if (v != null) {
7039                     if (!v.requestFocus(FOCUS_BACKWARD)) {
7040                         throw new IllegalStateException("focus search returned a view "
7041                                 + "that wasn't able to take focus!");
7042                     }
7043                 }
7044                 return;
7045 
7046             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
7047                 InputMethodManager imm = getInputMethodManager();
7048                 if (imm != null && imm.isActive(this)) {
7049                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7050                 }
7051                 return;
7052             }
7053         }
7054 
7055         ViewRootImpl viewRootImpl = getViewRootImpl();
7056         if (viewRootImpl != null) {
7057             long eventTime = SystemClock.uptimeMillis();
7058             viewRootImpl.dispatchKeyFromIme(
7059                     new KeyEvent(eventTime, eventTime,
7060                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
7061                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7062                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7063                     | KeyEvent.FLAG_EDITOR_ACTION));
7064             viewRootImpl.dispatchKeyFromIme(
7065                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
7066                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
7067                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7068                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7069                     | KeyEvent.FLAG_EDITOR_ACTION));
7070         }
7071     }
7072 
7073     /**
7074      * Set the private content type of the text, which is the
7075      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
7076      * field that will be filled in when creating an input connection.
7077      *
7078      * @see #getPrivateImeOptions()
7079      * @see EditorInfo#privateImeOptions
7080      * @attr ref android.R.styleable#TextView_privateImeOptions
7081      */
setPrivateImeOptions(String type)7082     public void setPrivateImeOptions(String type) {
7083         createEditorIfNeeded();
7084         mEditor.createInputContentTypeIfNeeded();
7085         mEditor.mInputContentType.privateImeOptions = type;
7086     }
7087 
7088     /**
7089      * Get the private type of the content.
7090      *
7091      * @see #setPrivateImeOptions(String)
7092      * @see EditorInfo#privateImeOptions
7093      */
7094     @InspectableProperty
getPrivateImeOptions()7095     public String getPrivateImeOptions() {
7096         return mEditor != null && mEditor.mInputContentType != null
7097                 ? mEditor.mInputContentType.privateImeOptions : null;
7098     }
7099 
7100     /**
7101      * Set the extra input data of the text, which is the
7102      * {@link EditorInfo#extras TextBoxAttribute.extras}
7103      * Bundle that will be filled in when creating an input connection.  The
7104      * given integer is the resource identifier of an XML resource holding an
7105      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
7106      *
7107      * @see #getInputExtras(boolean)
7108      * @see EditorInfo#extras
7109      * @attr ref android.R.styleable#TextView_editorExtras
7110      */
setInputExtras(@mlRes int xmlResId)7111     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
7112         createEditorIfNeeded();
7113         XmlResourceParser parser = getResources().getXml(xmlResId);
7114         mEditor.createInputContentTypeIfNeeded();
7115         mEditor.mInputContentType.extras = new Bundle();
7116         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
7117     }
7118 
7119     /**
7120      * Retrieve the input extras currently associated with the text view, which
7121      * can be viewed as well as modified.
7122      *
7123      * @param create If true, the extras will be created if they don't already
7124      * exist.  Otherwise, null will be returned if none have been created.
7125      * @see #setInputExtras(int)
7126      * @see EditorInfo#extras
7127      * @attr ref android.R.styleable#TextView_editorExtras
7128      */
getInputExtras(boolean create)7129     public Bundle getInputExtras(boolean create) {
7130         if (mEditor == null && !create) return null;
7131         createEditorIfNeeded();
7132         if (mEditor.mInputContentType == null) {
7133             if (!create) return null;
7134             mEditor.createInputContentTypeIfNeeded();
7135         }
7136         if (mEditor.mInputContentType.extras == null) {
7137             if (!create) return null;
7138             mEditor.mInputContentType.extras = new Bundle();
7139         }
7140         return mEditor.mInputContentType.extras;
7141     }
7142 
7143     /**
7144      * Change "hint" locales associated with the text view, which will be reported to an IME with
7145      * {@link EditorInfo#hintLocales} when it has focus.
7146      *
7147      * Starting with Android O, this also causes internationalized listeners to be created (or
7148      * change locale) based on the first locale in the input locale list.
7149      *
7150      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
7151      * call {@link InputMethodManager#restartInput(View)}.</p>
7152      * @param hintLocales List of the languages that the user is supposed to switch to no matter
7153      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
7154      * @see #getImeHintLocales()
7155      * @see android.view.inputmethod.EditorInfo#hintLocales
7156      */
setImeHintLocales(@ullable LocaleList hintLocales)7157     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
7158         createEditorIfNeeded();
7159         mEditor.createInputContentTypeIfNeeded();
7160         mEditor.mInputContentType.imeHintLocales = hintLocales;
7161         if (mUseInternationalizedInput) {
7162             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
7163         }
7164     }
7165 
7166     /**
7167      * @return The current languages list "hint". {@code null} when no "hint" is available.
7168      * @see #setImeHintLocales(LocaleList)
7169      * @see android.view.inputmethod.EditorInfo#hintLocales
7170      */
7171     @Nullable
getImeHintLocales()7172     public LocaleList getImeHintLocales() {
7173         if (mEditor == null) {
7174             return null;
7175         }
7176         if (mEditor.mInputContentType == null) {
7177             return null;
7178         }
7179         return mEditor.mInputContentType.imeHintLocales;
7180     }
7181 
7182     /**
7183      * Returns the error message that was set to be displayed with
7184      * {@link #setError}, or <code>null</code> if no error was set
7185      * or if it the error was cleared by the widget after user input.
7186      */
getError()7187     public CharSequence getError() {
7188         return mEditor == null ? null : mEditor.mError;
7189     }
7190 
7191     /**
7192      * Sets the right-hand compound drawable of the TextView to the "error"
7193      * icon and sets an error message that will be displayed in a popup when
7194      * the TextView has focus.  The icon and error message will be reset to
7195      * null when any key events cause changes to the TextView's text.  If the
7196      * <code>error</code> is <code>null</code>, the error message and icon
7197      * will be cleared.
7198      */
7199     @android.view.RemotableViewMethod
setError(CharSequence error)7200     public void setError(CharSequence error) {
7201         if (error == null) {
7202             setError(null, null);
7203         } else {
7204             Drawable dr = getContext().getDrawable(
7205                     com.android.internal.R.drawable.indicator_input_error);
7206 
7207             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
7208             setError(error, dr);
7209         }
7210     }
7211 
7212     /**
7213      * Sets the right-hand compound drawable of the TextView to the specified
7214      * icon and sets an error message that will be displayed in a popup when
7215      * the TextView has focus.  The icon and error message will be reset to
7216      * null when any key events cause changes to the TextView's text.  The
7217      * drawable must already have had {@link Drawable#setBounds} set on it.
7218      * If the <code>error</code> is <code>null</code>, the error message will
7219      * be cleared (and you should provide a <code>null</code> icon as well).
7220      */
setError(CharSequence error, Drawable icon)7221     public void setError(CharSequence error, Drawable icon) {
7222         createEditorIfNeeded();
7223         mEditor.setError(error, icon);
7224         notifyViewAccessibilityStateChangedIfNeeded(
7225                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7226     }
7227 
7228     @Override
setFrame(int l, int t, int r, int b)7229     protected boolean setFrame(int l, int t, int r, int b) {
7230         boolean result = super.setFrame(l, t, r, b);
7231 
7232         if (mEditor != null) mEditor.setFrame();
7233 
7234         restartMarqueeIfNeeded();
7235 
7236         return result;
7237     }
7238 
restartMarqueeIfNeeded()7239     private void restartMarqueeIfNeeded() {
7240         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7241             mRestartMarquee = false;
7242             startMarquee();
7243         }
7244     }
7245 
7246     /**
7247      * Sets the list of input filters that will be used if the buffer is
7248      * Editable. Has no effect otherwise.
7249      *
7250      * @attr ref android.R.styleable#TextView_maxLength
7251      */
setFilters(InputFilter[] filters)7252     public void setFilters(InputFilter[] filters) {
7253         if (filters == null) {
7254             throw new IllegalArgumentException();
7255         }
7256 
7257         mFilters = filters;
7258 
7259         if (mText instanceof Editable) {
7260             setFilters((Editable) mText, filters);
7261         }
7262     }
7263 
7264     /**
7265      * Sets the list of input filters on the specified Editable,
7266      * and includes mInput in the list if it is an InputFilter.
7267      */
setFilters(Editable e, InputFilter[] filters)7268     private void setFilters(Editable e, InputFilter[] filters) {
7269         if (mEditor != null) {
7270             final boolean undoFilter = mEditor.mUndoInputFilter != null;
7271             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
7272             int num = 0;
7273             if (undoFilter) num++;
7274             if (keyFilter) num++;
7275             if (num > 0) {
7276                 InputFilter[] nf = new InputFilter[filters.length + num];
7277 
7278                 System.arraycopy(filters, 0, nf, 0, filters.length);
7279                 num = 0;
7280                 if (undoFilter) {
7281                     nf[filters.length] = mEditor.mUndoInputFilter;
7282                     num++;
7283                 }
7284                 if (keyFilter) {
7285                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
7286                 }
7287 
7288                 e.setFilters(nf);
7289                 return;
7290             }
7291         }
7292         e.setFilters(filters);
7293     }
7294 
7295     /**
7296      * Returns the current list of input filters.
7297      *
7298      * @attr ref android.R.styleable#TextView_maxLength
7299      */
getFilters()7300     public InputFilter[] getFilters() {
7301         return mFilters;
7302     }
7303 
7304     /////////////////////////////////////////////////////////////////////////
7305 
getBoxHeight(Layout l)7306     private int getBoxHeight(Layout l) {
7307         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
7308         int padding = (l == mHintLayout)
7309                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
7310                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
7311         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
7312     }
7313 
7314     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)7315     int getVerticalOffset(boolean forceNormal) {
7316         int voffset = 0;
7317         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7318 
7319         Layout l = mLayout;
7320         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7321             l = mHintLayout;
7322         }
7323 
7324         if (gravity != Gravity.TOP) {
7325             int boxht = getBoxHeight(l);
7326             int textht = l.getHeight();
7327 
7328             if (textht < boxht) {
7329                 if (gravity == Gravity.BOTTOM) {
7330                     voffset = boxht - textht;
7331                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7332                     voffset = (boxht - textht) >> 1;
7333                 }
7334             }
7335         }
7336         return voffset;
7337     }
7338 
getBottomVerticalOffset(boolean forceNormal)7339     private int getBottomVerticalOffset(boolean forceNormal) {
7340         int voffset = 0;
7341         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7342 
7343         Layout l = mLayout;
7344         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7345             l = mHintLayout;
7346         }
7347 
7348         if (gravity != Gravity.BOTTOM) {
7349             int boxht = getBoxHeight(l);
7350             int textht = l.getHeight();
7351 
7352             if (textht < boxht) {
7353                 if (gravity == Gravity.TOP) {
7354                     voffset = boxht - textht;
7355                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7356                     voffset = (boxht - textht) >> 1;
7357                 }
7358             }
7359         }
7360         return voffset;
7361     }
7362 
invalidateCursorPath()7363     void invalidateCursorPath() {
7364         if (mHighlightPathBogus) {
7365             invalidateCursor();
7366         } else {
7367             final int horizontalPadding = getCompoundPaddingLeft();
7368             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7369 
7370             if (mEditor.mDrawableForCursor == null) {
7371                 synchronized (TEMP_RECTF) {
7372                     /*
7373                      * The reason for this concern about the thickness of the
7374                      * cursor and doing the floor/ceil on the coordinates is that
7375                      * some EditTexts (notably textfields in the Browser) have
7376                      * anti-aliased text where not all the characters are
7377                      * necessarily at integer-multiple locations.  This should
7378                      * make sure the entire cursor gets invalidated instead of
7379                      * sometimes missing half a pixel.
7380                      */
7381                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
7382                     if (thick < 1.0f) {
7383                         thick = 1.0f;
7384                     }
7385 
7386                     thick /= 2.0f;
7387 
7388                     // mHighlightPath is guaranteed to be non null at that point.
7389                     mHighlightPath.computeBounds(TEMP_RECTF, false);
7390 
7391                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
7392                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
7393                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
7394                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
7395                 }
7396             } else {
7397                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7398                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
7399                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
7400             }
7401         }
7402     }
7403 
invalidateCursor()7404     void invalidateCursor() {
7405         int where = getSelectionEnd();
7406 
7407         invalidateCursor(where, where, where);
7408     }
7409 
invalidateCursor(int a, int b, int c)7410     private void invalidateCursor(int a, int b, int c) {
7411         if (a >= 0 || b >= 0 || c >= 0) {
7412             int start = Math.min(Math.min(a, b), c);
7413             int end = Math.max(Math.max(a, b), c);
7414             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
7415         }
7416     }
7417 
7418     /**
7419      * Invalidates the region of text enclosed between the start and end text offsets.
7420      */
invalidateRegion(int start, int end, boolean invalidateCursor)7421     void invalidateRegion(int start, int end, boolean invalidateCursor) {
7422         if (mLayout == null) {
7423             invalidate();
7424         } else {
7425             int lineStart = mLayout.getLineForOffset(start);
7426             int top = mLayout.getLineTop(lineStart);
7427 
7428             // This is ridiculous, but the descent from the line above
7429             // can hang down into the line we really want to redraw,
7430             // so we have to invalidate part of the line above to make
7431             // sure everything that needs to be redrawn really is.
7432             // (But not the whole line above, because that would cause
7433             // the same problem with the descenders on the line above it!)
7434             if (lineStart > 0) {
7435                 top -= mLayout.getLineDescent(lineStart - 1);
7436             }
7437 
7438             int lineEnd;
7439 
7440             if (start == end) {
7441                 lineEnd = lineStart;
7442             } else {
7443                 lineEnd = mLayout.getLineForOffset(end);
7444             }
7445 
7446             int bottom = mLayout.getLineBottom(lineEnd);
7447 
7448             // mEditor can be null in case selection is set programmatically.
7449             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
7450                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7451                 top = Math.min(top, bounds.top);
7452                 bottom = Math.max(bottom, bounds.bottom);
7453             }
7454 
7455             final int compoundPaddingLeft = getCompoundPaddingLeft();
7456             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7457 
7458             int left, right;
7459             if (lineStart == lineEnd && !invalidateCursor) {
7460                 left = (int) mLayout.getPrimaryHorizontal(start);
7461                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
7462                 left += compoundPaddingLeft;
7463                 right += compoundPaddingLeft;
7464             } else {
7465                 // Rectangle bounding box when the region spans several lines
7466                 left = compoundPaddingLeft;
7467                 right = getWidth() - getCompoundPaddingRight();
7468             }
7469 
7470             invalidate(mScrollX + left, verticalPadding + top,
7471                     mScrollX + right, verticalPadding + bottom);
7472         }
7473     }
7474 
registerForPreDraw()7475     private void registerForPreDraw() {
7476         if (!mPreDrawRegistered) {
7477             getViewTreeObserver().addOnPreDrawListener(this);
7478             mPreDrawRegistered = true;
7479         }
7480     }
7481 
unregisterForPreDraw()7482     private void unregisterForPreDraw() {
7483         getViewTreeObserver().removeOnPreDrawListener(this);
7484         mPreDrawRegistered = false;
7485         mPreDrawListenerDetached = false;
7486     }
7487 
7488     /**
7489      * {@inheritDoc}
7490      */
7491     @Override
onPreDraw()7492     public boolean onPreDraw() {
7493         if (mLayout == null) {
7494             assumeLayout();
7495         }
7496 
7497         if (mMovement != null) {
7498             /* This code also provides auto-scrolling when a cursor is moved using a
7499              * CursorController (insertion point or selection limits).
7500              * For selection, ensure start or end is visible depending on controller's state.
7501              */
7502             int curs = getSelectionEnd();
7503             // Do not create the controller if it is not already created.
7504             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
7505                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
7506                 curs = getSelectionStart();
7507             }
7508 
7509             /*
7510              * TODO: This should really only keep the end in view if
7511              * it already was before the text changed.  I'm not sure
7512              * of a good way to tell from here if it was.
7513              */
7514             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7515                 curs = mText.length();
7516             }
7517 
7518             if (curs >= 0) {
7519                 bringPointIntoView(curs);
7520             }
7521         } else {
7522             bringTextIntoView();
7523         }
7524 
7525         // This has to be checked here since:
7526         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
7527         //   a screen rotation) since layout is not yet initialized at that point.
7528         if (mEditor != null && mEditor.mCreatedWithASelection) {
7529             mEditor.refreshTextActionMode();
7530             mEditor.mCreatedWithASelection = false;
7531         }
7532 
7533         unregisterForPreDraw();
7534 
7535         return true;
7536     }
7537 
7538     @Override
onAttachedToWindow()7539     protected void onAttachedToWindow() {
7540         super.onAttachedToWindow();
7541 
7542         if (mEditor != null) mEditor.onAttachedToWindow();
7543 
7544         if (mPreDrawListenerDetached) {
7545             getViewTreeObserver().addOnPreDrawListener(this);
7546             mPreDrawListenerDetached = false;
7547         }
7548     }
7549 
7550     /** @hide */
7551     @Override
onDetachedFromWindowInternal()7552     protected void onDetachedFromWindowInternal() {
7553         if (mPreDrawRegistered) {
7554             getViewTreeObserver().removeOnPreDrawListener(this);
7555             mPreDrawListenerDetached = true;
7556         }
7557 
7558         resetResolvedDrawables();
7559 
7560         if (mEditor != null) mEditor.onDetachedFromWindow();
7561 
7562         super.onDetachedFromWindowInternal();
7563     }
7564 
7565     @Override
onScreenStateChanged(int screenState)7566     public void onScreenStateChanged(int screenState) {
7567         super.onScreenStateChanged(screenState);
7568         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
7569     }
7570 
7571     @Override
isPaddingOffsetRequired()7572     protected boolean isPaddingOffsetRequired() {
7573         return mShadowRadius != 0 || mDrawables != null;
7574     }
7575 
7576     @Override
getLeftPaddingOffset()7577     protected int getLeftPaddingOffset() {
7578         return getCompoundPaddingLeft() - mPaddingLeft
7579                 + (int) Math.min(0, mShadowDx - mShadowRadius);
7580     }
7581 
7582     @Override
getTopPaddingOffset()7583     protected int getTopPaddingOffset() {
7584         return (int) Math.min(0, mShadowDy - mShadowRadius);
7585     }
7586 
7587     @Override
getBottomPaddingOffset()7588     protected int getBottomPaddingOffset() {
7589         return (int) Math.max(0, mShadowDy + mShadowRadius);
7590     }
7591 
7592     @Override
getRightPaddingOffset()7593     protected int getRightPaddingOffset() {
7594         return -(getCompoundPaddingRight() - mPaddingRight)
7595                 + (int) Math.max(0, mShadowDx + mShadowRadius);
7596     }
7597 
7598     @Override
verifyDrawable(@onNull Drawable who)7599     protected boolean verifyDrawable(@NonNull Drawable who) {
7600         final boolean verified = super.verifyDrawable(who);
7601         if (!verified && mDrawables != null) {
7602             for (Drawable dr : mDrawables.mShowing) {
7603                 if (who == dr) {
7604                     return true;
7605                 }
7606             }
7607         }
7608         return verified;
7609     }
7610 
7611     @Override
jumpDrawablesToCurrentState()7612     public void jumpDrawablesToCurrentState() {
7613         super.jumpDrawablesToCurrentState();
7614         if (mDrawables != null) {
7615             for (Drawable dr : mDrawables.mShowing) {
7616                 if (dr != null) {
7617                     dr.jumpToCurrentState();
7618                 }
7619             }
7620         }
7621     }
7622 
7623     @Override
invalidateDrawable(@onNull Drawable drawable)7624     public void invalidateDrawable(@NonNull Drawable drawable) {
7625         boolean handled = false;
7626 
7627         if (verifyDrawable(drawable)) {
7628             final Rect dirty = drawable.getBounds();
7629             int scrollX = mScrollX;
7630             int scrollY = mScrollY;
7631 
7632             // IMPORTANT: The coordinates below are based on the coordinates computed
7633             // for each compound drawable in onDraw(). Make sure to update each section
7634             // accordingly.
7635             final TextView.Drawables drawables = mDrawables;
7636             if (drawables != null) {
7637                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
7638                     final int compoundPaddingTop = getCompoundPaddingTop();
7639                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7640                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7641 
7642                     scrollX += mPaddingLeft;
7643                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
7644                     handled = true;
7645                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
7646                     final int compoundPaddingTop = getCompoundPaddingTop();
7647                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7648                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7649 
7650                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
7651                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
7652                     handled = true;
7653                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
7654                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7655                     final int compoundPaddingRight = getCompoundPaddingRight();
7656                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7657 
7658                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
7659                     scrollY += mPaddingTop;
7660                     handled = true;
7661                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
7662                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7663                     final int compoundPaddingRight = getCompoundPaddingRight();
7664                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7665 
7666                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
7667                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
7668                     handled = true;
7669                 }
7670             }
7671 
7672             if (handled) {
7673                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
7674                         dirty.right + scrollX, dirty.bottom + scrollY);
7675             }
7676         }
7677 
7678         if (!handled) {
7679             super.invalidateDrawable(drawable);
7680         }
7681     }
7682 
7683     @Override
hasOverlappingRendering()7684     public boolean hasOverlappingRendering() {
7685         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
7686         return ((getBackground() != null && getBackground().getCurrent() != null)
7687                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
7688                 || mShadowColor != 0);
7689     }
7690 
7691     /**
7692      *
7693      * Returns the state of the {@code textIsSelectable} flag (See
7694      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
7695      * to allow users to select and copy text in a non-editable TextView, the content of an
7696      * {@link EditText} can always be selected, independently of the value of this flag.
7697      * <p>
7698      *
7699      * @return True if the text displayed in this TextView can be selected by the user.
7700      *
7701      * @attr ref android.R.styleable#TextView_textIsSelectable
7702      */
7703     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()7704     public boolean isTextSelectable() {
7705         return mEditor == null ? false : mEditor.mTextIsSelectable;
7706     }
7707 
7708     /**
7709      * Sets whether the content of this view is selectable by the user. The default is
7710      * {@code false}, meaning that the content is not selectable.
7711      * <p>
7712      * When you use a TextView to display a useful piece of information to the user (such as a
7713      * contact's address), make it selectable, so that the user can select and copy its
7714      * content. You can also use set the XML attribute
7715      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
7716      * <p>
7717      * When you call this method to set the value of {@code textIsSelectable}, it sets
7718      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
7719      * and {@code longClickable} to the same value. These flags correspond to the attributes
7720      * {@link android.R.styleable#View_focusable android:focusable},
7721      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
7722      * {@link android.R.styleable#View_clickable android:clickable}, and
7723      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
7724      * flags to a state you had set previously, call one or more of the following methods:
7725      * {@link #setFocusable(boolean) setFocusable()},
7726      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
7727      * {@link #setClickable(boolean) setClickable()} or
7728      * {@link #setLongClickable(boolean) setLongClickable()}.
7729      *
7730      * @param selectable Whether the content of this TextView should be selectable.
7731      */
setTextIsSelectable(boolean selectable)7732     public void setTextIsSelectable(boolean selectable) {
7733         if (!selectable && mEditor == null) return; // false is default value with no edit data
7734 
7735         createEditorIfNeeded();
7736         if (mEditor.mTextIsSelectable == selectable) return;
7737 
7738         mEditor.mTextIsSelectable = selectable;
7739         setFocusableInTouchMode(selectable);
7740         setFocusable(FOCUSABLE_AUTO);
7741         setClickable(selectable);
7742         setLongClickable(selectable);
7743 
7744         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
7745 
7746         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
7747         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
7748 
7749         // Called by setText above, but safer in case of future code changes
7750         mEditor.prepareCursorControllers();
7751     }
7752 
7753     @Override
onCreateDrawableState(int extraSpace)7754     protected int[] onCreateDrawableState(int extraSpace) {
7755         final int[] drawableState;
7756 
7757         if (mSingleLine) {
7758             drawableState = super.onCreateDrawableState(extraSpace);
7759         } else {
7760             drawableState = super.onCreateDrawableState(extraSpace + 1);
7761             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
7762         }
7763 
7764         if (isTextSelectable()) {
7765             // Disable pressed state, which was introduced when TextView was made clickable.
7766             // Prevents text color change.
7767             // setClickable(false) would have a similar effect, but it also disables focus changes
7768             // and long press actions, which are both needed by text selection.
7769             final int length = drawableState.length;
7770             for (int i = 0; i < length; i++) {
7771                 if (drawableState[i] == R.attr.state_pressed) {
7772                     final int[] nonPressedState = new int[length - 1];
7773                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
7774                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
7775                     return nonPressedState;
7776                 }
7777             }
7778         }
7779 
7780         return drawableState;
7781     }
7782 
7783     @UnsupportedAppUsage
getUpdatedHighlightPath()7784     private Path getUpdatedHighlightPath() {
7785         Path highlight = null;
7786         Paint highlightPaint = mHighlightPaint;
7787 
7788         final int selStart = getSelectionStart();
7789         final int selEnd = getSelectionEnd();
7790         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
7791             if (selStart == selEnd) {
7792                 if (mEditor != null && mEditor.shouldRenderCursor()) {
7793                     if (mHighlightPathBogus) {
7794                         if (mHighlightPath == null) mHighlightPath = new Path();
7795                         mHighlightPath.reset();
7796                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
7797                         mEditor.updateCursorPosition();
7798                         mHighlightPathBogus = false;
7799                     }
7800 
7801                     // XXX should pass to skin instead of drawing directly
7802                     highlightPaint.setColor(mCurTextColor);
7803                     highlightPaint.setStyle(Paint.Style.STROKE);
7804                     highlight = mHighlightPath;
7805                 }
7806             } else {
7807                 if (mHighlightPathBogus) {
7808                     if (mHighlightPath == null) mHighlightPath = new Path();
7809                     mHighlightPath.reset();
7810                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7811                     mHighlightPathBogus = false;
7812                 }
7813 
7814                 // XXX should pass to skin instead of drawing directly
7815                 highlightPaint.setColor(mHighlightColor);
7816                 highlightPaint.setStyle(Paint.Style.FILL);
7817 
7818                 highlight = mHighlightPath;
7819             }
7820         }
7821         return highlight;
7822     }
7823 
7824     /**
7825      * @hide
7826      */
getHorizontalOffsetForDrawables()7827     public int getHorizontalOffsetForDrawables() {
7828         return 0;
7829     }
7830 
7831     @Override
onDraw(Canvas canvas)7832     protected void onDraw(Canvas canvas) {
7833         restartMarqueeIfNeeded();
7834 
7835         // Draw the background for this view
7836         super.onDraw(canvas);
7837 
7838         final int compoundPaddingLeft = getCompoundPaddingLeft();
7839         final int compoundPaddingTop = getCompoundPaddingTop();
7840         final int compoundPaddingRight = getCompoundPaddingRight();
7841         final int compoundPaddingBottom = getCompoundPaddingBottom();
7842         final int scrollX = mScrollX;
7843         final int scrollY = mScrollY;
7844         final int right = mRight;
7845         final int left = mLeft;
7846         final int bottom = mBottom;
7847         final int top = mTop;
7848         final boolean isLayoutRtl = isLayoutRtl();
7849         final int offset = getHorizontalOffsetForDrawables();
7850         final int leftOffset = isLayoutRtl ? 0 : offset;
7851         final int rightOffset = isLayoutRtl ? offset : 0;
7852 
7853         final Drawables dr = mDrawables;
7854         if (dr != null) {
7855             /*
7856              * Compound, not extended, because the icon is not clipped
7857              * if the text height is smaller.
7858              */
7859 
7860             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
7861             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
7862 
7863             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7864             // Make sure to update invalidateDrawable() when changing this code.
7865             if (dr.mShowing[Drawables.LEFT] != null) {
7866                 canvas.save();
7867                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
7868                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
7869                 dr.mShowing[Drawables.LEFT].draw(canvas);
7870                 canvas.restore();
7871             }
7872 
7873             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7874             // Make sure to update invalidateDrawable() when changing this code.
7875             if (dr.mShowing[Drawables.RIGHT] != null) {
7876                 canvas.save();
7877                 canvas.translate(scrollX + right - left - mPaddingRight
7878                         - dr.mDrawableSizeRight - rightOffset,
7879                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
7880                 dr.mShowing[Drawables.RIGHT].draw(canvas);
7881                 canvas.restore();
7882             }
7883 
7884             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7885             // Make sure to update invalidateDrawable() when changing this code.
7886             if (dr.mShowing[Drawables.TOP] != null) {
7887                 canvas.save();
7888                 canvas.translate(scrollX + compoundPaddingLeft
7889                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
7890                 dr.mShowing[Drawables.TOP].draw(canvas);
7891                 canvas.restore();
7892             }
7893 
7894             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7895             // Make sure to update invalidateDrawable() when changing this code.
7896             if (dr.mShowing[Drawables.BOTTOM] != null) {
7897                 canvas.save();
7898                 canvas.translate(scrollX + compoundPaddingLeft
7899                         + (hspace - dr.mDrawableWidthBottom) / 2,
7900                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
7901                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
7902                 canvas.restore();
7903             }
7904         }
7905 
7906         int color = mCurTextColor;
7907 
7908         if (mLayout == null) {
7909             assumeLayout();
7910         }
7911 
7912         Layout layout = mLayout;
7913 
7914         if (mHint != null && mText.length() == 0) {
7915             if (mHintTextColor != null) {
7916                 color = mCurHintTextColor;
7917             }
7918 
7919             layout = mHintLayout;
7920         }
7921 
7922         mTextPaint.setColor(color);
7923         mTextPaint.drawableState = getDrawableState();
7924 
7925         canvas.save();
7926         /*  Would be faster if we didn't have to do this. Can we chop the
7927             (displayable) text so that we don't need to do this ever?
7928         */
7929 
7930         int extendedPaddingTop = getExtendedPaddingTop();
7931         int extendedPaddingBottom = getExtendedPaddingBottom();
7932 
7933         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7934         final int maxScrollY = mLayout.getHeight() - vspace;
7935 
7936         float clipLeft = compoundPaddingLeft + scrollX;
7937         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
7938         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
7939         float clipBottom = bottom - top + scrollY
7940                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
7941 
7942         if (mShadowRadius != 0) {
7943             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
7944             clipRight += Math.max(0, mShadowDx + mShadowRadius);
7945 
7946             clipTop += Math.min(0, mShadowDy - mShadowRadius);
7947             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
7948         }
7949 
7950         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
7951 
7952         int voffsetText = 0;
7953         int voffsetCursor = 0;
7954 
7955         // translate in by our padding
7956         /* shortcircuit calling getVerticaOffset() */
7957         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7958             voffsetText = getVerticalOffset(false);
7959             voffsetCursor = getVerticalOffset(true);
7960         }
7961         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
7962 
7963         final int layoutDirection = getLayoutDirection();
7964         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7965         if (isMarqueeFadeEnabled()) {
7966             if (!mSingleLine && getLineCount() == 1 && canMarquee()
7967                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
7968                 final int width = mRight - mLeft;
7969                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
7970                 final float dx = mLayout.getLineRight(0) - (width - padding);
7971                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7972             }
7973 
7974             if (mMarquee != null && mMarquee.isRunning()) {
7975                 final float dx = -mMarquee.getScroll();
7976                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7977             }
7978         }
7979 
7980         final int cursorOffsetVertical = voffsetCursor - voffsetText;
7981 
7982         Path highlight = getUpdatedHighlightPath();
7983         if (mEditor != null) {
7984             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
7985         } else {
7986             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7987         }
7988 
7989         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
7990             final float dx = mMarquee.getGhostOffset();
7991             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7992             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7993         }
7994 
7995         canvas.restore();
7996     }
7997 
7998     @Override
getFocusedRect(Rect r)7999     public void getFocusedRect(Rect r) {
8000         if (mLayout == null) {
8001             super.getFocusedRect(r);
8002             return;
8003         }
8004 
8005         int selEnd = getSelectionEnd();
8006         if (selEnd < 0) {
8007             super.getFocusedRect(r);
8008             return;
8009         }
8010 
8011         int selStart = getSelectionStart();
8012         if (selStart < 0 || selStart >= selEnd) {
8013             int line = mLayout.getLineForOffset(selEnd);
8014             r.top = mLayout.getLineTop(line);
8015             r.bottom = mLayout.getLineBottom(line);
8016             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
8017             r.right = r.left + 4;
8018         } else {
8019             int lineStart = mLayout.getLineForOffset(selStart);
8020             int lineEnd = mLayout.getLineForOffset(selEnd);
8021             r.top = mLayout.getLineTop(lineStart);
8022             r.bottom = mLayout.getLineBottom(lineEnd);
8023             if (lineStart == lineEnd) {
8024                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
8025                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
8026             } else {
8027                 // Selection extends across multiple lines -- make the focused
8028                 // rect cover the entire width.
8029                 if (mHighlightPathBogus) {
8030                     if (mHighlightPath == null) mHighlightPath = new Path();
8031                     mHighlightPath.reset();
8032                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8033                     mHighlightPathBogus = false;
8034                 }
8035                 synchronized (TEMP_RECTF) {
8036                     mHighlightPath.computeBounds(TEMP_RECTF, true);
8037                     r.left = (int) TEMP_RECTF.left - 1;
8038                     r.right = (int) TEMP_RECTF.right + 1;
8039                 }
8040             }
8041         }
8042 
8043         // Adjust for padding and gravity.
8044         int paddingLeft = getCompoundPaddingLeft();
8045         int paddingTop = getExtendedPaddingTop();
8046         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8047             paddingTop += getVerticalOffset(false);
8048         }
8049         r.offset(paddingLeft, paddingTop);
8050         int paddingBottom = getExtendedPaddingBottom();
8051         r.bottom += paddingBottom;
8052     }
8053 
8054     /**
8055      * Return the number of lines of text, or 0 if the internal Layout has not
8056      * been built.
8057      */
getLineCount()8058     public int getLineCount() {
8059         return mLayout != null ? mLayout.getLineCount() : 0;
8060     }
8061 
8062     /**
8063      * Return the baseline for the specified line (0...getLineCount() - 1)
8064      * If bounds is not null, return the top, left, right, bottom extents
8065      * of the specified line in it. If the internal Layout has not been built,
8066      * return 0 and set bounds to (0, 0, 0, 0)
8067      * @param line which line to examine (0..getLineCount() - 1)
8068      * @param bounds Optional. If not null, it returns the extent of the line
8069      * @return the Y-coordinate of the baseline
8070      */
getLineBounds(int line, Rect bounds)8071     public int getLineBounds(int line, Rect bounds) {
8072         if (mLayout == null) {
8073             if (bounds != null) {
8074                 bounds.set(0, 0, 0, 0);
8075             }
8076             return 0;
8077         } else {
8078             int baseline = mLayout.getLineBounds(line, bounds);
8079 
8080             int voffset = getExtendedPaddingTop();
8081             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8082                 voffset += getVerticalOffset(true);
8083             }
8084             if (bounds != null) {
8085                 bounds.offset(getCompoundPaddingLeft(), voffset);
8086             }
8087             return baseline + voffset;
8088         }
8089     }
8090 
8091     @Override
getBaseline()8092     public int getBaseline() {
8093         if (mLayout == null) {
8094             return super.getBaseline();
8095         }
8096 
8097         return getBaselineOffset() + mLayout.getLineBaseline(0);
8098     }
8099 
getBaselineOffset()8100     int getBaselineOffset() {
8101         int voffset = 0;
8102         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8103             voffset = getVerticalOffset(true);
8104         }
8105 
8106         if (isLayoutModeOptical(mParent)) {
8107             voffset -= getOpticalInsets().top;
8108         }
8109 
8110         return getExtendedPaddingTop() + voffset;
8111     }
8112 
8113     /**
8114      * @hide
8115      */
8116     @Override
getFadeTop(boolean offsetRequired)8117     protected int getFadeTop(boolean offsetRequired) {
8118         if (mLayout == null) return 0;
8119 
8120         int voffset = 0;
8121         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8122             voffset = getVerticalOffset(true);
8123         }
8124 
8125         if (offsetRequired) voffset += getTopPaddingOffset();
8126 
8127         return getExtendedPaddingTop() + voffset;
8128     }
8129 
8130     /**
8131      * @hide
8132      */
8133     @Override
getFadeHeight(boolean offsetRequired)8134     protected int getFadeHeight(boolean offsetRequired) {
8135         return mLayout != null ? mLayout.getHeight() : 0;
8136     }
8137 
8138     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)8139     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
8140         if (mSpannable != null && mLinksClickable) {
8141             final float x = event.getX(pointerIndex);
8142             final float y = event.getY(pointerIndex);
8143             final int offset = getOffsetForPosition(x, y);
8144             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
8145                     ClickableSpan.class);
8146             if (clickables.length > 0) {
8147                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
8148             }
8149         }
8150         if (isTextSelectable() || isTextEditable()) {
8151             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
8152         }
8153         return super.onResolvePointerIcon(event, pointerIndex);
8154     }
8155 
8156     @Override
onKeyPreIme(int keyCode, KeyEvent event)8157     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
8158         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
8159         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
8160         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
8161         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
8162             return true;
8163         }
8164         return super.onKeyPreIme(keyCode, event);
8165     }
8166 
8167     /**
8168      * @hide
8169      */
handleBackInTextActionModeIfNeeded(KeyEvent event)8170     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
8171         // Do nothing unless mEditor is in text action mode.
8172         if (mEditor == null || mEditor.getTextActionMode() == null) {
8173             return false;
8174         }
8175 
8176         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
8177             KeyEvent.DispatcherState state = getKeyDispatcherState();
8178             if (state != null) {
8179                 state.startTracking(event, this);
8180             }
8181             return true;
8182         } else if (event.getAction() == KeyEvent.ACTION_UP) {
8183             KeyEvent.DispatcherState state = getKeyDispatcherState();
8184             if (state != null) {
8185                 state.handleUpEvent(event);
8186             }
8187             if (event.isTracking() && !event.isCanceled()) {
8188                 stopTextActionMode();
8189                 return true;
8190             }
8191         }
8192         return false;
8193     }
8194 
8195     @Override
onKeyDown(int keyCode, KeyEvent event)8196     public boolean onKeyDown(int keyCode, KeyEvent event) {
8197         final int which = doKeyDown(keyCode, event, null);
8198         if (which == KEY_EVENT_NOT_HANDLED) {
8199             return super.onKeyDown(keyCode, event);
8200         }
8201 
8202         return true;
8203     }
8204 
8205     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8206     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
8207         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
8208         final int which = doKeyDown(keyCode, down, event);
8209         if (which == KEY_EVENT_NOT_HANDLED) {
8210             // Go through default dispatching.
8211             return super.onKeyMultiple(keyCode, repeatCount, event);
8212         }
8213         if (which == KEY_EVENT_HANDLED) {
8214             // Consumed the whole thing.
8215             return true;
8216         }
8217 
8218         repeatCount--;
8219 
8220         // We are going to dispatch the remaining events to either the input
8221         // or movement method.  To do this, we will just send a repeated stream
8222         // of down and up events until we have done the complete repeatCount.
8223         // It would be nice if those interfaces had an onKeyMultiple() method,
8224         // but adding that is a more complicated change.
8225         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
8226         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
8227             // mEditor and mEditor.mInput are not null from doKeyDown
8228             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8229             while (--repeatCount > 0) {
8230                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
8231                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8232             }
8233             hideErrorIfUnchanged();
8234 
8235         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
8236             // mMovement is not null from doKeyDown
8237             mMovement.onKeyUp(this, mSpannable, keyCode, up);
8238             while (--repeatCount > 0) {
8239                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
8240                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
8241             }
8242         }
8243 
8244         return true;
8245     }
8246 
8247     /**
8248      * Returns true if pressing ENTER in this field advances focus instead
8249      * of inserting the character.  This is true mostly in single-line fields,
8250      * but also in mail addresses and subjects which will display on multiple
8251      * lines but where it doesn't make sense to insert newlines.
8252      */
shouldAdvanceFocusOnEnter()8253     private boolean shouldAdvanceFocusOnEnter() {
8254         if (getKeyListener() == null) {
8255             return false;
8256         }
8257 
8258         if (mSingleLine) {
8259             return true;
8260         }
8261 
8262         if (mEditor != null
8263                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8264                         == EditorInfo.TYPE_CLASS_TEXT) {
8265             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8266             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
8267                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
8268                 return true;
8269             }
8270         }
8271 
8272         return false;
8273     }
8274 
8275     /**
8276      * Returns true if pressing TAB in this field advances focus instead
8277      * of inserting the character.  Insert tabs only in multi-line editors.
8278      */
shouldAdvanceFocusOnTab()8279     private boolean shouldAdvanceFocusOnTab() {
8280         if (getKeyListener() != null && !mSingleLine && mEditor != null
8281                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8282                         == EditorInfo.TYPE_CLASS_TEXT) {
8283             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8284             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
8285                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
8286                 return false;
8287             }
8288         }
8289         return true;
8290     }
8291 
isDirectionalNavigationKey(int keyCode)8292     private boolean isDirectionalNavigationKey(int keyCode) {
8293         switch(keyCode) {
8294             case KeyEvent.KEYCODE_DPAD_UP:
8295             case KeyEvent.KEYCODE_DPAD_DOWN:
8296             case KeyEvent.KEYCODE_DPAD_LEFT:
8297             case KeyEvent.KEYCODE_DPAD_RIGHT:
8298                 return true;
8299         }
8300         return false;
8301     }
8302 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8303     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
8304         if (!isEnabled()) {
8305             return KEY_EVENT_NOT_HANDLED;
8306         }
8307 
8308         // If this is the initial keydown, we don't want to prevent a movement away from this view.
8309         // While this shouldn't be necessary because any time we're preventing default movement we
8310         // should be restricting the focus to remain within this view, thus we'll also receive
8311         // the key up event, occasionally key up events will get dropped and we don't want to
8312         // prevent the user from traversing out of this on the next key down.
8313         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8314             mPreventDefaultMovement = false;
8315         }
8316 
8317         switch (keyCode) {
8318             case KeyEvent.KEYCODE_ENTER:
8319                 if (event.hasNoModifiers()) {
8320                     // When mInputContentType is set, we know that we are
8321                     // running in a "modern" cupcake environment, so don't need
8322                     // to worry about the application trying to capture
8323                     // enter key events.
8324                     if (mEditor != null && mEditor.mInputContentType != null) {
8325                         // If there is an action listener, given them a
8326                         // chance to consume the event.
8327                         if (mEditor.mInputContentType.onEditorActionListener != null
8328                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8329                                         this, EditorInfo.IME_NULL, event)) {
8330                             mEditor.mInputContentType.enterDown = true;
8331                             // We are consuming the enter key for them.
8332                             return KEY_EVENT_HANDLED;
8333                         }
8334                     }
8335 
8336                     // If our editor should move focus when enter is pressed, or
8337                     // this is a generated event from an IME action button, then
8338                     // don't let it be inserted into the text.
8339                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8340                             || shouldAdvanceFocusOnEnter()) {
8341                         if (hasOnClickListeners()) {
8342                             return KEY_EVENT_NOT_HANDLED;
8343                         }
8344                         return KEY_EVENT_HANDLED;
8345                     }
8346                 }
8347                 break;
8348 
8349             case KeyEvent.KEYCODE_DPAD_CENTER:
8350                 if (event.hasNoModifiers()) {
8351                     if (shouldAdvanceFocusOnEnter()) {
8352                         return KEY_EVENT_NOT_HANDLED;
8353                     }
8354                 }
8355                 break;
8356 
8357             case KeyEvent.KEYCODE_TAB:
8358                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
8359                     if (shouldAdvanceFocusOnTab()) {
8360                         return KEY_EVENT_NOT_HANDLED;
8361                     }
8362                 }
8363                 break;
8364 
8365                 // Has to be done on key down (and not on key up) to correctly be intercepted.
8366             case KeyEvent.KEYCODE_BACK:
8367                 if (mEditor != null && mEditor.getTextActionMode() != null) {
8368                     stopTextActionMode();
8369                     return KEY_EVENT_HANDLED;
8370                 }
8371                 break;
8372 
8373             case KeyEvent.KEYCODE_CUT:
8374                 if (event.hasNoModifiers() && canCut()) {
8375                     if (onTextContextMenuItem(ID_CUT)) {
8376                         return KEY_EVENT_HANDLED;
8377                     }
8378                 }
8379                 break;
8380 
8381             case KeyEvent.KEYCODE_COPY:
8382                 if (event.hasNoModifiers() && canCopy()) {
8383                     if (onTextContextMenuItem(ID_COPY)) {
8384                         return KEY_EVENT_HANDLED;
8385                     }
8386                 }
8387                 break;
8388 
8389             case KeyEvent.KEYCODE_PASTE:
8390                 if (event.hasNoModifiers() && canPaste()) {
8391                     if (onTextContextMenuItem(ID_PASTE)) {
8392                         return KEY_EVENT_HANDLED;
8393                     }
8394                 }
8395                 break;
8396 
8397             case KeyEvent.KEYCODE_FORWARD_DEL:
8398                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
8399                     if (onTextContextMenuItem(ID_CUT)) {
8400                         return KEY_EVENT_HANDLED;
8401                     }
8402                 }
8403                 break;
8404 
8405             case KeyEvent.KEYCODE_INSERT:
8406                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
8407                     if (onTextContextMenuItem(ID_COPY)) {
8408                         return KEY_EVENT_HANDLED;
8409                     }
8410                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
8411                     if (onTextContextMenuItem(ID_PASTE)) {
8412                         return KEY_EVENT_HANDLED;
8413                     }
8414                 }
8415                 break;
8416         }
8417 
8418         if (mEditor != null && mEditor.mKeyListener != null) {
8419             boolean doDown = true;
8420             if (otherEvent != null) {
8421                 try {
8422                     beginBatchEdit();
8423                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
8424                             otherEvent);
8425                     hideErrorIfUnchanged();
8426                     doDown = false;
8427                     if (handled) {
8428                         return KEY_EVENT_HANDLED;
8429                     }
8430                 } catch (AbstractMethodError e) {
8431                     // onKeyOther was added after 1.0, so if it isn't
8432                     // implemented we need to try to dispatch as a regular down.
8433                 } finally {
8434                     endBatchEdit();
8435                 }
8436             }
8437 
8438             if (doDown) {
8439                 beginBatchEdit();
8440                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
8441                         keyCode, event);
8442                 endBatchEdit();
8443                 hideErrorIfUnchanged();
8444                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
8445             }
8446         }
8447 
8448         // bug 650865: sometimes we get a key event before a layout.
8449         // don't try to move around if we don't know the layout.
8450 
8451         if (mMovement != null && mLayout != null) {
8452             boolean doDown = true;
8453             if (otherEvent != null) {
8454                 try {
8455                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
8456                     doDown = false;
8457                     if (handled) {
8458                         return KEY_EVENT_HANDLED;
8459                     }
8460                 } catch (AbstractMethodError e) {
8461                     // onKeyOther was added after 1.0, so if it isn't
8462                     // implemented we need to try to dispatch as a regular down.
8463                 }
8464             }
8465             if (doDown) {
8466                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
8467                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8468                         mPreventDefaultMovement = true;
8469                     }
8470                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
8471                 }
8472             }
8473             // Consume arrows from keyboard devices to prevent focus leaving the editor.
8474             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
8475             // to move focus with arrows.
8476             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
8477                     && isDirectionalNavigationKey(keyCode)) {
8478                 return KEY_EVENT_HANDLED;
8479             }
8480         }
8481 
8482         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
8483                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
8484     }
8485 
8486     /**
8487      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
8488      * can be recorded.
8489      * @hide
8490      */
resetErrorChangedFlag()8491     public void resetErrorChangedFlag() {
8492         /*
8493          * Keep track of what the error was before doing the input
8494          * so that if an input filter changed the error, we leave
8495          * that error showing.  Otherwise, we take down whatever
8496          * error was showing when the user types something.
8497          */
8498         if (mEditor != null) mEditor.mErrorWasChanged = false;
8499     }
8500 
8501     /**
8502      * @hide
8503      */
hideErrorIfUnchanged()8504     public void hideErrorIfUnchanged() {
8505         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
8506             setError(null, null);
8507         }
8508     }
8509 
8510     @Override
onKeyUp(int keyCode, KeyEvent event)8511     public boolean onKeyUp(int keyCode, KeyEvent event) {
8512         if (!isEnabled()) {
8513             return super.onKeyUp(keyCode, event);
8514         }
8515 
8516         if (!KeyEvent.isModifierKey(keyCode)) {
8517             mPreventDefaultMovement = false;
8518         }
8519 
8520         switch (keyCode) {
8521             case KeyEvent.KEYCODE_DPAD_CENTER:
8522                 if (event.hasNoModifiers()) {
8523                     /*
8524                      * If there is a click listener, just call through to
8525                      * super, which will invoke it.
8526                      *
8527                      * If there isn't a click listener, try to show the soft
8528                      * input method.  (It will also
8529                      * call performClick(), but that won't do anything in
8530                      * this case.)
8531                      */
8532                     if (!hasOnClickListeners()) {
8533                         if (mMovement != null && mText instanceof Editable
8534                                 && mLayout != null && onCheckIsTextEditor()) {
8535                             InputMethodManager imm = getInputMethodManager();
8536                             viewClicked(imm);
8537                             if (imm != null && getShowSoftInputOnFocus()) {
8538                                 imm.showSoftInput(this, 0);
8539                             }
8540                         }
8541                     }
8542                 }
8543                 return super.onKeyUp(keyCode, event);
8544 
8545             case KeyEvent.KEYCODE_ENTER:
8546                 if (event.hasNoModifiers()) {
8547                     if (mEditor != null && mEditor.mInputContentType != null
8548                             && mEditor.mInputContentType.onEditorActionListener != null
8549                             && mEditor.mInputContentType.enterDown) {
8550                         mEditor.mInputContentType.enterDown = false;
8551                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8552                                 this, EditorInfo.IME_NULL, event)) {
8553                             return true;
8554                         }
8555                     }
8556 
8557                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8558                             || shouldAdvanceFocusOnEnter()) {
8559                         /*
8560                          * If there is a click listener, just call through to
8561                          * super, which will invoke it.
8562                          *
8563                          * If there isn't a click listener, try to advance focus,
8564                          * but still call through to super, which will reset the
8565                          * pressed state and longpress state.  (It will also
8566                          * call performClick(), but that won't do anything in
8567                          * this case.)
8568                          */
8569                         if (!hasOnClickListeners()) {
8570                             View v = focusSearch(FOCUS_DOWN);
8571 
8572                             if (v != null) {
8573                                 if (!v.requestFocus(FOCUS_DOWN)) {
8574                                     throw new IllegalStateException("focus search returned a view "
8575                                             + "that wasn't able to take focus!");
8576                                 }
8577 
8578                                 /*
8579                                  * Return true because we handled the key; super
8580                                  * will return false because there was no click
8581                                  * listener.
8582                                  */
8583                                 super.onKeyUp(keyCode, event);
8584                                 return true;
8585                             } else if ((event.getFlags()
8586                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
8587                                 // No target for next focus, but make sure the IME
8588                                 // if this came from it.
8589                                 InputMethodManager imm = getInputMethodManager();
8590                                 if (imm != null && imm.isActive(this)) {
8591                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
8592                                 }
8593                             }
8594                         }
8595                     }
8596                     return super.onKeyUp(keyCode, event);
8597                 }
8598                 break;
8599         }
8600 
8601         if (mEditor != null && mEditor.mKeyListener != null) {
8602             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
8603                 return true;
8604             }
8605         }
8606 
8607         if (mMovement != null && mLayout != null) {
8608             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
8609                 return true;
8610             }
8611         }
8612 
8613         return super.onKeyUp(keyCode, event);
8614     }
8615 
8616     @Override
onCheckIsTextEditor()8617     public boolean onCheckIsTextEditor() {
8618         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
8619     }
8620 
8621     @Override
onCreateInputConnection(EditorInfo outAttrs)8622     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
8623         if (onCheckIsTextEditor() && isEnabled()) {
8624             mEditor.createInputMethodStateIfNeeded();
8625             outAttrs.inputType = getInputType();
8626             if (mEditor.mInputContentType != null) {
8627                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
8628                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
8629                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
8630                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
8631                 outAttrs.extras = mEditor.mInputContentType.extras;
8632                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
8633             } else {
8634                 outAttrs.imeOptions = EditorInfo.IME_NULL;
8635                 outAttrs.hintLocales = null;
8636             }
8637             if (focusSearch(FOCUS_DOWN) != null) {
8638                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
8639             }
8640             if (focusSearch(FOCUS_UP) != null) {
8641                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
8642             }
8643             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
8644                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
8645                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
8646                     // An action has not been set, but the enter key will move to
8647                     // the next focus, so set the action to that.
8648                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
8649                 } else {
8650                     // An action has not been set, and there is no focus to move
8651                     // to, so let's just supply a "done" action.
8652                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
8653                 }
8654                 if (!shouldAdvanceFocusOnEnter()) {
8655                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8656                 }
8657             }
8658             if (isMultilineInputType(outAttrs.inputType)) {
8659                 // Multi-line text editors should always show an enter key.
8660                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8661             }
8662             outAttrs.hintText = mHint;
8663             outAttrs.targetInputMethodUser = mTextOperationUser;
8664             if (mText instanceof Editable) {
8665                 InputConnection ic = new EditableInputConnection(this);
8666                 outAttrs.initialSelStart = getSelectionStart();
8667                 outAttrs.initialSelEnd = getSelectionEnd();
8668                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
8669                 return ic;
8670             }
8671         }
8672         return null;
8673     }
8674 
8675     /**
8676      * If this TextView contains editable content, extract a portion of it
8677      * based on the information in <var>request</var> in to <var>outText</var>.
8678      * @return Returns true if the text was successfully extracted, else false.
8679      */
extractText(ExtractedTextRequest request, ExtractedText outText)8680     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
8681         createEditorIfNeeded();
8682         return mEditor.extractText(request, outText);
8683     }
8684 
8685     /**
8686      * This is used to remove all style-impacting spans from text before new
8687      * extracted text is being replaced into it, so that we don't have any
8688      * lingering spans applied during the replace.
8689      */
removeParcelableSpans(Spannable spannable, int start, int end)8690     static void removeParcelableSpans(Spannable spannable, int start, int end) {
8691         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
8692         int i = spans.length;
8693         while (i > 0) {
8694             i--;
8695             spannable.removeSpan(spans[i]);
8696         }
8697     }
8698 
8699     /**
8700      * Apply to this text view the given extracted text, as previously
8701      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
8702      */
setExtractedText(ExtractedText text)8703     public void setExtractedText(ExtractedText text) {
8704         Editable content = getEditableText();
8705         if (text.text != null) {
8706             if (content == null) {
8707                 setText(text.text, TextView.BufferType.EDITABLE);
8708             } else {
8709                 int start = 0;
8710                 int end = content.length();
8711 
8712                 if (text.partialStartOffset >= 0) {
8713                     final int N = content.length();
8714                     start = text.partialStartOffset;
8715                     if (start > N) start = N;
8716                     end = text.partialEndOffset;
8717                     if (end > N) end = N;
8718                 }
8719 
8720                 removeParcelableSpans(content, start, end);
8721                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
8722                     if (text.text instanceof Spanned) {
8723                         // OK to copy spans only.
8724                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
8725                                 Object.class, content, start);
8726                     }
8727                 } else {
8728                     content.replace(start, end, text.text);
8729                 }
8730             }
8731         }
8732 
8733         // Now set the selection position...  make sure it is in range, to
8734         // avoid crashes.  If this is a partial update, it is possible that
8735         // the underlying text may have changed, causing us problems here.
8736         // Also we just don't want to trust clients to do the right thing.
8737         Spannable sp = (Spannable) getText();
8738         final int N = sp.length();
8739         int start = text.selectionStart;
8740         if (start < 0) {
8741             start = 0;
8742         } else if (start > N) {
8743             start = N;
8744         }
8745         int end = text.selectionEnd;
8746         if (end < 0) {
8747             end = 0;
8748         } else if (end > N) {
8749             end = N;
8750         }
8751         Selection.setSelection(sp, start, end);
8752 
8753         // Finally, update the selection mode.
8754         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
8755             MetaKeyKeyListener.startSelecting(this, sp);
8756         } else {
8757             MetaKeyKeyListener.stopSelecting(this, sp);
8758         }
8759 
8760         setHintInternal(text.hint);
8761     }
8762 
8763     /**
8764      * @hide
8765      */
setExtracting(ExtractedTextRequest req)8766     public void setExtracting(ExtractedTextRequest req) {
8767         if (mEditor.mInputMethodState != null) {
8768             mEditor.mInputMethodState.mExtractedTextRequest = req;
8769         }
8770         // This would stop a possible selection mode, but no such mode is started in case
8771         // extracted mode will start. Some text is selected though, and will trigger an action mode
8772         // in the extracted view.
8773         mEditor.hideCursorAndSpanControllers();
8774         stopTextActionMode();
8775         if (mEditor.mSelectionModifierCursorController != null) {
8776             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
8777         }
8778     }
8779 
8780     /**
8781      * Called by the framework in response to a text completion from
8782      * the current input method, provided by it calling
8783      * {@link InputConnection#commitCompletion
8784      * InputConnection.commitCompletion()}.  The default implementation does
8785      * nothing; text views that are supporting auto-completion should override
8786      * this to do their desired behavior.
8787      *
8788      * @param text The auto complete text the user has selected.
8789      */
onCommitCompletion(CompletionInfo text)8790     public void onCommitCompletion(CompletionInfo text) {
8791         // intentionally empty
8792     }
8793 
8794     /**
8795      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
8796      * dictionary) from the current input method, provided by it calling
8797      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
8798      * The default implementation flashes the background of the corrected word to provide
8799      * feedback to the user.
8800      *
8801      * @param info The auto correct info about the text that was corrected.
8802      */
onCommitCorrection(CorrectionInfo info)8803     public void onCommitCorrection(CorrectionInfo info) {
8804         if (mEditor != null) mEditor.onCommitCorrection(info);
8805     }
8806 
beginBatchEdit()8807     public void beginBatchEdit() {
8808         if (mEditor != null) mEditor.beginBatchEdit();
8809     }
8810 
endBatchEdit()8811     public void endBatchEdit() {
8812         if (mEditor != null) mEditor.endBatchEdit();
8813     }
8814 
8815     /**
8816      * Called by the framework in response to a request to begin a batch
8817      * of edit operations through a call to link {@link #beginBatchEdit()}.
8818      */
onBeginBatchEdit()8819     public void onBeginBatchEdit() {
8820         // intentionally empty
8821     }
8822 
8823     /**
8824      * Called by the framework in response to a request to end a batch
8825      * of edit operations through a call to link {@link #endBatchEdit}.
8826      */
onEndBatchEdit()8827     public void onEndBatchEdit() {
8828         // intentionally empty
8829     }
8830 
8831     /**
8832      * Called by the framework in response to a private command from the
8833      * current method, provided by it calling
8834      * {@link InputConnection#performPrivateCommand
8835      * InputConnection.performPrivateCommand()}.
8836      *
8837      * @param action The action name of the command.
8838      * @param data Any additional data for the command.  This may be null.
8839      * @return Return true if you handled the command, else false.
8840      */
onPrivateIMECommand(String action, Bundle data)8841     public boolean onPrivateIMECommand(String action, Bundle data) {
8842         return false;
8843     }
8844 
8845     /** @hide */
8846     @VisibleForTesting
8847     @UnsupportedAppUsage
nullLayouts()8848     public void nullLayouts() {
8849         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
8850             mSavedLayout = (BoringLayout) mLayout;
8851         }
8852         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
8853             mSavedHintLayout = (BoringLayout) mHintLayout;
8854         }
8855 
8856         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
8857 
8858         mBoring = mHintBoring = null;
8859 
8860         // Since it depends on the value of mLayout
8861         if (mEditor != null) mEditor.prepareCursorControllers();
8862     }
8863 
8864     /**
8865      * Make a new Layout based on the already-measured size of the view,
8866      * on the assumption that it was measured correctly at some point.
8867      */
8868     @UnsupportedAppUsage
assumeLayout()8869     private void assumeLayout() {
8870         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8871 
8872         if (width < 1) {
8873             width = 0;
8874         }
8875 
8876         int physicalWidth = width;
8877 
8878         if (mHorizontallyScrolling) {
8879             width = VERY_WIDE;
8880         }
8881 
8882         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
8883                       physicalWidth, false);
8884     }
8885 
8886     @UnsupportedAppUsage
getLayoutAlignment()8887     private Layout.Alignment getLayoutAlignment() {
8888         Layout.Alignment alignment;
8889         switch (getTextAlignment()) {
8890             case TEXT_ALIGNMENT_GRAVITY:
8891                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
8892                     case Gravity.START:
8893                         alignment = Layout.Alignment.ALIGN_NORMAL;
8894                         break;
8895                     case Gravity.END:
8896                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
8897                         break;
8898                     case Gravity.LEFT:
8899                         alignment = Layout.Alignment.ALIGN_LEFT;
8900                         break;
8901                     case Gravity.RIGHT:
8902                         alignment = Layout.Alignment.ALIGN_RIGHT;
8903                         break;
8904                     case Gravity.CENTER_HORIZONTAL:
8905                         alignment = Layout.Alignment.ALIGN_CENTER;
8906                         break;
8907                     default:
8908                         alignment = Layout.Alignment.ALIGN_NORMAL;
8909                         break;
8910                 }
8911                 break;
8912             case TEXT_ALIGNMENT_TEXT_START:
8913                 alignment = Layout.Alignment.ALIGN_NORMAL;
8914                 break;
8915             case TEXT_ALIGNMENT_TEXT_END:
8916                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
8917                 break;
8918             case TEXT_ALIGNMENT_CENTER:
8919                 alignment = Layout.Alignment.ALIGN_CENTER;
8920                 break;
8921             case TEXT_ALIGNMENT_VIEW_START:
8922                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8923                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8924                 break;
8925             case TEXT_ALIGNMENT_VIEW_END:
8926                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8927                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8928                 break;
8929             case TEXT_ALIGNMENT_INHERIT:
8930                 // This should never happen as we have already resolved the text alignment
8931                 // but better safe than sorry so we just fall through
8932             default:
8933                 alignment = Layout.Alignment.ALIGN_NORMAL;
8934                 break;
8935         }
8936         return alignment;
8937     }
8938 
8939     /**
8940      * The width passed in is now the desired layout width,
8941      * not the full view width with padding.
8942      * {@hide}
8943      */
8944     @VisibleForTesting
8945     @UnsupportedAppUsage
makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8946     public void makeNewLayout(int wantWidth, int hintWidth,
8947                                  BoringLayout.Metrics boring,
8948                                  BoringLayout.Metrics hintBoring,
8949                                  int ellipsisWidth, boolean bringIntoView) {
8950         stopMarquee();
8951 
8952         // Update "old" cached values
8953         mOldMaximum = mMaximum;
8954         mOldMaxMode = mMaxMode;
8955 
8956         mHighlightPathBogus = true;
8957 
8958         if (wantWidth < 0) {
8959             wantWidth = 0;
8960         }
8961         if (hintWidth < 0) {
8962             hintWidth = 0;
8963         }
8964 
8965         Layout.Alignment alignment = getLayoutAlignment();
8966         final boolean testDirChange = mSingleLine && mLayout != null
8967                 && (alignment == Layout.Alignment.ALIGN_NORMAL
8968                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
8969         int oldDir = 0;
8970         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
8971         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
8972         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
8973                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
8974         TruncateAt effectiveEllipsize = mEllipsize;
8975         if (mEllipsize == TruncateAt.MARQUEE
8976                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8977             effectiveEllipsize = TruncateAt.END_SMALL;
8978         }
8979 
8980         if (mTextDir == null) {
8981             mTextDir = getTextDirectionHeuristic();
8982         }
8983 
8984         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
8985                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
8986         if (switchEllipsize) {
8987             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
8988                     ? TruncateAt.END : TruncateAt.MARQUEE;
8989             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
8990                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
8991         }
8992 
8993         shouldEllipsize = mEllipsize != null;
8994         mHintLayout = null;
8995 
8996         if (mHint != null) {
8997             if (shouldEllipsize) hintWidth = wantWidth;
8998 
8999             if (hintBoring == UNKNOWN_BORING) {
9000                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
9001                                                    mHintBoring);
9002                 if (hintBoring != null) {
9003                     mHintBoring = hintBoring;
9004                 }
9005             }
9006 
9007             if (hintBoring != null) {
9008                 if (hintBoring.width <= hintWidth
9009                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
9010                     if (mSavedHintLayout != null) {
9011                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9012                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9013                                 hintBoring, mIncludePad);
9014                     } else {
9015                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9016                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9017                                 hintBoring, mIncludePad);
9018                     }
9019 
9020                     mSavedHintLayout = (BoringLayout) mHintLayout;
9021                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
9022                     if (mSavedHintLayout != null) {
9023                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9024                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9025                                 hintBoring, mIncludePad, mEllipsize,
9026                                 ellipsisWidth);
9027                     } else {
9028                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9029                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9030                                 hintBoring, mIncludePad, mEllipsize,
9031                                 ellipsisWidth);
9032                     }
9033                 }
9034             }
9035             // TODO: code duplication with makeSingleLayout()
9036             if (mHintLayout == null) {
9037                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
9038                         mHint.length(), mTextPaint, hintWidth)
9039                         .setAlignment(alignment)
9040                         .setTextDirection(mTextDir)
9041                         .setLineSpacing(mSpacingAdd, mSpacingMult)
9042                         .setIncludePad(mIncludePad)
9043                         .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9044                         .setBreakStrategy(mBreakStrategy)
9045                         .setHyphenationFrequency(mHyphenationFrequency)
9046                         .setJustificationMode(mJustificationMode)
9047                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9048                 if (shouldEllipsize) {
9049                     builder.setEllipsize(mEllipsize)
9050                             .setEllipsizedWidth(ellipsisWidth);
9051                 }
9052                 mHintLayout = builder.build();
9053             }
9054         }
9055 
9056         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
9057             registerForPreDraw();
9058         }
9059 
9060         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9061             if (!compressText(ellipsisWidth)) {
9062                 final int height = mLayoutParams.height;
9063                 // If the size of the view does not depend on the size of the text, try to
9064                 // start the marquee immediately
9065                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
9066                     startMarquee();
9067                 } else {
9068                     // Defer the start of the marquee until we know our width (see setFrame())
9069                     mRestartMarquee = true;
9070                 }
9071             }
9072         }
9073 
9074         // CursorControllers need a non-null mLayout
9075         if (mEditor != null) mEditor.prepareCursorControllers();
9076     }
9077 
9078     /**
9079      * Returns true if DynamicLayout is required
9080      *
9081      * @hide
9082      */
9083     @VisibleForTesting
useDynamicLayout()9084     public boolean useDynamicLayout() {
9085         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
9086     }
9087 
9088     /**
9089      * @hide
9090      */
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9091     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
9092             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
9093             boolean useSaved) {
9094         Layout result = null;
9095         if (useDynamicLayout()) {
9096             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
9097                     wantWidth)
9098                     .setDisplayText(mTransformed)
9099                     .setAlignment(alignment)
9100                     .setTextDirection(mTextDir)
9101                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9102                     .setIncludePad(mIncludePad)
9103                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9104                     .setBreakStrategy(mBreakStrategy)
9105                     .setHyphenationFrequency(mHyphenationFrequency)
9106                     .setJustificationMode(mJustificationMode)
9107                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
9108                     .setEllipsizedWidth(ellipsisWidth);
9109             result = builder.build();
9110         } else {
9111             if (boring == UNKNOWN_BORING) {
9112                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9113                 if (boring != null) {
9114                     mBoring = boring;
9115                 }
9116             }
9117 
9118             if (boring != null) {
9119                 if (boring.width <= wantWidth
9120                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
9121                     if (useSaved && mSavedLayout != null) {
9122                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9123                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9124                                 boring, mIncludePad);
9125                     } else {
9126                         result = BoringLayout.make(mTransformed, mTextPaint,
9127                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9128                                 boring, mIncludePad);
9129                     }
9130 
9131                     if (useSaved) {
9132                         mSavedLayout = (BoringLayout) result;
9133                     }
9134                 } else if (shouldEllipsize && boring.width <= wantWidth) {
9135                     if (useSaved && mSavedLayout != null) {
9136                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9137                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9138                                 boring, mIncludePad, effectiveEllipsize,
9139                                 ellipsisWidth);
9140                     } else {
9141                         result = BoringLayout.make(mTransformed, mTextPaint,
9142                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9143                                 boring, mIncludePad, effectiveEllipsize,
9144                                 ellipsisWidth);
9145                     }
9146                 }
9147             }
9148         }
9149         if (result == null) {
9150             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
9151                     0, mTransformed.length(), mTextPaint, wantWidth)
9152                     .setAlignment(alignment)
9153                     .setTextDirection(mTextDir)
9154                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9155                     .setIncludePad(mIncludePad)
9156                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9157                     .setBreakStrategy(mBreakStrategy)
9158                     .setHyphenationFrequency(mHyphenationFrequency)
9159                     .setJustificationMode(mJustificationMode)
9160                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9161             if (shouldEllipsize) {
9162                 builder.setEllipsize(effectiveEllipsize)
9163                         .setEllipsizedWidth(ellipsisWidth);
9164             }
9165             result = builder.build();
9166         }
9167         return result;
9168     }
9169 
9170     @UnsupportedAppUsage
compressText(float width)9171     private boolean compressText(float width) {
9172         if (isHardwareAccelerated()) return false;
9173 
9174         // Only compress the text if it hasn't been compressed by the previous pass
9175         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
9176                 && mTextPaint.getTextScaleX() == 1.0f) {
9177             final float textWidth = mLayout.getLineWidth(0);
9178             final float overflow = (textWidth + 1.0f - width) / width;
9179             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
9180                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
9181                 post(new Runnable() {
9182                     public void run() {
9183                         requestLayout();
9184                     }
9185                 });
9186                 return true;
9187             }
9188         }
9189 
9190         return false;
9191     }
9192 
desired(Layout layout)9193     private static int desired(Layout layout) {
9194         int n = layout.getLineCount();
9195         CharSequence text = layout.getText();
9196         float max = 0;
9197 
9198         // if any line was wrapped, we can't use it.
9199         // but it's ok for the last line not to have a newline
9200 
9201         for (int i = 0; i < n - 1; i++) {
9202             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
9203                 return -1;
9204             }
9205         }
9206 
9207         for (int i = 0; i < n; i++) {
9208             max = Math.max(max, layout.getLineWidth(i));
9209         }
9210 
9211         return (int) Math.ceil(max);
9212     }
9213 
9214     /**
9215      * Set whether the TextView includes extra top and bottom padding to make
9216      * room for accents that go above the normal ascent and descent.
9217      * The default is true.
9218      *
9219      * @see #getIncludeFontPadding()
9220      *
9221      * @attr ref android.R.styleable#TextView_includeFontPadding
9222      */
setIncludeFontPadding(boolean includepad)9223     public void setIncludeFontPadding(boolean includepad) {
9224         if (mIncludePad != includepad) {
9225             mIncludePad = includepad;
9226 
9227             if (mLayout != null) {
9228                 nullLayouts();
9229                 requestLayout();
9230                 invalidate();
9231             }
9232         }
9233     }
9234 
9235     /**
9236      * Gets whether the TextView includes extra top and bottom padding to make
9237      * room for accents that go above the normal ascent and descent.
9238      *
9239      * @see #setIncludeFontPadding(boolean)
9240      *
9241      * @attr ref android.R.styleable#TextView_includeFontPadding
9242      */
9243     @InspectableProperty
getIncludeFontPadding()9244     public boolean getIncludeFontPadding() {
9245         return mIncludePad;
9246     }
9247 
9248     /** @hide */
9249     @VisibleForTesting
9250     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
9251 
9252     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)9253     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
9254         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
9255         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
9256         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
9257         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
9258 
9259         int width;
9260         int height;
9261 
9262         BoringLayout.Metrics boring = UNKNOWN_BORING;
9263         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
9264 
9265         if (mTextDir == null) {
9266             mTextDir = getTextDirectionHeuristic();
9267         }
9268 
9269         int des = -1;
9270         boolean fromexisting = false;
9271         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
9272                 ?  (float) widthSize : Float.MAX_VALUE;
9273 
9274         if (widthMode == MeasureSpec.EXACTLY) {
9275             // Parent has told us how big to be. So be it.
9276             width = widthSize;
9277         } else {
9278             if (mLayout != null && mEllipsize == null) {
9279                 des = desired(mLayout);
9280             }
9281 
9282             if (des < 0) {
9283                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9284                 if (boring != null) {
9285                     mBoring = boring;
9286                 }
9287             } else {
9288                 fromexisting = true;
9289             }
9290 
9291             if (boring == null || boring == UNKNOWN_BORING) {
9292                 if (des < 0) {
9293                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
9294                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
9295                 }
9296                 width = des;
9297             } else {
9298                 width = boring.width;
9299             }
9300 
9301             final Drawables dr = mDrawables;
9302             if (dr != null) {
9303                 width = Math.max(width, dr.mDrawableWidthTop);
9304                 width = Math.max(width, dr.mDrawableWidthBottom);
9305             }
9306 
9307             if (mHint != null) {
9308                 int hintDes = -1;
9309                 int hintWidth;
9310 
9311                 if (mHintLayout != null && mEllipsize == null) {
9312                     hintDes = desired(mHintLayout);
9313                 }
9314 
9315                 if (hintDes < 0) {
9316                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
9317                     if (hintBoring != null) {
9318                         mHintBoring = hintBoring;
9319                     }
9320                 }
9321 
9322                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
9323                     if (hintDes < 0) {
9324                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
9325                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
9326                     }
9327                     hintWidth = hintDes;
9328                 } else {
9329                     hintWidth = hintBoring.width;
9330                 }
9331 
9332                 if (hintWidth > width) {
9333                     width = hintWidth;
9334                 }
9335             }
9336 
9337             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
9338 
9339             if (mMaxWidthMode == EMS) {
9340                 width = Math.min(width, mMaxWidth * getLineHeight());
9341             } else {
9342                 width = Math.min(width, mMaxWidth);
9343             }
9344 
9345             if (mMinWidthMode == EMS) {
9346                 width = Math.max(width, mMinWidth * getLineHeight());
9347             } else {
9348                 width = Math.max(width, mMinWidth);
9349             }
9350 
9351             // Check against our minimum width
9352             width = Math.max(width, getSuggestedMinimumWidth());
9353 
9354             if (widthMode == MeasureSpec.AT_MOST) {
9355                 width = Math.min(widthSize, width);
9356             }
9357         }
9358 
9359         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
9360         int unpaddedWidth = want;
9361 
9362         if (mHorizontallyScrolling) want = VERY_WIDE;
9363 
9364         int hintWant = want;
9365         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
9366 
9367         if (mLayout == null) {
9368             makeNewLayout(want, hintWant, boring, hintBoring,
9369                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9370         } else {
9371             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
9372                     || (mLayout.getEllipsizedWidth()
9373                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
9374 
9375             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
9376                     && (want > mLayout.getWidth())
9377                     && (mLayout instanceof BoringLayout
9378                             || (fromexisting && des >= 0 && des <= want));
9379 
9380             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
9381 
9382             if (layoutChanged || maximumChanged) {
9383                 if (!maximumChanged && widthChanged) {
9384                     mLayout.increaseWidthTo(want);
9385                 } else {
9386                     makeNewLayout(want, hintWant, boring, hintBoring,
9387                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9388                 }
9389             } else {
9390                 // Nothing has changed
9391             }
9392         }
9393 
9394         if (heightMode == MeasureSpec.EXACTLY) {
9395             // Parent has told us how big to be. So be it.
9396             height = heightSize;
9397             mDesiredHeightAtMeasure = -1;
9398         } else {
9399             int desired = getDesiredHeight();
9400 
9401             height = desired;
9402             mDesiredHeightAtMeasure = desired;
9403 
9404             if (heightMode == MeasureSpec.AT_MOST) {
9405                 height = Math.min(desired, heightSize);
9406             }
9407         }
9408 
9409         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
9410         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
9411             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
9412         }
9413 
9414         /*
9415          * We didn't let makeNewLayout() register to bring the cursor into view,
9416          * so do it here if there is any possibility that it is needed.
9417          */
9418         if (mMovement != null
9419                 || mLayout.getWidth() > unpaddedWidth
9420                 || mLayout.getHeight() > unpaddedHeight) {
9421             registerForPreDraw();
9422         } else {
9423             scrollTo(0, 0);
9424         }
9425 
9426         setMeasuredDimension(width, height);
9427     }
9428 
9429     /**
9430      * Automatically computes and sets the text size.
9431      */
autoSizeText()9432     private void autoSizeText() {
9433         if (!isAutoSizeEnabled()) {
9434             return;
9435         }
9436 
9437         if (mNeedsAutoSizeText) {
9438             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
9439                 return;
9440             }
9441 
9442             final int availableWidth = mHorizontallyScrolling
9443                     ? VERY_WIDE
9444                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
9445             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
9446                     - getExtendedPaddingTop();
9447 
9448             if (availableWidth <= 0 || availableHeight <= 0) {
9449                 return;
9450             }
9451 
9452             synchronized (TEMP_RECTF) {
9453                 TEMP_RECTF.setEmpty();
9454                 TEMP_RECTF.right = availableWidth;
9455                 TEMP_RECTF.bottom = availableHeight;
9456                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
9457 
9458                 if (optimalTextSize != getTextSize()) {
9459                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
9460                             false /* shouldRequestLayout */);
9461 
9462                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
9463                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9464                             false /* bringIntoView */);
9465                 }
9466             }
9467         }
9468         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
9469         // after the next layout pass should set this to false.
9470         mNeedsAutoSizeText = true;
9471     }
9472 
9473     /**
9474      * Performs a binary search to find the largest text size that will still fit within the size
9475      * available to this view.
9476      */
findLargestTextSizeWhichFits(RectF availableSpace)9477     private int findLargestTextSizeWhichFits(RectF availableSpace) {
9478         final int sizesCount = mAutoSizeTextSizesInPx.length;
9479         if (sizesCount == 0) {
9480             throw new IllegalStateException("No available text sizes to choose from.");
9481         }
9482 
9483         int bestSizeIndex = 0;
9484         int lowIndex = bestSizeIndex + 1;
9485         int highIndex = sizesCount - 1;
9486         int sizeToTryIndex;
9487         while (lowIndex <= highIndex) {
9488             sizeToTryIndex = (lowIndex + highIndex) / 2;
9489             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
9490                 bestSizeIndex = lowIndex;
9491                 lowIndex = sizeToTryIndex + 1;
9492             } else {
9493                 highIndex = sizeToTryIndex - 1;
9494                 bestSizeIndex = highIndex;
9495             }
9496         }
9497 
9498         return mAutoSizeTextSizesInPx[bestSizeIndex];
9499     }
9500 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9501     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
9502         final CharSequence text = mTransformed != null
9503                 ? mTransformed
9504                 : getText();
9505         final int maxLines = getMaxLines();
9506         if (mTempTextPaint == null) {
9507             mTempTextPaint = new TextPaint();
9508         } else {
9509             mTempTextPaint.reset();
9510         }
9511         mTempTextPaint.set(getPaint());
9512         mTempTextPaint.setTextSize(suggestedSizeInPx);
9513 
9514         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
9515                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
9516 
9517         layoutBuilder.setAlignment(getLayoutAlignment())
9518                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
9519                 .setIncludePad(getIncludeFontPadding())
9520                 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9521                 .setBreakStrategy(getBreakStrategy())
9522                 .setHyphenationFrequency(getHyphenationFrequency())
9523                 .setJustificationMode(getJustificationMode())
9524                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9525                 .setTextDirection(getTextDirectionHeuristic());
9526 
9527         final StaticLayout layout = layoutBuilder.build();
9528 
9529         // Lines overflow.
9530         if (maxLines != -1 && layout.getLineCount() > maxLines) {
9531             return false;
9532         }
9533 
9534         // Height overflow.
9535         if (layout.getHeight() > availableSpace.bottom) {
9536             return false;
9537         }
9538 
9539         return true;
9540     }
9541 
getDesiredHeight()9542     private int getDesiredHeight() {
9543         return Math.max(
9544                 getDesiredHeight(mLayout, true),
9545                 getDesiredHeight(mHintLayout, mEllipsize != null));
9546     }
9547 
getDesiredHeight(Layout layout, boolean cap)9548     private int getDesiredHeight(Layout layout, boolean cap) {
9549         if (layout == null) {
9550             return 0;
9551         }
9552 
9553         /*
9554         * Don't cap the hint to a certain number of lines.
9555         * (Do cap it, though, if we have a maximum pixel height.)
9556         */
9557         int desired = layout.getHeight(cap);
9558 
9559         final Drawables dr = mDrawables;
9560         if (dr != null) {
9561             desired = Math.max(desired, dr.mDrawableHeightLeft);
9562             desired = Math.max(desired, dr.mDrawableHeightRight);
9563         }
9564 
9565         int linecount = layout.getLineCount();
9566         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
9567         desired += padding;
9568 
9569         if (mMaxMode != LINES) {
9570             desired = Math.min(desired, mMaximum);
9571         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
9572                 || layout instanceof BoringLayout)) {
9573             desired = layout.getLineTop(mMaximum);
9574 
9575             if (dr != null) {
9576                 desired = Math.max(desired, dr.mDrawableHeightLeft);
9577                 desired = Math.max(desired, dr.mDrawableHeightRight);
9578             }
9579 
9580             desired += padding;
9581             linecount = mMaximum;
9582         }
9583 
9584         if (mMinMode == LINES) {
9585             if (linecount < mMinimum) {
9586                 desired += getLineHeight() * (mMinimum - linecount);
9587             }
9588         } else {
9589             desired = Math.max(desired, mMinimum);
9590         }
9591 
9592         // Check against our minimum height
9593         desired = Math.max(desired, getSuggestedMinimumHeight());
9594 
9595         return desired;
9596     }
9597 
9598     /**
9599      * Check whether a change to the existing text layout requires a
9600      * new view layout.
9601      */
checkForResize()9602     private void checkForResize() {
9603         boolean sizeChanged = false;
9604 
9605         if (mLayout != null) {
9606             // Check if our width changed
9607             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
9608                 sizeChanged = true;
9609                 invalidate();
9610             }
9611 
9612             // Check if our height changed
9613             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
9614                 int desiredHeight = getDesiredHeight();
9615 
9616                 if (desiredHeight != this.getHeight()) {
9617                     sizeChanged = true;
9618                 }
9619             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
9620                 if (mDesiredHeightAtMeasure >= 0) {
9621                     int desiredHeight = getDesiredHeight();
9622 
9623                     if (desiredHeight != mDesiredHeightAtMeasure) {
9624                         sizeChanged = true;
9625                     }
9626                 }
9627             }
9628         }
9629 
9630         if (sizeChanged) {
9631             requestLayout();
9632             // caller will have already invalidated
9633         }
9634     }
9635 
9636     /**
9637      * Check whether entirely new text requires a new view layout
9638      * or merely a new text layout.
9639      */
9640     @UnsupportedAppUsage
checkForRelayout()9641     private void checkForRelayout() {
9642         // If we have a fixed width, we can just swap in a new text layout
9643         // if the text height stays the same or if the view height is fixed.
9644 
9645         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
9646                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
9647                 && (mHint == null || mHintLayout != null)
9648                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
9649             // Static width, so try making a new text layout.
9650 
9651             int oldht = mLayout.getHeight();
9652             int want = mLayout.getWidth();
9653             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
9654 
9655             /*
9656              * No need to bring the text into view, since the size is not
9657              * changing (unless we do the requestLayout(), in which case it
9658              * will happen at measure).
9659              */
9660             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
9661                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9662                           false);
9663 
9664             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
9665                 // In a fixed-height view, so use our new text layout.
9666                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
9667                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
9668                     autoSizeText();
9669                     invalidate();
9670                     return;
9671                 }
9672 
9673                 // Dynamic height, but height has stayed the same,
9674                 // so use our new text layout.
9675                 if (mLayout.getHeight() == oldht
9676                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
9677                     autoSizeText();
9678                     invalidate();
9679                     return;
9680                 }
9681             }
9682 
9683             // We lose: the height has changed and we have a dynamic height.
9684             // Request a new view layout using our new text layout.
9685             requestLayout();
9686             invalidate();
9687         } else {
9688             // Dynamic width, so we have no choice but to request a new
9689             // view layout with a new text layout.
9690             nullLayouts();
9691             requestLayout();
9692             invalidate();
9693         }
9694     }
9695 
9696     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)9697     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
9698         super.onLayout(changed, left, top, right, bottom);
9699         if (mDeferScroll >= 0) {
9700             int curs = mDeferScroll;
9701             mDeferScroll = -1;
9702             bringPointIntoView(Math.min(curs, mText.length()));
9703         }
9704         // Call auto-size after the width and height have been calculated.
9705         autoSizeText();
9706     }
9707 
isShowingHint()9708     private boolean isShowingHint() {
9709         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
9710     }
9711 
9712     /**
9713      * Returns true if anything changed.
9714      */
9715     @UnsupportedAppUsage
bringTextIntoView()9716     private boolean bringTextIntoView() {
9717         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9718         int line = 0;
9719         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9720             line = layout.getLineCount() - 1;
9721         }
9722 
9723         Layout.Alignment a = layout.getParagraphAlignment(line);
9724         int dir = layout.getParagraphDirection(line);
9725         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9726         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9727         int ht = layout.getHeight();
9728 
9729         int scrollx, scrolly;
9730 
9731         // Convert to left, center, or right alignment.
9732         if (a == Layout.Alignment.ALIGN_NORMAL) {
9733             a = dir == Layout.DIR_LEFT_TO_RIGHT
9734                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
9735         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
9736             a = dir == Layout.DIR_LEFT_TO_RIGHT
9737                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
9738         }
9739 
9740         if (a == Layout.Alignment.ALIGN_CENTER) {
9741             /*
9742              * Keep centered if possible, or, if it is too wide to fit,
9743              * keep leading edge in view.
9744              */
9745 
9746             int left = (int) Math.floor(layout.getLineLeft(line));
9747             int right = (int) Math.ceil(layout.getLineRight(line));
9748 
9749             if (right - left < hspace) {
9750                 scrollx = (right + left) / 2 - hspace / 2;
9751             } else {
9752                 if (dir < 0) {
9753                     scrollx = right - hspace;
9754                 } else {
9755                     scrollx = left;
9756                 }
9757             }
9758         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
9759             int right = (int) Math.ceil(layout.getLineRight(line));
9760             scrollx = right - hspace;
9761         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
9762             scrollx = (int) Math.floor(layout.getLineLeft(line));
9763         }
9764 
9765         if (ht < vspace) {
9766             scrolly = 0;
9767         } else {
9768             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9769                 scrolly = ht - vspace;
9770             } else {
9771                 scrolly = 0;
9772             }
9773         }
9774 
9775         if (scrollx != mScrollX || scrolly != mScrollY) {
9776             scrollTo(scrollx, scrolly);
9777             return true;
9778         } else {
9779             return false;
9780         }
9781     }
9782 
9783     /**
9784      * Move the point, specified by the offset, into the view if it is needed.
9785      * This has to be called after layout. Returns true if anything changed.
9786      */
bringPointIntoView(int offset)9787     public boolean bringPointIntoView(int offset) {
9788         if (isLayoutRequested()) {
9789             mDeferScroll = offset;
9790             return false;
9791         }
9792         boolean changed = false;
9793 
9794         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9795 
9796         if (layout == null) return changed;
9797 
9798         int line = layout.getLineForOffset(offset);
9799 
9800         int grav;
9801 
9802         switch (layout.getParagraphAlignment(line)) {
9803             case ALIGN_LEFT:
9804                 grav = 1;
9805                 break;
9806             case ALIGN_RIGHT:
9807                 grav = -1;
9808                 break;
9809             case ALIGN_NORMAL:
9810                 grav = layout.getParagraphDirection(line);
9811                 break;
9812             case ALIGN_OPPOSITE:
9813                 grav = -layout.getParagraphDirection(line);
9814                 break;
9815             case ALIGN_CENTER:
9816             default:
9817                 grav = 0;
9818                 break;
9819         }
9820 
9821         // We only want to clamp the cursor to fit within the layout width
9822         // in left-to-right modes, because in a right to left alignment,
9823         // we want to scroll to keep the line-right on the screen, as other
9824         // lines are likely to have text flush with the right margin, which
9825         // we want to keep visible.
9826         // A better long-term solution would probably be to measure both
9827         // the full line and a blank-trimmed version, and, for example, use
9828         // the latter measurement for centering and right alignment, but for
9829         // the time being we only implement the cursor clamping in left to
9830         // right where it is most likely to be annoying.
9831         final boolean clamped = grav > 0;
9832         // FIXME: Is it okay to truncate this, or should we round?
9833         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
9834         final int top = layout.getLineTop(line);
9835         final int bottom = layout.getLineTop(line + 1);
9836 
9837         int left = (int) Math.floor(layout.getLineLeft(line));
9838         int right = (int) Math.ceil(layout.getLineRight(line));
9839         int ht = layout.getHeight();
9840 
9841         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9842         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9843         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
9844             // If cursor has been clamped, make sure we don't scroll.
9845             right = Math.max(x, left + hspace);
9846         }
9847 
9848         int hslack = (bottom - top) / 2;
9849         int vslack = hslack;
9850 
9851         if (vslack > vspace / 4) {
9852             vslack = vspace / 4;
9853         }
9854         if (hslack > hspace / 4) {
9855             hslack = hspace / 4;
9856         }
9857 
9858         int hs = mScrollX;
9859         int vs = mScrollY;
9860 
9861         if (top - vs < vslack) {
9862             vs = top - vslack;
9863         }
9864         if (bottom - vs > vspace - vslack) {
9865             vs = bottom - (vspace - vslack);
9866         }
9867         if (ht - vs < vspace) {
9868             vs = ht - vspace;
9869         }
9870         if (0 - vs > 0) {
9871             vs = 0;
9872         }
9873 
9874         if (grav != 0) {
9875             if (x - hs < hslack) {
9876                 hs = x - hslack;
9877             }
9878             if (x - hs > hspace - hslack) {
9879                 hs = x - (hspace - hslack);
9880             }
9881         }
9882 
9883         if (grav < 0) {
9884             if (left - hs > 0) {
9885                 hs = left;
9886             }
9887             if (right - hs < hspace) {
9888                 hs = right - hspace;
9889             }
9890         } else if (grav > 0) {
9891             if (right - hs < hspace) {
9892                 hs = right - hspace;
9893             }
9894             if (left - hs > 0) {
9895                 hs = left;
9896             }
9897         } else /* grav == 0 */ {
9898             if (right - left <= hspace) {
9899                 /*
9900                  * If the entire text fits, center it exactly.
9901                  */
9902                 hs = left - (hspace - (right - left)) / 2;
9903             } else if (x > right - hslack) {
9904                 /*
9905                  * If we are near the right edge, keep the right edge
9906                  * at the edge of the view.
9907                  */
9908                 hs = right - hspace;
9909             } else if (x < left + hslack) {
9910                 /*
9911                  * If we are near the left edge, keep the left edge
9912                  * at the edge of the view.
9913                  */
9914                 hs = left;
9915             } else if (left > hs) {
9916                 /*
9917                  * Is there whitespace visible at the left?  Fix it if so.
9918                  */
9919                 hs = left;
9920             } else if (right < hs + hspace) {
9921                 /*
9922                  * Is there whitespace visible at the right?  Fix it if so.
9923                  */
9924                 hs = right - hspace;
9925             } else {
9926                 /*
9927                  * Otherwise, float as needed.
9928                  */
9929                 if (x - hs < hslack) {
9930                     hs = x - hslack;
9931                 }
9932                 if (x - hs > hspace - hslack) {
9933                     hs = x - (hspace - hslack);
9934                 }
9935             }
9936         }
9937 
9938         if (hs != mScrollX || vs != mScrollY) {
9939             if (mScroller == null) {
9940                 scrollTo(hs, vs);
9941             } else {
9942                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
9943                 int dx = hs - mScrollX;
9944                 int dy = vs - mScrollY;
9945 
9946                 if (duration > ANIMATED_SCROLL_GAP) {
9947                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
9948                     awakenScrollBars(mScroller.getDuration());
9949                     invalidate();
9950                 } else {
9951                     if (!mScroller.isFinished()) {
9952                         mScroller.abortAnimation();
9953                     }
9954 
9955                     scrollBy(dx, dy);
9956                 }
9957 
9958                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
9959             }
9960 
9961             changed = true;
9962         }
9963 
9964         if (isFocused()) {
9965             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
9966             // requestRectangleOnScreen() is in terms of content coordinates.
9967 
9968             // The offsets here are to ensure the rectangle we are using is
9969             // within our view bounds, in case the cursor is on the far left
9970             // or right.  If it isn't withing the bounds, then this request
9971             // will be ignored.
9972             if (mTempRect == null) mTempRect = new Rect();
9973             mTempRect.set(x - 2, top, x + 2, bottom);
9974             getInterestingRect(mTempRect, line);
9975             mTempRect.offset(mScrollX, mScrollY);
9976 
9977             if (requestRectangleOnScreen(mTempRect)) {
9978                 changed = true;
9979             }
9980         }
9981 
9982         return changed;
9983     }
9984 
9985     /**
9986      * Move the cursor, if needed, so that it is at an offset that is visible
9987      * to the user.  This will not move the cursor if it represents more than
9988      * one character (a selection range).  This will only work if the
9989      * TextView contains spannable text; otherwise it will do nothing.
9990      *
9991      * @return True if the cursor was actually moved, false otherwise.
9992      */
moveCursorToVisibleOffset()9993     public boolean moveCursorToVisibleOffset() {
9994         if (!(mText instanceof Spannable)) {
9995             return false;
9996         }
9997         int start = getSelectionStart();
9998         int end = getSelectionEnd();
9999         if (start != end) {
10000             return false;
10001         }
10002 
10003         // First: make sure the line is visible on screen:
10004 
10005         int line = mLayout.getLineForOffset(start);
10006 
10007         final int top = mLayout.getLineTop(line);
10008         final int bottom = mLayout.getLineTop(line + 1);
10009         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10010         int vslack = (bottom - top) / 2;
10011         if (vslack > vspace / 4) {
10012             vslack = vspace / 4;
10013         }
10014         final int vs = mScrollY;
10015 
10016         if (top < (vs + vslack)) {
10017             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
10018         } else if (bottom > (vspace + vs - vslack)) {
10019             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
10020         }
10021 
10022         // Next: make sure the character is visible on screen:
10023 
10024         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10025         final int hs = mScrollX;
10026         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
10027         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
10028 
10029         // line might contain bidirectional text
10030         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
10031         final int highChar = leftChar > rightChar ? leftChar : rightChar;
10032 
10033         int newStart = start;
10034         if (newStart < lowChar) {
10035             newStart = lowChar;
10036         } else if (newStart > highChar) {
10037             newStart = highChar;
10038         }
10039 
10040         if (newStart != start) {
10041             Selection.setSelection(mSpannable, newStart);
10042             return true;
10043         }
10044 
10045         return false;
10046     }
10047 
10048     @Override
computeScroll()10049     public void computeScroll() {
10050         if (mScroller != null) {
10051             if (mScroller.computeScrollOffset()) {
10052                 mScrollX = mScroller.getCurrX();
10053                 mScrollY = mScroller.getCurrY();
10054                 invalidateParentCaches();
10055                 postInvalidate();  // So we draw again
10056             }
10057         }
10058     }
10059 
getInterestingRect(Rect r, int line)10060     private void getInterestingRect(Rect r, int line) {
10061         convertFromViewportToContentCoordinates(r);
10062 
10063         // Rectangle can can be expanded on first and last line to take
10064         // padding into account.
10065         // TODO Take left/right padding into account too?
10066         if (line == 0) r.top -= getExtendedPaddingTop();
10067         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
10068     }
10069 
convertFromViewportToContentCoordinates(Rect r)10070     private void convertFromViewportToContentCoordinates(Rect r) {
10071         final int horizontalOffset = viewportToContentHorizontalOffset();
10072         r.left += horizontalOffset;
10073         r.right += horizontalOffset;
10074 
10075         final int verticalOffset = viewportToContentVerticalOffset();
10076         r.top += verticalOffset;
10077         r.bottom += verticalOffset;
10078     }
10079 
viewportToContentHorizontalOffset()10080     int viewportToContentHorizontalOffset() {
10081         return getCompoundPaddingLeft() - mScrollX;
10082     }
10083 
10084     @UnsupportedAppUsage
viewportToContentVerticalOffset()10085     int viewportToContentVerticalOffset() {
10086         int offset = getExtendedPaddingTop() - mScrollY;
10087         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
10088             offset += getVerticalOffset(false);
10089         }
10090         return offset;
10091     }
10092 
10093     @Override
debug(int depth)10094     public void debug(int depth) {
10095         super.debug(depth);
10096 
10097         String output = debugIndent(depth);
10098         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
10099                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
10100                 + "} ";
10101 
10102         if (mText != null) {
10103 
10104             output += "mText=\"" + mText + "\" ";
10105             if (mLayout != null) {
10106                 output += "mLayout width=" + mLayout.getWidth()
10107                         + " height=" + mLayout.getHeight();
10108             }
10109         } else {
10110             output += "mText=NULL";
10111         }
10112         Log.d(VIEW_LOG_TAG, output);
10113     }
10114 
10115     /**
10116      * Convenience for {@link Selection#getSelectionStart}.
10117      */
10118     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()10119     public int getSelectionStart() {
10120         return Selection.getSelectionStart(getText());
10121     }
10122 
10123     /**
10124      * Convenience for {@link Selection#getSelectionEnd}.
10125      */
10126     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()10127     public int getSelectionEnd() {
10128         return Selection.getSelectionEnd(getText());
10129     }
10130 
10131     /**
10132      * Return true iff there is a selection of nonzero length inside this text view.
10133      */
hasSelection()10134     public boolean hasSelection() {
10135         final int selectionStart = getSelectionStart();
10136         final int selectionEnd = getSelectionEnd();
10137 
10138         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
10139     }
10140 
getSelectedText()10141     String getSelectedText() {
10142         if (!hasSelection()) {
10143             return null;
10144         }
10145 
10146         final int start = getSelectionStart();
10147         final int end = getSelectionEnd();
10148         return String.valueOf(
10149                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
10150     }
10151 
10152     /**
10153      * Sets the properties of this field (lines, horizontally scrolling,
10154      * transformation method) to be for a single-line input.
10155      *
10156      * @attr ref android.R.styleable#TextView_singleLine
10157      */
setSingleLine()10158     public void setSingleLine() {
10159         setSingleLine(true);
10160     }
10161 
10162     /**
10163      * Sets the properties of this field to transform input to ALL CAPS
10164      * display. This may use a "small caps" formatting if available.
10165      * This setting will be ignored if this field is editable or selectable.
10166      *
10167      * This call replaces the current transformation method. Disabling this
10168      * will not necessarily restore the previous behavior from before this
10169      * was enabled.
10170      *
10171      * @see #setTransformationMethod(TransformationMethod)
10172      * @attr ref android.R.styleable#TextView_textAllCaps
10173      */
setAllCaps(boolean allCaps)10174     public void setAllCaps(boolean allCaps) {
10175         if (allCaps) {
10176             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
10177         } else {
10178             setTransformationMethod(null);
10179         }
10180     }
10181 
10182     /**
10183      *
10184      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
10185      * @return Whether the current transformation method is for ALL CAPS.
10186      *
10187      * @see #setAllCaps(boolean)
10188      * @see #setTransformationMethod(TransformationMethod)
10189      */
10190     @InspectableProperty(name = "textAllCaps")
isAllCaps()10191     public boolean isAllCaps() {
10192         final TransformationMethod method = getTransformationMethod();
10193         return method != null && method instanceof AllCapsTransformationMethod;
10194     }
10195 
10196     /**
10197      * If true, sets the properties of this field (number of lines, horizontally scrolling,
10198      * transformation method) to be for a single-line input; if false, restores these to the default
10199      * conditions.
10200      *
10201      * Note that the default conditions are not necessarily those that were in effect prior this
10202      * method, and you may want to reset these properties to your custom values.
10203      *
10204      * @attr ref android.R.styleable#TextView_singleLine
10205      */
10206     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)10207     public void setSingleLine(boolean singleLine) {
10208         // Could be used, but may break backward compatibility.
10209         // if (mSingleLine == singleLine) return;
10210         setInputTypeSingleLine(singleLine);
10211         applySingleLine(singleLine, true, true);
10212     }
10213 
10214     /**
10215      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
10216      * @param singleLine
10217      */
setInputTypeSingleLine(boolean singleLine)10218     private void setInputTypeSingleLine(boolean singleLine) {
10219         if (mEditor != null
10220                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
10221                         == EditorInfo.TYPE_CLASS_TEXT) {
10222             if (singleLine) {
10223                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10224             } else {
10225                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10226             }
10227         }
10228     }
10229 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)10230     private void applySingleLine(boolean singleLine, boolean applyTransformation,
10231             boolean changeMaxLines) {
10232         mSingleLine = singleLine;
10233         if (singleLine) {
10234             setLines(1);
10235             setHorizontallyScrolling(true);
10236             if (applyTransformation) {
10237                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
10238             }
10239         } else {
10240             if (changeMaxLines) {
10241                 setMaxLines(Integer.MAX_VALUE);
10242             }
10243             setHorizontallyScrolling(false);
10244             if (applyTransformation) {
10245                 setTransformationMethod(null);
10246             }
10247         }
10248     }
10249 
10250     /**
10251      * Causes words in the text that are longer than the view's width
10252      * to be ellipsized instead of broken in the middle.  You may also
10253      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
10254      * to constrain the text to a single line.  Use <code>null</code>
10255      * to turn off ellipsizing.
10256      *
10257      * If {@link #setMaxLines} has been used to set two or more lines,
10258      * only {@link android.text.TextUtils.TruncateAt#END} and
10259      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
10260      * (other ellipsizing types will not do anything).
10261      *
10262      * @attr ref android.R.styleable#TextView_ellipsize
10263      */
setEllipsize(TextUtils.TruncateAt where)10264     public void setEllipsize(TextUtils.TruncateAt where) {
10265         // TruncateAt is an enum. != comparison is ok between these singleton objects.
10266         if (mEllipsize != where) {
10267             mEllipsize = where;
10268 
10269             if (mLayout != null) {
10270                 nullLayouts();
10271                 requestLayout();
10272                 invalidate();
10273             }
10274         }
10275     }
10276 
10277     /**
10278      * Sets how many times to repeat the marquee animation. Only applied if the
10279      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
10280      *
10281      * @see #getMarqueeRepeatLimit()
10282      *
10283      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10284      */
setMarqueeRepeatLimit(int marqueeLimit)10285     public void setMarqueeRepeatLimit(int marqueeLimit) {
10286         mMarqueeRepeatLimit = marqueeLimit;
10287     }
10288 
10289     /**
10290      * Gets the number of times the marquee animation is repeated. Only meaningful if the
10291      * TextView has marquee enabled.
10292      *
10293      * @return the number of times the marquee animation is repeated. -1 if the animation
10294      * repeats indefinitely
10295      *
10296      * @see #setMarqueeRepeatLimit(int)
10297      *
10298      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10299      */
10300     @InspectableProperty
getMarqueeRepeatLimit()10301     public int getMarqueeRepeatLimit() {
10302         return mMarqueeRepeatLimit;
10303     }
10304 
10305     /**
10306      * Returns where, if anywhere, words that are longer than the view
10307      * is wide should be ellipsized.
10308      */
10309     @InspectableProperty
10310     @ViewDebug.ExportedProperty
getEllipsize()10311     public TextUtils.TruncateAt getEllipsize() {
10312         return mEllipsize;
10313     }
10314 
10315     /**
10316      * Set the TextView so that when it takes focus, all the text is
10317      * selected.
10318      *
10319      * @attr ref android.R.styleable#TextView_selectAllOnFocus
10320      */
10321     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)10322     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
10323         createEditorIfNeeded();
10324         mEditor.mSelectAllOnFocus = selectAllOnFocus;
10325 
10326         if (selectAllOnFocus && !(mText instanceof Spannable)) {
10327             setText(mText, BufferType.SPANNABLE);
10328         }
10329     }
10330 
10331     /**
10332      * Set whether the cursor is visible. The default is true. Note that this property only
10333      * makes sense for editable TextView.
10334      *
10335      * @see #isCursorVisible()
10336      *
10337      * @attr ref android.R.styleable#TextView_cursorVisible
10338      */
10339     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)10340     public void setCursorVisible(boolean visible) {
10341         if (visible && mEditor == null) return; // visible is the default value with no edit data
10342         createEditorIfNeeded();
10343         if (mEditor.mCursorVisible != visible) {
10344             mEditor.mCursorVisible = visible;
10345             invalidate();
10346 
10347             mEditor.makeBlink();
10348 
10349             // InsertionPointCursorController depends on mCursorVisible
10350             mEditor.prepareCursorControllers();
10351         }
10352     }
10353 
10354     /**
10355      * @return whether or not the cursor is visible (assuming this TextView is editable)
10356      *
10357      * @see #setCursorVisible(boolean)
10358      *
10359      * @attr ref android.R.styleable#TextView_cursorVisible
10360      */
10361     @InspectableProperty
isCursorVisible()10362     public boolean isCursorVisible() {
10363         // true is the default value
10364         return mEditor == null ? true : mEditor.mCursorVisible;
10365     }
10366 
canMarquee()10367     private boolean canMarquee() {
10368         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10369         return width > 0 && (mLayout.getLineWidth(0) > width
10370                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
10371                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
10372     }
10373 
10374     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()10375     private void startMarquee() {
10376         // Do not ellipsize EditText
10377         if (getKeyListener() != null) return;
10378 
10379         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
10380             return;
10381         }
10382 
10383         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
10384                 && getLineCount() == 1 && canMarquee()) {
10385 
10386             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10387                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
10388                 final Layout tmp = mLayout;
10389                 mLayout = mSavedMarqueeModeLayout;
10390                 mSavedMarqueeModeLayout = tmp;
10391                 setHorizontalFadingEdgeEnabled(true);
10392                 requestLayout();
10393                 invalidate();
10394             }
10395 
10396             if (mMarquee == null) mMarquee = new Marquee(this);
10397             mMarquee.start(mMarqueeRepeatLimit);
10398         }
10399     }
10400 
stopMarquee()10401     private void stopMarquee() {
10402         if (mMarquee != null && !mMarquee.isStopped()) {
10403             mMarquee.stop();
10404         }
10405 
10406         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
10407             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10408             final Layout tmp = mSavedMarqueeModeLayout;
10409             mSavedMarqueeModeLayout = mLayout;
10410             mLayout = tmp;
10411             setHorizontalFadingEdgeEnabled(false);
10412             requestLayout();
10413             invalidate();
10414         }
10415     }
10416 
10417     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)10418     private void startStopMarquee(boolean start) {
10419         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10420             if (start) {
10421                 startMarquee();
10422             } else {
10423                 stopMarquee();
10424             }
10425         }
10426     }
10427 
10428     /**
10429      * This method is called when the text is changed, in case any subclasses
10430      * would like to know.
10431      *
10432      * Within <code>text</code>, the <code>lengthAfter</code> characters
10433      * beginning at <code>start</code> have just replaced old text that had
10434      * length <code>lengthBefore</code>. It is an error to attempt to make
10435      * changes to <code>text</code> from this callback.
10436      *
10437      * @param text The text the TextView is displaying
10438      * @param start The offset of the start of the range of the text that was
10439      * modified
10440      * @param lengthBefore The length of the former text that has been replaced
10441      * @param lengthAfter The length of the replacement modified text
10442      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10443     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
10444         // intentionally empty, template pattern method can be overridden by subclasses
10445     }
10446 
10447     /**
10448      * This method is called when the selection has changed, in case any
10449      * subclasses would like to know.
10450      *
10451      * @param selStart The new selection start location.
10452      * @param selEnd The new selection end location.
10453      */
onSelectionChanged(int selStart, int selEnd)10454     protected void onSelectionChanged(int selStart, int selEnd) {
10455         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
10456     }
10457 
10458     /**
10459      * Adds a TextWatcher to the list of those whose methods are called
10460      * whenever this TextView's text changes.
10461      * <p>
10462      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
10463      * not called after {@link #setText} calls.  Now, doing {@link #setText}
10464      * if there are any text changed listeners forces the buffer type to
10465      * Editable if it would not otherwise be and does call this method.
10466      */
addTextChangedListener(TextWatcher watcher)10467     public void addTextChangedListener(TextWatcher watcher) {
10468         if (mListeners == null) {
10469             mListeners = new ArrayList<TextWatcher>();
10470         }
10471 
10472         mListeners.add(watcher);
10473     }
10474 
10475     /**
10476      * Removes the specified TextWatcher from the list of those whose
10477      * methods are called
10478      * whenever this TextView's text changes.
10479      */
removeTextChangedListener(TextWatcher watcher)10480     public void removeTextChangedListener(TextWatcher watcher) {
10481         if (mListeners != null) {
10482             int i = mListeners.indexOf(watcher);
10483 
10484             if (i >= 0) {
10485                 mListeners.remove(i);
10486             }
10487         }
10488     }
10489 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)10490     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
10491         if (mListeners != null) {
10492             final ArrayList<TextWatcher> list = mListeners;
10493             final int count = list.size();
10494             for (int i = 0; i < count; i++) {
10495                 list.get(i).beforeTextChanged(text, start, before, after);
10496             }
10497         }
10498 
10499         // The spans that are inside or intersect the modified region no longer make sense
10500         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
10501         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
10502     }
10503 
10504     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10505     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
10506         if (!(mText instanceof Editable)) return;
10507         Editable text = (Editable) mText;
10508 
10509         T[] spans = text.getSpans(start, end, type);
10510         final int length = spans.length;
10511         for (int i = 0; i < length; i++) {
10512             final int spanStart = text.getSpanStart(spans[i]);
10513             final int spanEnd = text.getSpanEnd(spans[i]);
10514             if (spanEnd == start || spanStart == end) break;
10515             text.removeSpan(spans[i]);
10516         }
10517     }
10518 
removeAdjacentSuggestionSpans(final int pos)10519     void removeAdjacentSuggestionSpans(final int pos) {
10520         if (!(mText instanceof Editable)) return;
10521         final Editable text = (Editable) mText;
10522 
10523         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
10524         final int length = spans.length;
10525         for (int i = 0; i < length; i++) {
10526             final int spanStart = text.getSpanStart(spans[i]);
10527             final int spanEnd = text.getSpanEnd(spans[i]);
10528             if (spanEnd == pos || spanStart == pos) {
10529                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
10530                     text.removeSpan(spans[i]);
10531                 }
10532             }
10533         }
10534     }
10535 
10536     /**
10537      * Not private so it can be called from an inner class without going
10538      * through a thunk.
10539      */
sendOnTextChanged(CharSequence text, int start, int before, int after)10540     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
10541         if (mListeners != null) {
10542             final ArrayList<TextWatcher> list = mListeners;
10543             final int count = list.size();
10544             for (int i = 0; i < count; i++) {
10545                 list.get(i).onTextChanged(text, start, before, after);
10546             }
10547         }
10548 
10549         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
10550     }
10551 
10552     /**
10553      * Not private so it can be called from an inner class without going
10554      * through a thunk.
10555      */
sendAfterTextChanged(Editable text)10556     void sendAfterTextChanged(Editable text) {
10557         if (mListeners != null) {
10558             final ArrayList<TextWatcher> list = mListeners;
10559             final int count = list.size();
10560             for (int i = 0; i < count; i++) {
10561                 list.get(i).afterTextChanged(text);
10562             }
10563         }
10564 
10565         notifyListeningManagersAfterTextChanged();
10566 
10567         hideErrorIfUnchanged();
10568     }
10569 
10570     /**
10571      * Notify managers (such as {@link AutofillManager}) that are interested in text changes.
10572      */
notifyListeningManagersAfterTextChanged()10573     private void notifyListeningManagersAfterTextChanged() {
10574 
10575         // Autofill
10576         if (isAutofillable()) {
10577             // It is important to not check whether the view is important for autofill
10578             // since the user can trigger autofill manually on not important views.
10579             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10580             if (afm != null) {
10581                 if (android.view.autofill.Helper.sVerbose) {
10582                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
10583                 }
10584                 afm.notifyValueChanged(TextView.this);
10585             }
10586         }
10587     }
10588 
isAutofillable()10589     private boolean isAutofillable() {
10590         // It is important to not check whether the view is important for autofill
10591         // since the user can trigger autofill manually on not important views.
10592         return getAutofillType() != AUTOFILL_TYPE_NONE;
10593     }
10594 
updateAfterEdit()10595     void updateAfterEdit() {
10596         invalidate();
10597         int curs = getSelectionStart();
10598 
10599         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
10600             registerForPreDraw();
10601         }
10602 
10603         checkForResize();
10604 
10605         if (curs >= 0) {
10606             mHighlightPathBogus = true;
10607             if (mEditor != null) mEditor.makeBlink();
10608             bringPointIntoView(curs);
10609         }
10610     }
10611 
10612     /**
10613      * Not private so it can be called from an inner class without going
10614      * through a thunk.
10615      */
handleTextChanged(CharSequence buffer, int start, int before, int after)10616     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
10617         sLastCutCopyOrTextChangedTime = 0;
10618 
10619         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10620         if (ims == null || ims.mBatchEditNesting == 0) {
10621             updateAfterEdit();
10622         }
10623         if (ims != null) {
10624             ims.mContentChanged = true;
10625             if (ims.mChangedStart < 0) {
10626                 ims.mChangedStart = start;
10627                 ims.mChangedEnd = start + before;
10628             } else {
10629                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
10630                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
10631             }
10632             ims.mChangedDelta += after - before;
10633         }
10634         resetErrorChangedFlag();
10635         sendOnTextChanged(buffer, start, before, after);
10636         onTextChanged(buffer, start, before, after);
10637     }
10638 
10639     /**
10640      * Not private so it can be called from an inner class without going
10641      * through a thunk.
10642      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10643     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
10644         // XXX Make the start and end move together if this ends up
10645         // spending too much time invalidating.
10646 
10647         boolean selChanged = false;
10648         int newSelStart = -1, newSelEnd = -1;
10649 
10650         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10651 
10652         if (what == Selection.SELECTION_END) {
10653             selChanged = true;
10654             newSelEnd = newStart;
10655 
10656             if (oldStart >= 0 || newStart >= 0) {
10657                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
10658                 checkForResize();
10659                 registerForPreDraw();
10660                 if (mEditor != null) mEditor.makeBlink();
10661             }
10662         }
10663 
10664         if (what == Selection.SELECTION_START) {
10665             selChanged = true;
10666             newSelStart = newStart;
10667 
10668             if (oldStart >= 0 || newStart >= 0) {
10669                 int end = Selection.getSelectionEnd(buf);
10670                 invalidateCursor(end, oldStart, newStart);
10671             }
10672         }
10673 
10674         if (selChanged) {
10675             mHighlightPathBogus = true;
10676             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
10677 
10678             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
10679                 if (newSelStart < 0) {
10680                     newSelStart = Selection.getSelectionStart(buf);
10681                 }
10682                 if (newSelEnd < 0) {
10683                     newSelEnd = Selection.getSelectionEnd(buf);
10684                 }
10685 
10686                 if (mEditor != null) {
10687                     mEditor.refreshTextActionMode();
10688                     if (!hasSelection()
10689                             && mEditor.getTextActionMode() == null && hasTransientState()) {
10690                         // User generated selection has been removed.
10691                         setHasTransientState(false);
10692                     }
10693                 }
10694                 onSelectionChanged(newSelStart, newSelEnd);
10695             }
10696         }
10697 
10698         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
10699                 || what instanceof CharacterStyle) {
10700             if (ims == null || ims.mBatchEditNesting == 0) {
10701                 invalidate();
10702                 mHighlightPathBogus = true;
10703                 checkForResize();
10704             } else {
10705                 ims.mContentChanged = true;
10706             }
10707             if (mEditor != null) {
10708                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
10709                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
10710                 mEditor.invalidateHandlesAndActionMode();
10711             }
10712         }
10713 
10714         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
10715             mHighlightPathBogus = true;
10716             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
10717                 ims.mSelectionModeChanged = true;
10718             }
10719 
10720             if (Selection.getSelectionStart(buf) >= 0) {
10721                 if (ims == null || ims.mBatchEditNesting == 0) {
10722                     invalidateCursor();
10723                 } else {
10724                     ims.mCursorChanged = true;
10725                 }
10726             }
10727         }
10728 
10729         if (what instanceof ParcelableSpan) {
10730             // If this is a span that can be sent to a remote process,
10731             // the current extract editor would be interested in it.
10732             if (ims != null && ims.mExtractedTextRequest != null) {
10733                 if (ims.mBatchEditNesting != 0) {
10734                     if (oldStart >= 0) {
10735                         if (ims.mChangedStart > oldStart) {
10736                             ims.mChangedStart = oldStart;
10737                         }
10738                         if (ims.mChangedStart > oldEnd) {
10739                             ims.mChangedStart = oldEnd;
10740                         }
10741                     }
10742                     if (newStart >= 0) {
10743                         if (ims.mChangedStart > newStart) {
10744                             ims.mChangedStart = newStart;
10745                         }
10746                         if (ims.mChangedStart > newEnd) {
10747                             ims.mChangedStart = newEnd;
10748                         }
10749                     }
10750                 } else {
10751                     if (DEBUG_EXTRACT) {
10752                         Log.v(LOG_TAG, "Span change outside of batch: "
10753                                 + oldStart + "-" + oldEnd + ","
10754                                 + newStart + "-" + newEnd + " " + what);
10755                     }
10756                     ims.mContentChanged = true;
10757                 }
10758             }
10759         }
10760 
10761         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
10762                 && what instanceof SpellCheckSpan) {
10763             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
10764         }
10765     }
10766 
10767     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10768     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
10769         if (isTemporarilyDetached()) {
10770             // If we are temporarily in the detach state, then do nothing.
10771             super.onFocusChanged(focused, direction, previouslyFocusedRect);
10772             return;
10773         }
10774 
10775         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
10776 
10777         if (focused) {
10778             if (mSpannable != null) {
10779                 MetaKeyKeyListener.resetMetaState(mSpannable);
10780             }
10781         }
10782 
10783         startStopMarquee(focused);
10784 
10785         if (mTransformation != null) {
10786             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
10787         }
10788 
10789         super.onFocusChanged(focused, direction, previouslyFocusedRect);
10790     }
10791 
10792     @Override
onWindowFocusChanged(boolean hasWindowFocus)10793     public void onWindowFocusChanged(boolean hasWindowFocus) {
10794         super.onWindowFocusChanged(hasWindowFocus);
10795 
10796         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
10797 
10798         startStopMarquee(hasWindowFocus);
10799     }
10800 
10801     @Override
onVisibilityChanged(View changedView, int visibility)10802     protected void onVisibilityChanged(View changedView, int visibility) {
10803         super.onVisibilityChanged(changedView, visibility);
10804         if (mEditor != null && visibility != VISIBLE) {
10805             mEditor.hideCursorAndSpanControllers();
10806             stopTextActionMode();
10807         }
10808     }
10809 
10810     /**
10811      * Use {@link BaseInputConnection#removeComposingSpans
10812      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
10813      * state from this text view.
10814      */
clearComposingText()10815     public void clearComposingText() {
10816         if (mText instanceof Spannable) {
10817             BaseInputConnection.removeComposingSpans(mSpannable);
10818         }
10819     }
10820 
10821     @Override
setSelected(boolean selected)10822     public void setSelected(boolean selected) {
10823         boolean wasSelected = isSelected();
10824 
10825         super.setSelected(selected);
10826 
10827         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10828             if (selected) {
10829                 startMarquee();
10830             } else {
10831                 stopMarquee();
10832             }
10833         }
10834     }
10835 
10836     @Override
onTouchEvent(MotionEvent event)10837     public boolean onTouchEvent(MotionEvent event) {
10838         final int action = event.getActionMasked();
10839         if (mEditor != null) {
10840             mEditor.onTouchEvent(event);
10841 
10842             if (mEditor.mSelectionModifierCursorController != null
10843                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
10844                 return true;
10845             }
10846         }
10847 
10848         final boolean superResult = super.onTouchEvent(event);
10849 
10850         /*
10851          * Don't handle the release after a long press, because it will move the selection away from
10852          * whatever the menu action was trying to affect. If the long press should have triggered an
10853          * insertion action mode, we can now actually show it.
10854          */
10855         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
10856             mEditor.mDiscardNextActionUp = false;
10857 
10858             if (mEditor.mIsInsertionActionModeStartPending) {
10859                 mEditor.startInsertionActionMode();
10860                 mEditor.mIsInsertionActionModeStartPending = false;
10861             }
10862             return superResult;
10863         }
10864 
10865         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
10866                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
10867 
10868         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
10869                 && mText instanceof Spannable && mLayout != null) {
10870             boolean handled = false;
10871 
10872             if (mMovement != null) {
10873                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
10874             }
10875 
10876             final boolean textIsSelectable = isTextSelectable();
10877             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
10878                 // The LinkMovementMethod which should handle taps on links has not been installed
10879                 // on non editable text that support text selection.
10880                 // We reproduce its behavior here to open links for these.
10881                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
10882                     getSelectionEnd(), ClickableSpan.class);
10883 
10884                 if (links.length > 0) {
10885                     links[0].onClick(this);
10886                     handled = true;
10887                 }
10888             }
10889 
10890             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
10891                 // Show the IME, except when selecting in read-only text.
10892                 final InputMethodManager imm = getInputMethodManager();
10893                 viewClicked(imm);
10894                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10895                     imm.showSoftInput(this, 0);
10896                 }
10897 
10898                 // The above condition ensures that the mEditor is not null
10899                 mEditor.onTouchUpEvent(event);
10900 
10901                 handled = true;
10902             }
10903 
10904             if (handled) {
10905                 return true;
10906             }
10907         }
10908 
10909         return superResult;
10910     }
10911 
10912     @Override
onGenericMotionEvent(MotionEvent event)10913     public boolean onGenericMotionEvent(MotionEvent event) {
10914         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
10915             try {
10916                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
10917                     return true;
10918                 }
10919             } catch (AbstractMethodError ex) {
10920                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
10921                 // Ignore its absence in case third party applications implemented the
10922                 // interface directly.
10923             }
10924         }
10925         return super.onGenericMotionEvent(event);
10926     }
10927 
10928     @Override
onCreateContextMenu(ContextMenu menu)10929     protected void onCreateContextMenu(ContextMenu menu) {
10930         if (mEditor != null) {
10931             mEditor.onCreateContextMenu(menu);
10932         }
10933     }
10934 
10935     @Override
showContextMenu()10936     public boolean showContextMenu() {
10937         if (mEditor != null) {
10938             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
10939         }
10940         return super.showContextMenu();
10941     }
10942 
10943     @Override
showContextMenu(float x, float y)10944     public boolean showContextMenu(float x, float y) {
10945         if (mEditor != null) {
10946             mEditor.setContextMenuAnchor(x, y);
10947         }
10948         return super.showContextMenu(x, y);
10949     }
10950 
10951     /**
10952      * @return True iff this TextView contains a text that can be edited, or if this is
10953      * a selectable TextView.
10954      */
10955     @UnsupportedAppUsage
isTextEditable()10956     boolean isTextEditable() {
10957         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
10958     }
10959 
10960     /**
10961      * Returns true, only while processing a touch gesture, if the initial
10962      * touch down event caused focus to move to the text view and as a result
10963      * its selection changed.  Only valid while processing the touch gesture
10964      * of interest, in an editable text view.
10965      */
didTouchFocusSelect()10966     public boolean didTouchFocusSelect() {
10967         return mEditor != null && mEditor.mTouchFocusSelected;
10968     }
10969 
10970     @Override
cancelLongPress()10971     public void cancelLongPress() {
10972         super.cancelLongPress();
10973         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
10974     }
10975 
10976     @Override
onTrackballEvent(MotionEvent event)10977     public boolean onTrackballEvent(MotionEvent event) {
10978         if (mMovement != null && mSpannable != null && mLayout != null) {
10979             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
10980                 return true;
10981             }
10982         }
10983 
10984         return super.onTrackballEvent(event);
10985     }
10986 
10987     /**
10988      * Sets the Scroller used for producing a scrolling animation
10989      *
10990      * @param s A Scroller instance
10991      */
setScroller(Scroller s)10992     public void setScroller(Scroller s) {
10993         mScroller = s;
10994     }
10995 
10996     @Override
getLeftFadingEdgeStrength()10997     protected float getLeftFadingEdgeStrength() {
10998         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
10999             final Marquee marquee = mMarquee;
11000             if (marquee.shouldDrawLeftFade()) {
11001                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
11002             } else {
11003                 return 0.0f;
11004             }
11005         } else if (getLineCount() == 1) {
11006             final float lineLeft = getLayout().getLineLeft(0);
11007             if (lineLeft > mScrollX) return 0.0f;
11008             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
11009         }
11010         return super.getLeftFadingEdgeStrength();
11011     }
11012 
11013     @Override
getRightFadingEdgeStrength()11014     protected float getRightFadingEdgeStrength() {
11015         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11016             final Marquee marquee = mMarquee;
11017             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
11018         } else if (getLineCount() == 1) {
11019             final float rightEdge = mScrollX +
11020                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
11021             final float lineRight = getLayout().getLineRight(0);
11022             if (lineRight < rightEdge) return 0.0f;
11023             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
11024         }
11025         return super.getRightFadingEdgeStrength();
11026     }
11027 
11028     /**
11029      * Calculates the fading edge strength as the ratio of the distance between two
11030      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
11031      * value for the distance calculation.
11032      *
11033      * @param position1 A horizontal position.
11034      * @param position2 A horizontal position.
11035      * @return Fading edge strength between [0.0f, 1.0f].
11036      */
11037     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)11038     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
11039         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
11040         if (horizontalFadingEdgeLength == 0) return 0.0f;
11041         final float diff = Math.abs(position1 - position2);
11042         if (diff > horizontalFadingEdgeLength) return 1.0f;
11043         return diff / horizontalFadingEdgeLength;
11044     }
11045 
isMarqueeFadeEnabled()11046     private boolean isMarqueeFadeEnabled() {
11047         return mEllipsize == TextUtils.TruncateAt.MARQUEE
11048                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
11049     }
11050 
11051     @Override
computeHorizontalScrollRange()11052     protected int computeHorizontalScrollRange() {
11053         if (mLayout != null) {
11054             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
11055                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
11056         }
11057 
11058         return super.computeHorizontalScrollRange();
11059     }
11060 
11061     @Override
computeVerticalScrollRange()11062     protected int computeVerticalScrollRange() {
11063         if (mLayout != null) {
11064             return mLayout.getHeight();
11065         }
11066         return super.computeVerticalScrollRange();
11067     }
11068 
11069     @Override
computeVerticalScrollExtent()11070     protected int computeVerticalScrollExtent() {
11071         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
11072     }
11073 
11074     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11075     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
11076         super.findViewsWithText(outViews, searched, flags);
11077         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
11078                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
11079             String searchedLowerCase = searched.toString().toLowerCase();
11080             String textLowerCase = mText.toString().toLowerCase();
11081             if (textLowerCase.contains(searchedLowerCase)) {
11082                 outViews.add(this);
11083             }
11084         }
11085     }
11086 
11087     /**
11088      * Type of the text buffer that defines the characteristics of the text such as static,
11089      * styleable, or editable.
11090      */
11091     public enum BufferType {
11092         NORMAL, SPANNABLE, EDITABLE
11093     }
11094 
11095     /**
11096      * Returns the TextView_textColor attribute from the TypedArray, if set, or
11097      * the TextAppearance_textColor from the TextView_textAppearance attribute,
11098      * if TextView_textColor was not set directly.
11099      *
11100      * @removed
11101      */
getTextColors(Context context, TypedArray attrs)11102     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
11103         if (attrs == null) {
11104             // Preserve behavior prior to removal of this API.
11105             throw new NullPointerException();
11106         }
11107 
11108         // It's not safe to use this method from apps. The parameter 'attrs'
11109         // must have been obtained using the TextView filter array which is not
11110         // available to the SDK. As such, we grab a default TypedArray with the
11111         // right filter instead here.
11112         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
11113         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
11114         if (colors == null) {
11115             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
11116             if (ap != 0) {
11117                 final TypedArray appearance = context.obtainStyledAttributes(
11118                         ap, R.styleable.TextAppearance);
11119                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
11120                 appearance.recycle();
11121             }
11122         }
11123         a.recycle();
11124 
11125         return colors;
11126     }
11127 
11128     /**
11129      * Returns the default color from the TextView_textColor attribute from the
11130      * AttributeSet, if set, or the default color from the
11131      * TextAppearance_textColor from the TextView_textAppearance attribute, if
11132      * TextView_textColor was not set directly.
11133      *
11134      * @removed
11135      */
getTextColor(Context context, TypedArray attrs, int def)11136     public static int getTextColor(Context context, TypedArray attrs, int def) {
11137         final ColorStateList colors = getTextColors(context, attrs);
11138         if (colors == null) {
11139             return def;
11140         } else {
11141             return colors.getDefaultColor();
11142         }
11143     }
11144 
11145     @Override
onKeyShortcut(int keyCode, KeyEvent event)11146     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
11147         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
11148             // Handle Ctrl-only shortcuts.
11149             switch (keyCode) {
11150                 case KeyEvent.KEYCODE_A:
11151                     if (canSelectText()) {
11152                         return onTextContextMenuItem(ID_SELECT_ALL);
11153                     }
11154                     break;
11155                 case KeyEvent.KEYCODE_Z:
11156                     if (canUndo()) {
11157                         return onTextContextMenuItem(ID_UNDO);
11158                     }
11159                     break;
11160                 case KeyEvent.KEYCODE_X:
11161                     if (canCut()) {
11162                         return onTextContextMenuItem(ID_CUT);
11163                     }
11164                     break;
11165                 case KeyEvent.KEYCODE_C:
11166                     if (canCopy()) {
11167                         return onTextContextMenuItem(ID_COPY);
11168                     }
11169                     break;
11170                 case KeyEvent.KEYCODE_V:
11171                     if (canPaste()) {
11172                         return onTextContextMenuItem(ID_PASTE);
11173                     }
11174                     break;
11175             }
11176         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
11177             // Handle Ctrl-Shift shortcuts.
11178             switch (keyCode) {
11179                 case KeyEvent.KEYCODE_Z:
11180                     if (canRedo()) {
11181                         return onTextContextMenuItem(ID_REDO);
11182                     }
11183                     break;
11184                 case KeyEvent.KEYCODE_V:
11185                     if (canPaste()) {
11186                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
11187                     }
11188             }
11189         }
11190         return super.onKeyShortcut(keyCode, event);
11191     }
11192 
11193     /**
11194      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
11195      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
11196      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
11197      * sufficient.
11198      */
canSelectText()11199     boolean canSelectText() {
11200         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
11201     }
11202 
11203     /**
11204      * Test based on the <i>intrinsic</i> charateristics of the TextView.
11205      * The text must be spannable and the movement method must allow for arbitary selection.
11206      *
11207      * See also {@link #canSelectText()}.
11208      */
textCanBeSelected()11209     boolean textCanBeSelected() {
11210         // prepareCursorController() relies on this method.
11211         // If you change this condition, make sure prepareCursorController is called anywhere
11212         // the value of this condition might be changed.
11213         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
11214         return isTextEditable()
11215                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
11216     }
11217 
11218     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)11219     private Locale getTextServicesLocale(boolean allowNullLocale) {
11220         // Start fetching the text services locale asynchronously.
11221         updateTextServicesLocaleAsync();
11222         // If !allowNullLocale and there is no cached text services locale, just return the default
11223         // locale.
11224         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
11225                 : mCurrentSpellCheckerLocaleCache;
11226     }
11227 
11228     /**
11229      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
11230      * this {@link TextView}.
11231      *
11232      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
11233      * other apps may need to set this so that the system can user right user's resources and
11234      * services such as input methods and spell checkers.</p>
11235      *
11236      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
11237      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
11238      * @hide
11239      */
11240     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)11241     public final void setTextOperationUser(@Nullable UserHandle user) {
11242         if (Objects.equals(mTextOperationUser, user)) {
11243             return;
11244         }
11245         if (user != null && !Process.myUserHandle().equals(user)) {
11246             // Just for preventing people from accidentally using this hidden API without
11247             // the required permission.  The same permission is also checked in the system server.
11248             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
11249                     != PackageManager.PERMISSION_GRANTED) {
11250                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
11251                         + " userId=" + user.getIdentifier()
11252                         + " callingUserId" + UserHandle.myUserId());
11253             }
11254         }
11255         mTextOperationUser = user;
11256         // Invalidate some resources
11257         mCurrentSpellCheckerLocaleCache = null;
11258         if (mEditor != null) {
11259             mEditor.onTextOperationUserChanged();
11260         }
11261     }
11262 
11263     @Nullable
getTextServicesManagerForUser()11264     final TextServicesManager getTextServicesManagerForUser() {
11265         return getServiceManagerForUser("android", TextServicesManager.class);
11266     }
11267 
11268     @Nullable
getClipboardManagerForUser()11269     final ClipboardManager getClipboardManagerForUser() {
11270         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
11271     }
11272 
11273     @Nullable
getTextClassificationManagerForUser()11274     final TextClassificationManager getTextClassificationManagerForUser() {
11275         return getServiceManagerForUser(
11276                 getContext().getPackageName(), TextClassificationManager.class);
11277     }
11278 
11279     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)11280     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
11281         if (mTextOperationUser == null) {
11282             return getContext().getSystemService(managerClazz);
11283         }
11284         try {
11285             Context context = getContext().createPackageContextAsUser(
11286                     packageName, 0 /* flags */, mTextOperationUser);
11287             return context.getSystemService(managerClazz);
11288         } catch (PackageManager.NameNotFoundException e) {
11289             return null;
11290         }
11291     }
11292 
11293     /**
11294      * Starts {@link Activity} as a text-operation user if it is specified with
11295      * {@link #setTextOperationUser(UserHandle)}.
11296      *
11297      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
11298      *
11299      * @param intent The description of the activity to start.
11300      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11301     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
11302         if (mTextOperationUser != null) {
11303             getContext().startActivityAsUser(intent, mTextOperationUser);
11304         } else {
11305             getContext().startActivity(intent);
11306         }
11307     }
11308 
11309     /**
11310      * This is a temporary method. Future versions may support multi-locale text.
11311      * Caveat: This method may not return the latest text services locale, but this should be
11312      * acceptable and it's more important to make this method asynchronous.
11313      *
11314      * @return The locale that should be used for a word iterator
11315      * in this TextView, based on the current spell checker settings,
11316      * the current IME's locale, or the system default locale.
11317      * Please note that a word iterator in this TextView is different from another word iterator
11318      * used by SpellChecker.java of TextView. This method should be used for the former.
11319      * @hide
11320      */
11321     // TODO: Support multi-locale
11322     // TODO: Update the text services locale immediately after the keyboard locale is switched
11323     // by catching intent of keyboard switch event
getTextServicesLocale()11324     public Locale getTextServicesLocale() {
11325         return getTextServicesLocale(false /* allowNullLocale */);
11326     }
11327 
11328     /**
11329      * @return {@code true} if this TextView is specialized for showing and interacting with the
11330      * extracted text in a full-screen input method.
11331      * @hide
11332      */
isInExtractedMode()11333     public boolean isInExtractedMode() {
11334         return false;
11335     }
11336 
11337     /**
11338      * @return {@code true} if this widget supports auto-sizing text and has been configured to
11339      * auto-size.
11340      */
isAutoSizeEnabled()11341     private boolean isAutoSizeEnabled() {
11342         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
11343     }
11344 
11345     /**
11346      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
11347      * @hide
11348      */
supportsAutoSizeText()11349     protected boolean supportsAutoSizeText() {
11350         return true;
11351     }
11352 
11353     /**
11354      * This is a temporary method. Future versions may support multi-locale text.
11355      * Caveat: This method may not return the latest spell checker locale, but this should be
11356      * acceptable and it's more important to make this method asynchronous.
11357      *
11358      * @return The locale that should be used for a spell checker in this TextView,
11359      * based on the current spell checker settings, the current IME's locale, or the system default
11360      * locale.
11361      * @hide
11362      */
getSpellCheckerLocale()11363     public Locale getSpellCheckerLocale() {
11364         return getTextServicesLocale(true /* allowNullLocale */);
11365     }
11366 
updateTextServicesLocaleAsync()11367     private void updateTextServicesLocaleAsync() {
11368         // AsyncTask.execute() uses a serial executor which means we don't have
11369         // to lock around updateTextServicesLocaleLocked() to prevent it from
11370         // being executed n times in parallel.
11371         AsyncTask.execute(new Runnable() {
11372             @Override
11373             public void run() {
11374                 updateTextServicesLocaleLocked();
11375             }
11376         });
11377     }
11378 
11379     @UnsupportedAppUsage
updateTextServicesLocaleLocked()11380     private void updateTextServicesLocaleLocked() {
11381         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
11382         if (textServicesManager == null) {
11383             return;
11384         }
11385         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
11386         final Locale locale;
11387         if (subtype != null) {
11388             locale = subtype.getLocaleObject();
11389         } else {
11390             locale = null;
11391         }
11392         mCurrentSpellCheckerLocaleCache = locale;
11393     }
11394 
onLocaleChanged()11395     void onLocaleChanged() {
11396         mEditor.onLocaleChanged();
11397     }
11398 
11399     /**
11400      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
11401      * Made available to achieve a consistent behavior.
11402      * @hide
11403      */
getWordIterator()11404     public WordIterator getWordIterator() {
11405         if (mEditor != null) {
11406             return mEditor.getWordIterator();
11407         } else {
11408             return null;
11409         }
11410     }
11411 
11412     /** @hide */
11413     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)11414     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
11415         super.onPopulateAccessibilityEventInternal(event);
11416 
11417         final CharSequence text = getTextForAccessibility();
11418         if (!TextUtils.isEmpty(text)) {
11419             event.getText().add(text);
11420         }
11421     }
11422 
11423     @Override
getAccessibilityClassName()11424     public CharSequence getAccessibilityClassName() {
11425         return TextView.class.getName();
11426     }
11427 
11428     /** @hide */
11429     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11430     protected void onProvideStructure(@NonNull ViewStructure structure,
11431             @ViewStructureType int viewFor, int flags) {
11432         super.onProvideStructure(structure, viewFor, flags);
11433 
11434         final boolean isPassword = hasPasswordTransformationMethod()
11435                 || isPasswordInputType(getInputType());
11436         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11437             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11438                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
11439             }
11440             if (mTextId != Resources.ID_NULL) {
11441                 try {
11442                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
11443                 } catch (Resources.NotFoundException e) {
11444                     if (android.view.autofill.Helper.sVerbose) {
11445                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
11446                                 + mTextId + ": " + e.getMessage());
11447                     }
11448                 }
11449             }
11450         }
11451 
11452         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11453             if (mLayout == null) {
11454                 assumeLayout();
11455             }
11456             Layout layout = mLayout;
11457             final int lineCount = layout.getLineCount();
11458             if (lineCount <= 1) {
11459                 // Simple case: this is a single line.
11460                 final CharSequence text = getText();
11461                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11462                     structure.setText(text);
11463                 } else {
11464                     structure.setText(text, getSelectionStart(), getSelectionEnd());
11465                 }
11466             } else {
11467                 // Complex case: multi-line, could be scrolled or within a scroll container
11468                 // so some lines are not visible.
11469                 final int[] tmpCords = new int[2];
11470                 getLocationInWindow(tmpCords);
11471                 final int topWindowLocation = tmpCords[1];
11472                 View root = this;
11473                 ViewParent viewParent = getParent();
11474                 while (viewParent instanceof View) {
11475                     root = (View) viewParent;
11476                     viewParent = root.getParent();
11477                 }
11478                 final int windowHeight = root.getHeight();
11479                 final int topLine;
11480                 final int bottomLine;
11481                 if (topWindowLocation >= 0) {
11482                     // The top of the view is fully within its window; start text at line 0.
11483                     topLine = getLineAtCoordinateUnclamped(0);
11484                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
11485                 } else {
11486                     // The top of hte window has scrolled off the top of the window; figure out
11487                     // the starting line for this.
11488                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
11489                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
11490                 }
11491                 // We want to return some contextual lines above/below the lines that are
11492                 // actually visible.
11493                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
11494                 if (expandedTopLine < 0) {
11495                     expandedTopLine = 0;
11496                 }
11497                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
11498                 if (expandedBottomLine >= lineCount) {
11499                     expandedBottomLine = lineCount - 1;
11500                 }
11501 
11502                 // Convert lines into character offsets.
11503                 int expandedTopChar = layout.getLineStart(expandedTopLine);
11504                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
11505 
11506                 // Take into account selection -- if there is a selection, we need to expand
11507                 // the text we are returning to include that selection.
11508                 final int selStart = getSelectionStart();
11509                 final int selEnd = getSelectionEnd();
11510                 if (selStart < selEnd) {
11511                     if (selStart < expandedTopChar) {
11512                         expandedTopChar = selStart;
11513                     }
11514                     if (selEnd > expandedBottomChar) {
11515                         expandedBottomChar = selEnd;
11516                     }
11517                 }
11518 
11519                 // Get the text and trim it to the range we are reporting.
11520                 CharSequence text = getText();
11521                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
11522                     text = text.subSequence(expandedTopChar, expandedBottomChar);
11523                 }
11524 
11525                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11526                     structure.setText(text);
11527                 } else {
11528                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
11529 
11530                     final int[] lineOffsets = new int[bottomLine - topLine + 1];
11531                     final int[] lineBaselines = new int[bottomLine - topLine + 1];
11532                     final int baselineOffset = getBaselineOffset();
11533                     for (int i = topLine; i <= bottomLine; i++) {
11534                         lineOffsets[i - topLine] = layout.getLineStart(i);
11535                         lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
11536                     }
11537                     structure.setTextLines(lineOffsets, lineBaselines);
11538                 }
11539             }
11540 
11541             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST) {
11542                 // Extract style information that applies to the TextView as a whole.
11543                 int style = 0;
11544                 int typefaceStyle = getTypefaceStyle();
11545                 if ((typefaceStyle & Typeface.BOLD) != 0) {
11546                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11547                 }
11548                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
11549                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
11550                 }
11551 
11552                 // Global styles can also be set via TextView.setPaintFlags().
11553                 int paintFlags = mTextPaint.getFlags();
11554                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
11555                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11556                 }
11557                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
11558                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
11559                 }
11560                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
11561                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
11562                 }
11563 
11564                 // TextView does not have its own text background color. A background is either part
11565                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
11566                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
11567                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
11568             }
11569             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11570                 structure.setMinTextEms(getMinEms());
11571                 structure.setMaxTextEms(getMaxEms());
11572                 int maxLength = -1;
11573                 for (InputFilter filter: getFilters()) {
11574                     if (filter instanceof InputFilter.LengthFilter) {
11575                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
11576                         break;
11577                     }
11578                 }
11579                 structure.setMaxTextLength(maxLength);
11580             }
11581         }
11582         structure.setHint(getHint());
11583         structure.setInputType(getInputType());
11584     }
11585 
canRequestAutofill()11586     boolean canRequestAutofill() {
11587         if (!isAutofillable()) {
11588             return false;
11589         }
11590         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11591         if (afm != null) {
11592             return afm.isEnabled();
11593         }
11594         return false;
11595     }
11596 
requestAutofill()11597     private void requestAutofill() {
11598         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11599         if (afm != null) {
11600             afm.requestAutofill(this);
11601         }
11602     }
11603 
11604     @Override
autofill(AutofillValue value)11605     public void autofill(AutofillValue value) {
11606         if (!value.isText() || !isTextEditable()) {
11607             Log.w(LOG_TAG, value + " could not be autofilled into " + this);
11608             return;
11609         }
11610 
11611         final CharSequence autofilledValue = value.getTextValue();
11612 
11613         // First autofill it...
11614         setText(autofilledValue, mBufferType, true, 0);
11615 
11616         // ...then move cursor to the end.
11617         final CharSequence text = getText();
11618         if ((text instanceof Spannable)) {
11619             Selection.setSelection((Spannable) text, text.length());
11620         }
11621     }
11622 
11623     @Override
getAutofillType()11624     public @AutofillType int getAutofillType() {
11625         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
11626     }
11627 
11628     /**
11629      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
11630      * {@code char}s if longer.
11631      *
11632      * @return current text, {@code null} if the text is not editable
11633      *
11634      * @see View#getAutofillValue()
11635      */
11636     @Override
11637     @Nullable
getAutofillValue()11638     public AutofillValue getAutofillValue() {
11639         if (isTextEditable()) {
11640             final CharSequence text = TextUtils.trimToParcelableSize(getText());
11641             return AutofillValue.forText(text);
11642         }
11643         return null;
11644     }
11645 
11646     /** @hide */
11647     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)11648     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
11649         super.onInitializeAccessibilityEventInternal(event);
11650 
11651         final boolean isPassword = hasPasswordTransformationMethod();
11652         event.setPassword(isPassword);
11653 
11654         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
11655             event.setFromIndex(Selection.getSelectionStart(mText));
11656             event.setToIndex(Selection.getSelectionEnd(mText));
11657             event.setItemCount(mText.length());
11658         }
11659     }
11660 
11661     /** @hide */
11662     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11663     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
11664         super.onInitializeAccessibilityNodeInfoInternal(info);
11665 
11666         final boolean isPassword = hasPasswordTransformationMethod();
11667         info.setPassword(isPassword);
11668         info.setText(getTextForAccessibility());
11669         info.setHintText(mHint);
11670         info.setShowingHintText(isShowingHint());
11671 
11672         if (mBufferType == BufferType.EDITABLE) {
11673             info.setEditable(true);
11674             if (isEnabled()) {
11675                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
11676             }
11677         }
11678 
11679         if (mEditor != null) {
11680             info.setInputType(mEditor.mInputType);
11681 
11682             if (mEditor.mError != null) {
11683                 info.setContentInvalid(true);
11684                 info.setError(mEditor.mError);
11685             }
11686         }
11687 
11688         if (!TextUtils.isEmpty(mText)) {
11689             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
11690             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
11691             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
11692                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
11693                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
11694                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
11695                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
11696             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
11697             info.setAvailableExtraData(
11698                     Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
11699         }
11700 
11701         if (isFocused()) {
11702             if (canCopy()) {
11703                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
11704             }
11705             if (canPaste()) {
11706                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
11707             }
11708             if (canCut()) {
11709                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
11710             }
11711             if (canShare()) {
11712                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
11713                         ACCESSIBILITY_ACTION_SHARE,
11714                         getResources().getString(com.android.internal.R.string.share)));
11715             }
11716             if (canProcessText()) {  // also implies mEditor is not null.
11717                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
11718             }
11719         }
11720 
11721         // Check for known input filter types.
11722         final int numFilters = mFilters.length;
11723         for (int i = 0; i < numFilters; i++) {
11724             final InputFilter filter = mFilters[i];
11725             if (filter instanceof InputFilter.LengthFilter) {
11726                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
11727             }
11728         }
11729 
11730         if (!isSingleLine()) {
11731             info.setMultiLine(true);
11732         }
11733     }
11734 
11735     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11736     public void addExtraDataToAccessibilityNodeInfo(
11737             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
11738         // The only extra data we support requires arguments.
11739         if (arguments == null) {
11740             return;
11741         }
11742         if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
11743             int positionInfoStartIndex = arguments.getInt(
11744                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
11745             int positionInfoLength = arguments.getInt(
11746                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
11747             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
11748                     || (positionInfoStartIndex >= mText.length())) {
11749                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
11750                 return;
11751             }
11752             RectF[] boundingRects = new RectF[positionInfoLength];
11753             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
11754             populateCharacterBounds(builder, positionInfoStartIndex,
11755                     positionInfoStartIndex + positionInfoLength,
11756                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
11757             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
11758             for (int i = 0; i < positionInfoLength; i++) {
11759                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
11760                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
11761                     RectF bounds = cursorAnchorInfo
11762                             .getCharacterBounds(positionInfoStartIndex + i);
11763                     if (bounds != null) {
11764                         mapRectFromViewToScreenCoords(bounds, true);
11765                         boundingRects[i] = bounds;
11766                     }
11767                 }
11768             }
11769             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
11770         }
11771     }
11772 
11773     /**
11774      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
11775      *
11776      * @param builder The builder to populate
11777      * @param startIndex The starting character index to populate
11778      * @param endIndex The ending character index to populate
11779      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
11780      * content
11781      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
11782      * @hide
11783      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)11784     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
11785             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
11786             float viewportToContentVerticalOffset) {
11787         final int minLine = mLayout.getLineForOffset(startIndex);
11788         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
11789         for (int line = minLine; line <= maxLine; ++line) {
11790             final int lineStart = mLayout.getLineStart(line);
11791             final int lineEnd = mLayout.getLineEnd(line);
11792             final int offsetStart = Math.max(lineStart, startIndex);
11793             final int offsetEnd = Math.min(lineEnd, endIndex);
11794             final boolean ltrLine =
11795                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
11796             final float[] widths = new float[offsetEnd - offsetStart];
11797             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
11798             final float top = mLayout.getLineTop(line);
11799             final float bottom = mLayout.getLineBottom(line);
11800             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
11801                 final float charWidth = widths[offset - offsetStart];
11802                 final boolean isRtl = mLayout.isRtlCharAt(offset);
11803                 final float primary = mLayout.getPrimaryHorizontal(offset);
11804                 final float secondary = mLayout.getSecondaryHorizontal(offset);
11805                 // TODO: This doesn't work perfectly for text with custom styles and
11806                 // TAB chars.
11807                 final float left;
11808                 final float right;
11809                 if (ltrLine) {
11810                     if (isRtl) {
11811                         left = secondary - charWidth;
11812                         right = secondary;
11813                     } else {
11814                         left = primary;
11815                         right = primary + charWidth;
11816                     }
11817                 } else {
11818                     if (!isRtl) {
11819                         left = secondary;
11820                         right = secondary + charWidth;
11821                     } else {
11822                         left = primary - charWidth;
11823                         right = primary;
11824                     }
11825                 }
11826                 // TODO: Check top-right and bottom-left as well.
11827                 final float localLeft = left + viewportToContentHorizontalOffset;
11828                 final float localRight = right + viewportToContentHorizontalOffset;
11829                 final float localTop = top + viewportToContentVerticalOffset;
11830                 final float localBottom = bottom + viewportToContentVerticalOffset;
11831                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
11832                 final boolean isBottomRightVisible =
11833                         isPositionVisible(localRight, localBottom);
11834                 int characterBoundsFlags = 0;
11835                 if (isTopLeftVisible || isBottomRightVisible) {
11836                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
11837                 }
11838                 if (!isTopLeftVisible || !isBottomRightVisible) {
11839                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
11840                 }
11841                 if (isRtl) {
11842                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
11843                 }
11844                 // Here offset is the index in Java chars.
11845                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
11846                         localBottom, characterBoundsFlags);
11847             }
11848         }
11849     }
11850 
11851     /**
11852      * @hide
11853      */
isPositionVisible(final float positionX, final float positionY)11854     public boolean isPositionVisible(final float positionX, final float positionY) {
11855         synchronized (TEMP_POSITION) {
11856             final float[] position = TEMP_POSITION;
11857             position[0] = positionX;
11858             position[1] = positionY;
11859             View view = this;
11860 
11861             while (view != null) {
11862                 if (view != this) {
11863                     // Local scroll is already taken into account in positionX/Y
11864                     position[0] -= view.getScrollX();
11865                     position[1] -= view.getScrollY();
11866                 }
11867 
11868                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
11869                         || position[1] > view.getHeight()) {
11870                     return false;
11871                 }
11872 
11873                 if (!view.getMatrix().isIdentity()) {
11874                     view.getMatrix().mapPoints(position);
11875                 }
11876 
11877                 position[0] += view.getLeft();
11878                 position[1] += view.getTop();
11879 
11880                 final ViewParent parent = view.getParent();
11881                 if (parent instanceof View) {
11882                     view = (View) parent;
11883                 } else {
11884                     // We've reached the ViewRoot, stop iterating
11885                     view = null;
11886                 }
11887             }
11888         }
11889 
11890         // We've been able to walk up the view hierarchy and the position was never clipped
11891         return true;
11892     }
11893 
11894     /**
11895      * Performs an accessibility action after it has been offered to the
11896      * delegate.
11897      *
11898      * @hide
11899      */
11900     @Override
performAccessibilityActionInternal(int action, Bundle arguments)11901     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
11902         if (mEditor != null
11903                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
11904             return true;
11905         }
11906         switch (action) {
11907             case AccessibilityNodeInfo.ACTION_CLICK: {
11908                 return performAccessibilityActionClick(arguments);
11909             }
11910             case AccessibilityNodeInfo.ACTION_COPY: {
11911                 if (isFocused() && canCopy()) {
11912                     if (onTextContextMenuItem(ID_COPY)) {
11913                         return true;
11914                     }
11915                 }
11916             } return false;
11917             case AccessibilityNodeInfo.ACTION_PASTE: {
11918                 if (isFocused() && canPaste()) {
11919                     if (onTextContextMenuItem(ID_PASTE)) {
11920                         return true;
11921                     }
11922                 }
11923             } return false;
11924             case AccessibilityNodeInfo.ACTION_CUT: {
11925                 if (isFocused() && canCut()) {
11926                     if (onTextContextMenuItem(ID_CUT)) {
11927                         return true;
11928                     }
11929                 }
11930             } return false;
11931             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
11932                 ensureIterableTextForAccessibilitySelectable();
11933                 CharSequence text = getIterableTextForAccessibility();
11934                 if (text == null) {
11935                     return false;
11936                 }
11937                 final int start = (arguments != null) ? arguments.getInt(
11938                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
11939                 final int end = (arguments != null) ? arguments.getInt(
11940                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
11941                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
11942                     // No arguments clears the selection.
11943                     if (start == end && end == -1) {
11944                         Selection.removeSelection((Spannable) text);
11945                         return true;
11946                     }
11947                     if (start >= 0 && start <= end && end <= text.length()) {
11948                         Selection.setSelection((Spannable) text, start, end);
11949                         // Make sure selection mode is engaged.
11950                         if (mEditor != null) {
11951                             mEditor.startSelectionActionModeAsync(false);
11952                         }
11953                         return true;
11954                     }
11955                 }
11956             } return false;
11957             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
11958             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
11959                 ensureIterableTextForAccessibilitySelectable();
11960                 return super.performAccessibilityActionInternal(action, arguments);
11961             }
11962             case ACCESSIBILITY_ACTION_SHARE: {
11963                 if (isFocused() && canShare()) {
11964                     if (onTextContextMenuItem(ID_SHARE)) {
11965                         return true;
11966                     }
11967                 }
11968             } return false;
11969             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
11970                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
11971                     return false;
11972                 }
11973                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
11974                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
11975                 setText(text);
11976                 if (mText != null) {
11977                     int updatedTextLength = mText.length();
11978                     if (updatedTextLength > 0) {
11979                         Selection.setSelection(mSpannable, updatedTextLength);
11980                     }
11981                 }
11982             } return true;
11983             default: {
11984                 return super.performAccessibilityActionInternal(action, arguments);
11985             }
11986         }
11987     }
11988 
performAccessibilityActionClick(Bundle arguments)11989     private boolean performAccessibilityActionClick(Bundle arguments) {
11990         boolean handled = false;
11991 
11992         if (!isEnabled()) {
11993             return false;
11994         }
11995 
11996         if (isClickable() || isLongClickable()) {
11997             // Simulate View.onTouchEvent for an ACTION_UP event
11998             if (isFocusable() && !isFocused()) {
11999                 requestFocus();
12000             }
12001 
12002             performClick();
12003             handled = true;
12004         }
12005 
12006         // Show the IME, except when selecting in read-only text.
12007         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
12008                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
12009             final InputMethodManager imm = getInputMethodManager();
12010             viewClicked(imm);
12011             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
12012                 handled |= imm.showSoftInput(this, 0);
12013             }
12014         }
12015 
12016         return handled;
12017     }
12018 
hasSpannableText()12019     private boolean hasSpannableText() {
12020         return mText != null && mText instanceof Spannable;
12021     }
12022 
12023     /** @hide */
12024     @Override
sendAccessibilityEventInternal(int eventType)12025     public void sendAccessibilityEventInternal(int eventType) {
12026         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
12027             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
12028         }
12029 
12030         super.sendAccessibilityEventInternal(eventType);
12031     }
12032 
12033     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)12034     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
12035         // Do not send scroll events since first they are not interesting for
12036         // accessibility and second such events a generated too frequently.
12037         // For details see the implementation of bringTextIntoView().
12038         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
12039             return;
12040         }
12041         super.sendAccessibilityEventUnchecked(event);
12042     }
12043 
12044     /**
12045      * Returns the text that should be exposed to accessibility services.
12046      * <p>
12047      * This approximates what is displayed visually. If the user has specified
12048      * that accessibility services should speak passwords, this method will
12049      * bypass any password transformation method and return unobscured text.
12050      *
12051      * @return the text that should be exposed to accessibility services, may
12052      *         be {@code null} if no text is set
12053      */
12054     @Nullable
12055     @UnsupportedAppUsage
getTextForAccessibility()12056     private CharSequence getTextForAccessibility() {
12057         // If the text is empty, we must be showing the hint text.
12058         if (TextUtils.isEmpty(mText)) {
12059             return mHint;
12060         }
12061 
12062         // Otherwise, return whatever text is being displayed.
12063         return TextUtils.trimToParcelableSize(mTransformed);
12064     }
12065 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12066     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
12067             int fromIndex, int removedCount, int addedCount) {
12068         AccessibilityEvent event =
12069                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
12070         event.setFromIndex(fromIndex);
12071         event.setRemovedCount(removedCount);
12072         event.setAddedCount(addedCount);
12073         event.setBeforeText(beforeText);
12074         sendAccessibilityEventUnchecked(event);
12075     }
12076 
getInputMethodManager()12077     private InputMethodManager getInputMethodManager() {
12078         return getContext().getSystemService(InputMethodManager.class);
12079     }
12080 
12081     /**
12082      * Returns whether this text view is a current input method target.  The
12083      * default implementation just checks with {@link InputMethodManager}.
12084      * @return True if the TextView is a current input method target; false otherwise.
12085      */
isInputMethodTarget()12086     public boolean isInputMethodTarget() {
12087         InputMethodManager imm = getInputMethodManager();
12088         return imm != null && imm.isActive(this);
12089     }
12090 
12091     static final int ID_SELECT_ALL = android.R.id.selectAll;
12092     static final int ID_UNDO = android.R.id.undo;
12093     static final int ID_REDO = android.R.id.redo;
12094     static final int ID_CUT = android.R.id.cut;
12095     static final int ID_COPY = android.R.id.copy;
12096     static final int ID_PASTE = android.R.id.paste;
12097     static final int ID_SHARE = android.R.id.shareText;
12098     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
12099     static final int ID_REPLACE = android.R.id.replaceText;
12100     static final int ID_ASSIST = android.R.id.textAssist;
12101     static final int ID_AUTOFILL = android.R.id.autofill;
12102 
12103     /**
12104      * Called when a context menu option for the text view is selected.  Currently
12105      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
12106      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
12107      *
12108      * @return true if the context menu item action was performed.
12109      */
onTextContextMenuItem(int id)12110     public boolean onTextContextMenuItem(int id) {
12111         int min = 0;
12112         int max = mText.length();
12113 
12114         if (isFocused()) {
12115             final int selStart = getSelectionStart();
12116             final int selEnd = getSelectionEnd();
12117 
12118             min = Math.max(0, Math.min(selStart, selEnd));
12119             max = Math.max(0, Math.max(selStart, selEnd));
12120         }
12121 
12122         switch (id) {
12123             case ID_SELECT_ALL:
12124                 final boolean hadSelection = hasSelection();
12125                 selectAllText();
12126                 if (mEditor != null && hadSelection) {
12127                     mEditor.invalidateActionModeAsync();
12128                 }
12129                 return true;
12130 
12131             case ID_UNDO:
12132                 if (mEditor != null) {
12133                     mEditor.undo();
12134                 }
12135                 return true;  // Returns true even if nothing was undone.
12136 
12137             case ID_REDO:
12138                 if (mEditor != null) {
12139                     mEditor.redo();
12140                 }
12141                 return true;  // Returns true even if nothing was undone.
12142 
12143             case ID_PASTE:
12144                 paste(min, max, true /* withFormatting */);
12145                 return true;
12146 
12147             case ID_PASTE_AS_PLAIN_TEXT:
12148                 paste(min, max, false /* withFormatting */);
12149                 return true;
12150 
12151             case ID_CUT:
12152                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
12153                 if (setPrimaryClip(cutData)) {
12154                     deleteText_internal(min, max);
12155                 } else {
12156                     Toast.makeText(getContext(),
12157                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12158                             Toast.LENGTH_SHORT).show();
12159                 }
12160                 return true;
12161 
12162             case ID_COPY:
12163                 // For link action mode in a non-selectable/non-focusable TextView,
12164                 // make sure that we set the appropriate min/max.
12165                 final int selStart = getSelectionStart();
12166                 final int selEnd = getSelectionEnd();
12167                 min = Math.max(0, Math.min(selStart, selEnd));
12168                 max = Math.max(0, Math.max(selStart, selEnd));
12169                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
12170                 if (setPrimaryClip(copyData)) {
12171                     stopTextActionMode();
12172                 } else {
12173                     Toast.makeText(getContext(),
12174                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12175                             Toast.LENGTH_SHORT).show();
12176                 }
12177                 return true;
12178 
12179             case ID_REPLACE:
12180                 if (mEditor != null) {
12181                     mEditor.replace();
12182                 }
12183                 return true;
12184 
12185             case ID_SHARE:
12186                 shareSelectedText();
12187                 return true;
12188 
12189             case ID_AUTOFILL:
12190                 requestAutofill();
12191                 stopTextActionMode();
12192                 return true;
12193         }
12194         return false;
12195     }
12196 
12197     @UnsupportedAppUsage
getTransformedText(int start, int end)12198     CharSequence getTransformedText(int start, int end) {
12199         return removeSuggestionSpans(mTransformed.subSequence(start, end));
12200     }
12201 
12202     @Override
performLongClick()12203     public boolean performLongClick() {
12204         boolean handled = false;
12205         boolean performedHapticFeedback = false;
12206 
12207         if (mEditor != null) {
12208             mEditor.mIsBeingLongClicked = true;
12209         }
12210 
12211         if (super.performLongClick()) {
12212             handled = true;
12213             performedHapticFeedback = true;
12214         }
12215 
12216         if (mEditor != null) {
12217             handled |= mEditor.performLongClick(handled);
12218             mEditor.mIsBeingLongClicked = false;
12219         }
12220 
12221         if (handled) {
12222             if (!performedHapticFeedback) {
12223               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
12224             }
12225             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
12226         } else {
12227             MetricsLogger.action(
12228                     mContext,
12229                     MetricsEvent.TEXT_LONGPRESS,
12230                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
12231         }
12232 
12233         return handled;
12234     }
12235 
12236     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12237     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
12238         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
12239         if (mEditor != null) {
12240             mEditor.onScrollChanged();
12241         }
12242     }
12243 
12244     /**
12245      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
12246      * by the IME or by the spell checker as the user types. This is done by adding
12247      * {@link SuggestionSpan}s to the text.
12248      *
12249      * When suggestions are enabled (default), this list of suggestions will be displayed when the
12250      * user asks for them on these parts of the text. This value depends on the inputType of this
12251      * TextView.
12252      *
12253      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
12254      *
12255      * In addition, the type variation must be one of
12256      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
12257      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
12258      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
12259      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
12260      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
12261      *
12262      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
12263      *
12264      * @return true if the suggestions popup window is enabled, based on the inputType.
12265      */
isSuggestionsEnabled()12266     public boolean isSuggestionsEnabled() {
12267         if (mEditor == null) return false;
12268         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
12269             return false;
12270         }
12271         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
12272 
12273         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
12274         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
12275                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
12276                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
12277                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
12278                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
12279     }
12280 
12281     /**
12282      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12283      * selection is initiated in this View.
12284      *
12285      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
12286      * Paste, Replace and Share actions, depending on what this View supports.
12287      *
12288      * <p>A custom implementation can add new entries in the default menu in its
12289      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
12290      * method. The default actions can also be removed from the menu using
12291      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12292      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
12293      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
12294      *
12295      * <p>Returning false from
12296      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
12297      * will prevent the action mode from being started.
12298      *
12299      * <p>Action click events should be handled by the custom implementation of
12300      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
12301      * android.view.MenuItem)}.
12302      *
12303      * <p>Note that text selection mode is not started when a TextView receives focus and the
12304      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
12305      * that case, to allow for quick replacement.
12306      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12307     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
12308         createEditorIfNeeded();
12309         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
12310     }
12311 
12312     /**
12313      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
12314      *
12315      * @return The current custom selection callback.
12316      */
getCustomSelectionActionModeCallback()12317     public ActionMode.Callback getCustomSelectionActionModeCallback() {
12318         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
12319     }
12320 
12321     /**
12322      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12323      * insertion is initiated in this View.
12324      * The standard implementation populates the menu with a subset of Select All,
12325      * Paste and Replace actions, depending on what this View supports.
12326      *
12327      * <p>A custom implementation can add new entries in the default menu in its
12328      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
12329      * android.view.Menu)} method. The default actions can also be removed from the menu using
12330      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12331      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
12332      *
12333      * <p>Returning false from
12334      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
12335      * android.view.Menu)} will prevent the action mode from being started.</p>
12336      *
12337      * <p>Action click events should be handled by the custom implementation of
12338      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
12339      * android.view.MenuItem)}.</p>
12340      *
12341      * <p>Note that text insertion mode is not started when a TextView receives focus and the
12342      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
12343      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12344     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
12345         createEditorIfNeeded();
12346         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
12347     }
12348 
12349     /**
12350      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
12351      *
12352      * @return The current custom insertion callback.
12353      */
getCustomInsertionActionModeCallback()12354     public ActionMode.Callback getCustomInsertionActionModeCallback() {
12355         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
12356     }
12357 
12358     /**
12359      * Sets the {@link TextClassifier} for this TextView.
12360      */
setTextClassifier(@ullable TextClassifier textClassifier)12361     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
12362         mTextClassifier = textClassifier;
12363     }
12364 
12365     /**
12366      * Returns the {@link TextClassifier} used by this TextView.
12367      * If no TextClassifier has been set, this TextView uses the default set by the
12368      * {@link TextClassificationManager}.
12369      */
12370     @NonNull
getTextClassifier()12371     public TextClassifier getTextClassifier() {
12372         if (mTextClassifier == null) {
12373             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12374             if (tcm != null) {
12375                 return tcm.getTextClassifier();
12376             }
12377             return TextClassifier.NO_OP;
12378         }
12379         return mTextClassifier;
12380     }
12381 
12382     /**
12383      * Returns a session-aware text classifier.
12384      * This method creates one if none already exists or the current one is destroyed.
12385      */
12386     @NonNull
getTextClassificationSession()12387     TextClassifier getTextClassificationSession() {
12388         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
12389             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12390             if (tcm != null) {
12391                 final String widgetType;
12392                 if (isTextEditable()) {
12393                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
12394                 } else if (isTextSelectable()) {
12395                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
12396                 } else {
12397                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
12398                 }
12399                 mTextClassificationContext = new TextClassificationContext.Builder(
12400                         mContext.getPackageName(), widgetType)
12401                         .build();
12402                 if (mTextClassifier != null) {
12403                     mTextClassificationSession = tcm.createTextClassificationSession(
12404                             mTextClassificationContext, mTextClassifier);
12405                 } else {
12406                     mTextClassificationSession = tcm.createTextClassificationSession(
12407                             mTextClassificationContext);
12408                 }
12409             } else {
12410                 mTextClassificationSession = TextClassifier.NO_OP;
12411             }
12412         }
12413         return mTextClassificationSession;
12414     }
12415 
12416     /**
12417      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
12418      * @see #getTextClassificationSession()
12419      */
12420     @Nullable
getTextClassificationContext()12421     TextClassificationContext getTextClassificationContext() {
12422         return mTextClassificationContext;
12423     }
12424 
12425     /**
12426      * Returns true if this TextView uses a no-op TextClassifier.
12427      */
usesNoOpTextClassifier()12428     boolean usesNoOpTextClassifier() {
12429         return getTextClassifier() == TextClassifier.NO_OP;
12430     }
12431 
12432 
12433     /**
12434      * Starts an ActionMode for the specified TextLinkSpan.
12435      *
12436      * @return Whether or not we're attempting to start the action mode.
12437      * @hide
12438      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12439     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12440         Preconditions.checkNotNull(clickedSpan);
12441 
12442         if (!(mText instanceof Spanned)) {
12443             return false;
12444         }
12445 
12446         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
12447         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
12448 
12449         if (start < 0 || end > mText.length() || start >= end) {
12450             return false;
12451         }
12452 
12453         createEditorIfNeeded();
12454         mEditor.startLinkActionModeAsync(start, end);
12455         return true;
12456     }
12457 
12458     /**
12459      * Handles a click on the specified TextLinkSpan.
12460      *
12461      * @return Whether or not the click is being handled.
12462      * @hide
12463      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12464     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12465         Preconditions.checkNotNull(clickedSpan);
12466         if (mText instanceof Spanned) {
12467             final Spanned spanned = (Spanned) mText;
12468             final int start = spanned.getSpanStart(clickedSpan);
12469             final int end = spanned.getSpanEnd(clickedSpan);
12470             if (start >= 0 && end <= mText.length() && start < end) {
12471                 final TextClassification.Request request = new TextClassification.Request.Builder(
12472                         mText, start, end)
12473                         .setDefaultLocales(getTextLocales())
12474                         .build();
12475                 final Supplier<TextClassification> supplier = () ->
12476                         getTextClassifier().classifyText(request);
12477                 final Consumer<TextClassification> consumer = classification -> {
12478                     if (classification != null) {
12479                         if (!classification.getActions().isEmpty()) {
12480                             try {
12481                                 classification.getActions().get(0).getActionIntent().send();
12482                             } catch (PendingIntent.CanceledException e) {
12483                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
12484                             }
12485                         } else {
12486                             Log.d(LOG_TAG, "No link action to perform");
12487                         }
12488                     } else {
12489                         // classification == null
12490                         Log.d(LOG_TAG, "Timeout while classifying text");
12491                     }
12492                 };
12493                 CompletableFuture.supplyAsync(supplier)
12494                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
12495                         .thenAccept(consumer);
12496                 return true;
12497             }
12498         }
12499         return false;
12500     }
12501 
12502     /**
12503      * @hide
12504      */
12505     @UnsupportedAppUsage
stopTextActionMode()12506     protected void stopTextActionMode() {
12507         if (mEditor != null) {
12508             mEditor.stopTextActionMode();
12509         }
12510     }
12511 
12512     /** @hide */
hideFloatingToolbar(int durationMs)12513     public void hideFloatingToolbar(int durationMs) {
12514         if (mEditor != null) {
12515             mEditor.hideFloatingToolbar(durationMs);
12516         }
12517     }
12518 
canUndo()12519     boolean canUndo() {
12520         return mEditor != null && mEditor.canUndo();
12521     }
12522 
canRedo()12523     boolean canRedo() {
12524         return mEditor != null && mEditor.canRedo();
12525     }
12526 
canCut()12527     boolean canCut() {
12528         if (hasPasswordTransformationMethod()) {
12529             return false;
12530         }
12531 
12532         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
12533                 && mEditor.mKeyListener != null) {
12534             return true;
12535         }
12536 
12537         return false;
12538     }
12539 
canCopy()12540     boolean canCopy() {
12541         if (hasPasswordTransformationMethod()) {
12542             return false;
12543         }
12544 
12545         if (mText.length() > 0 && hasSelection() && mEditor != null) {
12546             return true;
12547         }
12548 
12549         return false;
12550     }
12551 
canShare()12552     boolean canShare() {
12553         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
12554             return false;
12555         }
12556         return canCopy();
12557     }
12558 
isDeviceProvisioned()12559     boolean isDeviceProvisioned() {
12560         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
12561             mDeviceProvisionedState = Settings.Global.getInt(
12562                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
12563                     ? DEVICE_PROVISIONED_YES
12564                     : DEVICE_PROVISIONED_NO;
12565         }
12566         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
12567     }
12568 
12569     @UnsupportedAppUsage
canPaste()12570     boolean canPaste() {
12571         return (mText instanceof Editable
12572                 && mEditor != null && mEditor.mKeyListener != null
12573                 && getSelectionStart() >= 0
12574                 && getSelectionEnd() >= 0
12575                 && getClipboardManagerForUser().hasPrimaryClip());
12576     }
12577 
canPasteAsPlainText()12578     boolean canPasteAsPlainText() {
12579         if (!canPaste()) {
12580             return false;
12581         }
12582 
12583         final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
12584         final ClipDescription description = clipData.getDescription();
12585         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
12586         final CharSequence text = clipData.getItemAt(0).getText();
12587         if (isPlainType && (text instanceof Spanned)) {
12588             Spanned spanned = (Spanned) text;
12589             if (TextUtils.hasStyleSpan(spanned)) {
12590                 return true;
12591             }
12592         }
12593         return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
12594     }
12595 
canProcessText()12596     boolean canProcessText() {
12597         if (getId() == View.NO_ID) {
12598             return false;
12599         }
12600         return canShare();
12601     }
12602 
canSelectAllText()12603     boolean canSelectAllText() {
12604         return canSelectText() && !hasPasswordTransformationMethod()
12605                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
12606     }
12607 
selectAllText()12608     boolean selectAllText() {
12609         if (mEditor != null) {
12610             // Hide the toolbar before changing the selection to avoid flickering.
12611             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
12612         }
12613         final int length = mText.length();
12614         Selection.setSelection(mSpannable, 0, length);
12615         return length > 0;
12616     }
12617 
replaceSelectionWithText(CharSequence text)12618     void replaceSelectionWithText(CharSequence text) {
12619         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
12620     }
12621 
12622     /**
12623      * Paste clipboard content between min and max positions.
12624      */
paste(int min, int max, boolean withFormatting)12625     private void paste(int min, int max, boolean withFormatting) {
12626         ClipboardManager clipboard = getClipboardManagerForUser();
12627         ClipData clip = clipboard.getPrimaryClip();
12628         if (clip != null) {
12629             boolean didFirst = false;
12630             for (int i = 0; i < clip.getItemCount(); i++) {
12631                 final CharSequence paste;
12632                 if (withFormatting) {
12633                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
12634                 } else {
12635                     // Get an item as text and remove all spans by toString().
12636                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
12637                     paste = (text instanceof Spanned) ? text.toString() : text;
12638                 }
12639                 if (paste != null) {
12640                     if (!didFirst) {
12641                         Selection.setSelection(mSpannable, max);
12642                         ((Editable) mText).replace(min, max, paste);
12643                         didFirst = true;
12644                     } else {
12645                         ((Editable) mText).insert(getSelectionEnd(), "\n");
12646                         ((Editable) mText).insert(getSelectionEnd(), paste);
12647                     }
12648                 }
12649             }
12650             sLastCutCopyOrTextChangedTime = 0;
12651         }
12652     }
12653 
shareSelectedText()12654     private void shareSelectedText() {
12655         String selectedText = getSelectedText();
12656         if (selectedText != null && !selectedText.isEmpty()) {
12657             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
12658             sharingIntent.setType("text/plain");
12659             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
12660             selectedText = TextUtils.trimToParcelableSize(selectedText);
12661             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
12662             getContext().startActivity(Intent.createChooser(sharingIntent, null));
12663             Selection.setSelection(mSpannable, getSelectionEnd());
12664         }
12665     }
12666 
12667     @CheckResult
setPrimaryClip(ClipData clip)12668     private boolean setPrimaryClip(ClipData clip) {
12669         ClipboardManager clipboard = getClipboardManagerForUser();
12670         try {
12671             clipboard.setPrimaryClip(clip);
12672         } catch (Throwable t) {
12673             return false;
12674         }
12675         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
12676         return true;
12677     }
12678 
12679     /**
12680      * Get the character offset closest to the specified absolute position. A typical use case is to
12681      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
12682      *
12683      * @param x The horizontal absolute position of a point on screen
12684      * @param y The vertical absolute position of a point on screen
12685      * @return the character offset for the character whose position is closest to the specified
12686      *  position. Returns -1 if there is no layout.
12687      */
getOffsetForPosition(float x, float y)12688     public int getOffsetForPosition(float x, float y) {
12689         if (getLayout() == null) return -1;
12690         final int line = getLineAtCoordinate(y);
12691         final int offset = getOffsetAtCoordinate(line, x);
12692         return offset;
12693     }
12694 
convertToLocalHorizontalCoordinate(float x)12695     float convertToLocalHorizontalCoordinate(float x) {
12696         x -= getTotalPaddingLeft();
12697         // Clamp the position to inside of the view.
12698         x = Math.max(0.0f, x);
12699         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
12700         x += getScrollX();
12701         return x;
12702     }
12703 
12704     @UnsupportedAppUsage
getLineAtCoordinate(float y)12705     int getLineAtCoordinate(float y) {
12706         y -= getTotalPaddingTop();
12707         // Clamp the position to inside of the view.
12708         y = Math.max(0.0f, y);
12709         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
12710         y += getScrollY();
12711         return getLayout().getLineForVertical((int) y);
12712     }
12713 
getLineAtCoordinateUnclamped(float y)12714     int getLineAtCoordinateUnclamped(float y) {
12715         y -= getTotalPaddingTop();
12716         y += getScrollY();
12717         return getLayout().getLineForVertical((int) y);
12718     }
12719 
getOffsetAtCoordinate(int line, float x)12720     int getOffsetAtCoordinate(int line, float x) {
12721         x = convertToLocalHorizontalCoordinate(x);
12722         return getLayout().getOffsetForHorizontal(line, x);
12723     }
12724 
12725     @Override
onDragEvent(DragEvent event)12726     public boolean onDragEvent(DragEvent event) {
12727         switch (event.getAction()) {
12728             case DragEvent.ACTION_DRAG_STARTED:
12729                 return mEditor != null && mEditor.hasInsertionController();
12730 
12731             case DragEvent.ACTION_DRAG_ENTERED:
12732                 TextView.this.requestFocus();
12733                 return true;
12734 
12735             case DragEvent.ACTION_DRAG_LOCATION:
12736                 if (mText instanceof Spannable) {
12737                     final int offset = getOffsetForPosition(event.getX(), event.getY());
12738                     Selection.setSelection(mSpannable, offset);
12739                 }
12740                 return true;
12741 
12742             case DragEvent.ACTION_DROP:
12743                 if (mEditor != null) mEditor.onDrop(event);
12744                 return true;
12745 
12746             case DragEvent.ACTION_DRAG_ENDED:
12747             case DragEvent.ACTION_DRAG_EXITED:
12748             default:
12749                 return true;
12750         }
12751     }
12752 
isInBatchEditMode()12753     boolean isInBatchEditMode() {
12754         if (mEditor == null) return false;
12755         final Editor.InputMethodState ims = mEditor.mInputMethodState;
12756         if (ims != null) {
12757             return ims.mBatchEditNesting > 0;
12758         }
12759         return mEditor.mInBatchEditControllers;
12760     }
12761 
12762     @Override
onRtlPropertiesChanged(int layoutDirection)12763     public void onRtlPropertiesChanged(int layoutDirection) {
12764         super.onRtlPropertiesChanged(layoutDirection);
12765 
12766         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
12767         if (mTextDir != newTextDir) {
12768             mTextDir = newTextDir;
12769             if (mLayout != null) {
12770                 checkForRelayout();
12771             }
12772         }
12773     }
12774 
12775     /**
12776      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
12777      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
12778      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
12779      * return value may not be the same as the one TextView uses if the View's layout direction is
12780      * not resolved or detached from parent root view.
12781      */
getTextDirectionHeuristic()12782     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
12783         if (hasPasswordTransformationMethod()) {
12784             // passwords fields should be LTR
12785             return TextDirectionHeuristics.LTR;
12786         }
12787 
12788         if (mEditor != null
12789                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
12790                     == EditorInfo.TYPE_CLASS_PHONE) {
12791             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
12792             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
12793             // RTL digits.
12794             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
12795             final String zero = symbols.getDigitStrings()[0];
12796             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
12797             // direction.
12798             final int firstCodepoint = zero.codePointAt(0);
12799             final byte digitDirection = Character.getDirectionality(firstCodepoint);
12800             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
12801                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
12802                 return TextDirectionHeuristics.RTL;
12803             } else {
12804                 return TextDirectionHeuristics.LTR;
12805             }
12806         }
12807 
12808         // Always need to resolve layout direction first
12809         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
12810 
12811         // Now, we can select the heuristic
12812         switch (getTextDirection()) {
12813             default:
12814             case TEXT_DIRECTION_FIRST_STRONG:
12815                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
12816                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
12817             case TEXT_DIRECTION_ANY_RTL:
12818                 return TextDirectionHeuristics.ANYRTL_LTR;
12819             case TEXT_DIRECTION_LTR:
12820                 return TextDirectionHeuristics.LTR;
12821             case TEXT_DIRECTION_RTL:
12822                 return TextDirectionHeuristics.RTL;
12823             case TEXT_DIRECTION_LOCALE:
12824                 return TextDirectionHeuristics.LOCALE;
12825             case TEXT_DIRECTION_FIRST_STRONG_LTR:
12826                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
12827             case TEXT_DIRECTION_FIRST_STRONG_RTL:
12828                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
12829         }
12830     }
12831 
12832     /**
12833      * @hide
12834      */
12835     @Override
onResolveDrawables(int layoutDirection)12836     public void onResolveDrawables(int layoutDirection) {
12837         // No need to resolve twice
12838         if (mLastLayoutDirection == layoutDirection) {
12839             return;
12840         }
12841         mLastLayoutDirection = layoutDirection;
12842 
12843         // Resolve drawables
12844         if (mDrawables != null) {
12845             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
12846                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
12847                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
12848                 applyCompoundDrawableTint();
12849             }
12850         }
12851     }
12852 
12853     /**
12854      * Prepares a drawable for display by propagating layout direction and
12855      * drawable state.
12856      *
12857      * @param dr the drawable to prepare
12858      */
prepareDrawableForDisplay(@ullable Drawable dr)12859     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
12860         if (dr == null) {
12861             return;
12862         }
12863 
12864         dr.setLayoutDirection(getLayoutDirection());
12865 
12866         if (dr.isStateful()) {
12867             dr.setState(getDrawableState());
12868             dr.jumpToCurrentState();
12869         }
12870     }
12871 
12872     /**
12873      * @hide
12874      */
resetResolvedDrawables()12875     protected void resetResolvedDrawables() {
12876         super.resetResolvedDrawables();
12877         mLastLayoutDirection = -1;
12878     }
12879 
12880     /**
12881      * @hide
12882      */
viewClicked(InputMethodManager imm)12883     protected void viewClicked(InputMethodManager imm) {
12884         if (imm != null) {
12885             imm.viewClicked(this);
12886         }
12887     }
12888 
12889     /**
12890      * Deletes the range of text [start, end[.
12891      * @hide
12892      */
12893     @UnsupportedAppUsage
deleteText_internal(int start, int end)12894     protected void deleteText_internal(int start, int end) {
12895         ((Editable) mText).delete(start, end);
12896     }
12897 
12898     /**
12899      * Replaces the range of text [start, end[ by replacement text
12900      * @hide
12901      */
replaceText_internal(int start, int end, CharSequence text)12902     protected void replaceText_internal(int start, int end, CharSequence text) {
12903         ((Editable) mText).replace(start, end, text);
12904     }
12905 
12906     /**
12907      * Sets a span on the specified range of text
12908      * @hide
12909      */
setSpan_internal(Object span, int start, int end, int flags)12910     protected void setSpan_internal(Object span, int start, int end, int flags) {
12911         ((Editable) mText).setSpan(span, start, end, flags);
12912     }
12913 
12914     /**
12915      * Moves the cursor to the specified offset position in text
12916      * @hide
12917      */
setCursorPosition_internal(int start, int end)12918     protected void setCursorPosition_internal(int start, int end) {
12919         Selection.setSelection(((Editable) mText), start, end);
12920     }
12921 
12922     /**
12923      * An Editor should be created as soon as any of the editable-specific fields (grouped
12924      * inside the Editor object) is assigned to a non-default value.
12925      * This method will create the Editor if needed.
12926      *
12927      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
12928      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
12929      * Editor for backward compatibility, as soon as one of these fields is assigned.
12930      *
12931      * Also note that for performance reasons, the mEditor is created when needed, but not
12932      * reset when no more edit-specific fields are needed.
12933      */
12934     @UnsupportedAppUsage
createEditorIfNeeded()12935     private void createEditorIfNeeded() {
12936         if (mEditor == null) {
12937             mEditor = new Editor(this);
12938         }
12939     }
12940 
12941     /**
12942      * @hide
12943      */
12944     @Override
12945     @UnsupportedAppUsage
getIterableTextForAccessibility()12946     public CharSequence getIterableTextForAccessibility() {
12947         return mText;
12948     }
12949 
ensureIterableTextForAccessibilitySelectable()12950     private void ensureIterableTextForAccessibilitySelectable() {
12951         if (!(mText instanceof Spannable)) {
12952             setText(mText, BufferType.SPANNABLE);
12953         }
12954     }
12955 
12956     /**
12957      * @hide
12958      */
12959     @Override
getIteratorForGranularity(int granularity)12960     public TextSegmentIterator getIteratorForGranularity(int granularity) {
12961         switch (granularity) {
12962             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
12963                 Spannable text = (Spannable) getIterableTextForAccessibility();
12964                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
12965                     AccessibilityIterators.LineTextSegmentIterator iterator =
12966                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
12967                     iterator.initialize(text, getLayout());
12968                     return iterator;
12969                 }
12970             } break;
12971             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
12972                 Spannable text = (Spannable) getIterableTextForAccessibility();
12973                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
12974                     AccessibilityIterators.PageTextSegmentIterator iterator =
12975                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
12976                     iterator.initialize(this);
12977                     return iterator;
12978                 }
12979             } break;
12980         }
12981         return super.getIteratorForGranularity(granularity);
12982     }
12983 
12984     /**
12985      * @hide
12986      */
12987     @Override
getAccessibilitySelectionStart()12988     public int getAccessibilitySelectionStart() {
12989         return getSelectionStart();
12990     }
12991 
12992     /**
12993      * @hide
12994      */
isAccessibilitySelectionExtendable()12995     public boolean isAccessibilitySelectionExtendable() {
12996         return true;
12997     }
12998 
12999     /**
13000      * @hide
13001      */
13002     @Override
getAccessibilitySelectionEnd()13003     public int getAccessibilitySelectionEnd() {
13004         return getSelectionEnd();
13005     }
13006 
13007     /**
13008      * @hide
13009      */
13010     @Override
setAccessibilitySelection(int start, int end)13011     public void setAccessibilitySelection(int start, int end) {
13012         if (getAccessibilitySelectionStart() == start
13013                 && getAccessibilitySelectionEnd() == end) {
13014             return;
13015         }
13016         CharSequence text = getIterableTextForAccessibility();
13017         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
13018             Selection.setSelection((Spannable) text, start, end);
13019         } else {
13020             Selection.removeSelection((Spannable) text);
13021         }
13022         // Hide all selection controllers used for adjusting selection
13023         // since we are doing so explicitlty by other means and these
13024         // controllers interact with how selection behaves.
13025         if (mEditor != null) {
13026             mEditor.hideCursorAndSpanControllers();
13027             mEditor.stopTextActionMode();
13028         }
13029     }
13030 
13031     /** @hide */
13032     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)13033     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
13034         super.encodeProperties(stream);
13035 
13036         TruncateAt ellipsize = getEllipsize();
13037         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
13038         stream.addProperty("text:textSize", getTextSize());
13039         stream.addProperty("text:scaledTextSize", getScaledTextSize());
13040         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
13041         stream.addProperty("text:selectionStart", getSelectionStart());
13042         stream.addProperty("text:selectionEnd", getSelectionEnd());
13043         stream.addProperty("text:curTextColor", mCurTextColor);
13044         stream.addProperty("text:text", mText == null ? null : mText.toString());
13045         stream.addProperty("text:gravity", mGravity);
13046     }
13047 
13048     /**
13049      * User interface state that is stored by TextView for implementing
13050      * {@link View#onSaveInstanceState}.
13051      */
13052     public static class SavedState extends BaseSavedState {
13053         int selStart = -1;
13054         int selEnd = -1;
13055         @UnsupportedAppUsage
13056         CharSequence text;
13057         boolean frozenWithFocus;
13058         CharSequence error;
13059         ParcelableParcel editorState;  // Optional state from Editor.
13060 
SavedState(Parcelable superState)13061         SavedState(Parcelable superState) {
13062             super(superState);
13063         }
13064 
13065         @Override
writeToParcel(Parcel out, int flags)13066         public void writeToParcel(Parcel out, int flags) {
13067             super.writeToParcel(out, flags);
13068             out.writeInt(selStart);
13069             out.writeInt(selEnd);
13070             out.writeInt(frozenWithFocus ? 1 : 0);
13071             TextUtils.writeToParcel(text, out, flags);
13072 
13073             if (error == null) {
13074                 out.writeInt(0);
13075             } else {
13076                 out.writeInt(1);
13077                 TextUtils.writeToParcel(error, out, flags);
13078             }
13079 
13080             if (editorState == null) {
13081                 out.writeInt(0);
13082             } else {
13083                 out.writeInt(1);
13084                 editorState.writeToParcel(out, flags);
13085             }
13086         }
13087 
13088         @Override
toString()13089         public String toString() {
13090             String str = "TextView.SavedState{"
13091                     + Integer.toHexString(System.identityHashCode(this))
13092                     + " start=" + selStart + " end=" + selEnd;
13093             if (text != null) {
13094                 str += " text=" + text;
13095             }
13096             return str + "}";
13097         }
13098 
13099         @SuppressWarnings("hiding")
13100         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
13101                 new Parcelable.Creator<SavedState>() {
13102                     public SavedState createFromParcel(Parcel in) {
13103                         return new SavedState(in);
13104                     }
13105 
13106                     public SavedState[] newArray(int size) {
13107                         return new SavedState[size];
13108                     }
13109                 };
13110 
SavedState(Parcel in)13111         private SavedState(Parcel in) {
13112             super(in);
13113             selStart = in.readInt();
13114             selEnd = in.readInt();
13115             frozenWithFocus = (in.readInt() != 0);
13116             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13117 
13118             if (in.readInt() != 0) {
13119                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13120             }
13121 
13122             if (in.readInt() != 0) {
13123                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
13124             }
13125         }
13126     }
13127 
13128     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
13129         private char[] mChars;
13130         private int mStart, mLength;
13131 
CharWrapper(char[] chars, int start, int len)13132         public CharWrapper(char[] chars, int start, int len) {
13133             mChars = chars;
13134             mStart = start;
13135             mLength = len;
13136         }
13137 
set(char[] chars, int start, int len)13138         /* package */ void set(char[] chars, int start, int len) {
13139             mChars = chars;
13140             mStart = start;
13141             mLength = len;
13142         }
13143 
length()13144         public int length() {
13145             return mLength;
13146         }
13147 
charAt(int off)13148         public char charAt(int off) {
13149             return mChars[off + mStart];
13150         }
13151 
13152         @Override
toString()13153         public String toString() {
13154             return new String(mChars, mStart, mLength);
13155         }
13156 
subSequence(int start, int end)13157         public CharSequence subSequence(int start, int end) {
13158             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13159                 throw new IndexOutOfBoundsException(start + ", " + end);
13160             }
13161 
13162             return new String(mChars, start + mStart, end - start);
13163         }
13164 
getChars(int start, int end, char[] buf, int off)13165         public void getChars(int start, int end, char[] buf, int off) {
13166             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13167                 throw new IndexOutOfBoundsException(start + ", " + end);
13168             }
13169 
13170             System.arraycopy(mChars, start + mStart, buf, off, end - start);
13171         }
13172 
13173         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13174         public void drawText(BaseCanvas c, int start, int end,
13175                              float x, float y, Paint p) {
13176             c.drawText(mChars, start + mStart, end - start, x, y, p);
13177         }
13178 
13179         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13180         public void drawTextRun(BaseCanvas c, int start, int end,
13181                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
13182             int count = end - start;
13183             int contextCount = contextEnd - contextStart;
13184             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
13185                     contextCount, x, y, isRtl, p);
13186         }
13187 
measureText(int start, int end, Paint p)13188         public float measureText(int start, int end, Paint p) {
13189             return p.measureText(mChars, start + mStart, end - start);
13190         }
13191 
getTextWidths(int start, int end, float[] widths, Paint p)13192         public int getTextWidths(int start, int end, float[] widths, Paint p) {
13193             return p.getTextWidths(mChars, start + mStart, end - start, widths);
13194         }
13195 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13196         public float getTextRunAdvances(int start, int end, int contextStart,
13197                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
13198                 Paint p) {
13199             int count = end - start;
13200             int contextCount = contextEnd - contextStart;
13201             return p.getTextRunAdvances(mChars, start + mStart, count,
13202                     contextStart + mStart, contextCount, isRtl, advances,
13203                     advancesIndex);
13204         }
13205 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13206         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
13207                 int offset, int cursorOpt, Paint p) {
13208             int contextCount = contextEnd - contextStart;
13209             return p.getTextRunCursor(mChars, contextStart + mStart,
13210                     contextCount, isRtl, offset + mStart, cursorOpt);
13211         }
13212     }
13213 
13214     private static final class Marquee {
13215         // TODO: Add an option to configure this
13216         private static final float MARQUEE_DELTA_MAX = 0.07f;
13217         private static final int MARQUEE_DELAY = 1200;
13218         private static final int MARQUEE_DP_PER_SECOND = 30;
13219 
13220         private static final byte MARQUEE_STOPPED = 0x0;
13221         private static final byte MARQUEE_STARTING = 0x1;
13222         private static final byte MARQUEE_RUNNING = 0x2;
13223 
13224         private final WeakReference<TextView> mView;
13225         private final Choreographer mChoreographer;
13226 
13227         private byte mStatus = MARQUEE_STOPPED;
13228         private final float mPixelsPerMs;
13229         private float mMaxScroll;
13230         private float mMaxFadeScroll;
13231         private float mGhostStart;
13232         private float mGhostOffset;
13233         private float mFadeStop;
13234         private int mRepeatLimit;
13235 
13236         private float mScroll;
13237         private long mLastAnimationMs;
13238 
Marquee(TextView v)13239         Marquee(TextView v) {
13240             final float density = v.getContext().getResources().getDisplayMetrics().density;
13241             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
13242             mView = new WeakReference<TextView>(v);
13243             mChoreographer = Choreographer.getInstance();
13244         }
13245 
13246         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
13247             @Override
13248             public void doFrame(long frameTimeNanos) {
13249                 tick();
13250             }
13251         };
13252 
13253         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
13254             @Override
13255             public void doFrame(long frameTimeNanos) {
13256                 mStatus = MARQUEE_RUNNING;
13257                 mLastAnimationMs = mChoreographer.getFrameTime();
13258                 tick();
13259             }
13260         };
13261 
13262         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
13263             @Override
13264             public void doFrame(long frameTimeNanos) {
13265                 if (mStatus == MARQUEE_RUNNING) {
13266                     if (mRepeatLimit >= 0) {
13267                         mRepeatLimit--;
13268                     }
13269                     start(mRepeatLimit);
13270                 }
13271             }
13272         };
13273 
tick()13274         void tick() {
13275             if (mStatus != MARQUEE_RUNNING) {
13276                 return;
13277             }
13278 
13279             mChoreographer.removeFrameCallback(mTickCallback);
13280 
13281             final TextView textView = mView.get();
13282             if (textView != null && (textView.isFocused() || textView.isSelected())) {
13283                 long currentMs = mChoreographer.getFrameTime();
13284                 long deltaMs = currentMs - mLastAnimationMs;
13285                 mLastAnimationMs = currentMs;
13286                 float deltaPx = deltaMs * mPixelsPerMs;
13287                 mScroll += deltaPx;
13288                 if (mScroll > mMaxScroll) {
13289                     mScroll = mMaxScroll;
13290                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
13291                 } else {
13292                     mChoreographer.postFrameCallback(mTickCallback);
13293                 }
13294                 textView.invalidate();
13295             }
13296         }
13297 
stop()13298         void stop() {
13299             mStatus = MARQUEE_STOPPED;
13300             mChoreographer.removeFrameCallback(mStartCallback);
13301             mChoreographer.removeFrameCallback(mRestartCallback);
13302             mChoreographer.removeFrameCallback(mTickCallback);
13303             resetScroll();
13304         }
13305 
resetScroll()13306         private void resetScroll() {
13307             mScroll = 0.0f;
13308             final TextView textView = mView.get();
13309             if (textView != null) textView.invalidate();
13310         }
13311 
start(int repeatLimit)13312         void start(int repeatLimit) {
13313             if (repeatLimit == 0) {
13314                 stop();
13315                 return;
13316             }
13317             mRepeatLimit = repeatLimit;
13318             final TextView textView = mView.get();
13319             if (textView != null && textView.mLayout != null) {
13320                 mStatus = MARQUEE_STARTING;
13321                 mScroll = 0.0f;
13322                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
13323                         - textView.getCompoundPaddingRight();
13324                 final float lineWidth = textView.mLayout.getLineWidth(0);
13325                 final float gap = textWidth / 3.0f;
13326                 mGhostStart = lineWidth - textWidth + gap;
13327                 mMaxScroll = mGhostStart + textWidth;
13328                 mGhostOffset = lineWidth + gap;
13329                 mFadeStop = lineWidth + textWidth / 6.0f;
13330                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
13331 
13332                 textView.invalidate();
13333                 mChoreographer.postFrameCallback(mStartCallback);
13334             }
13335         }
13336 
getGhostOffset()13337         float getGhostOffset() {
13338             return mGhostOffset;
13339         }
13340 
getScroll()13341         float getScroll() {
13342             return mScroll;
13343         }
13344 
getMaxFadeScroll()13345         float getMaxFadeScroll() {
13346             return mMaxFadeScroll;
13347         }
13348 
shouldDrawLeftFade()13349         boolean shouldDrawLeftFade() {
13350             return mScroll <= mFadeStop;
13351         }
13352 
shouldDrawGhost()13353         boolean shouldDrawGhost() {
13354             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
13355         }
13356 
isRunning()13357         boolean isRunning() {
13358             return mStatus == MARQUEE_RUNNING;
13359         }
13360 
isStopped()13361         boolean isStopped() {
13362             return mStatus == MARQUEE_STOPPED;
13363         }
13364     }
13365 
13366     private class ChangeWatcher implements TextWatcher, SpanWatcher {
13367 
13368         private CharSequence mBeforeText;
13369 
beforeTextChanged(CharSequence buffer, int start, int before, int after)13370         public void beforeTextChanged(CharSequence buffer, int start,
13371                                       int before, int after) {
13372             if (DEBUG_EXTRACT) {
13373                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
13374                         + " before=" + before + " after=" + after + ": " + buffer);
13375             }
13376 
13377             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
13378                 mBeforeText = mTransformed.toString();
13379             }
13380 
13381             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
13382         }
13383 
onTextChanged(CharSequence buffer, int start, int before, int after)13384         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
13385             if (DEBUG_EXTRACT) {
13386                 Log.v(LOG_TAG, "onTextChanged start=" + start
13387                         + " before=" + before + " after=" + after + ": " + buffer);
13388             }
13389             TextView.this.handleTextChanged(buffer, start, before, after);
13390 
13391             if (AccessibilityManager.getInstance(mContext).isEnabled()
13392                     && (isFocused() || isSelected() && isShown())) {
13393                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
13394                 mBeforeText = null;
13395             }
13396         }
13397 
afterTextChanged(Editable buffer)13398         public void afterTextChanged(Editable buffer) {
13399             if (DEBUG_EXTRACT) {
13400                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
13401             }
13402             TextView.this.sendAfterTextChanged(buffer);
13403 
13404             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
13405                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
13406             }
13407         }
13408 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13409         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
13410             if (DEBUG_EXTRACT) {
13411                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
13412                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
13413             }
13414             TextView.this.spanChange(buf, what, s, st, e, en);
13415         }
13416 
onSpanAdded(Spannable buf, Object what, int s, int e)13417         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
13418             if (DEBUG_EXTRACT) {
13419                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
13420             }
13421             TextView.this.spanChange(buf, what, -1, s, -1, e);
13422         }
13423 
onSpanRemoved(Spannable buf, Object what, int s, int e)13424         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
13425             if (DEBUG_EXTRACT) {
13426                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
13427             }
13428             TextView.this.spanChange(buf, what, s, -1, e, -1);
13429         }
13430     }
13431 }
13432