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 
isPasswordInputType(int inputType)6580     static boolean isPasswordInputType(int inputType) {
6581         final int variation =
6582                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6583         return variation
6584                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
6585                 || variation
6586                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
6587                 || variation
6588                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
6589     }
6590 
isVisiblePasswordInputType(int inputType)6591     private static boolean isVisiblePasswordInputType(int inputType) {
6592         final int variation =
6593                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6594         return variation
6595                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
6596     }
6597 
6598     /**
6599      * Directly change the content type integer of the text view, without
6600      * modifying any other state.
6601      * @see #setInputType(int)
6602      * @see android.text.InputType
6603      * @attr ref android.R.styleable#TextView_inputType
6604      */
setRawInputType(int type)6605     public void setRawInputType(int type) {
6606         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
6607         createEditorIfNeeded();
6608         mEditor.mInputType = type;
6609     }
6610 
6611     /**
6612      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
6613      *         a {@code Locale} object that can be used to customize key various listeners.
6614      * @see DateKeyListener#getInstance(Locale)
6615      * @see DateTimeKeyListener#getInstance(Locale)
6616      * @see DigitsKeyListener#getInstance(Locale)
6617      * @see TimeKeyListener#getInstance(Locale)
6618      */
6619     @Nullable
getCustomLocaleForKeyListenerOrNull()6620     private Locale getCustomLocaleForKeyListenerOrNull() {
6621         if (!mUseInternationalizedInput) {
6622             // If the application does not target O, stick to the previous behavior.
6623             return null;
6624         }
6625         final LocaleList locales = getImeHintLocales();
6626         if (locales == null) {
6627             // If the application does not explicitly specify IME hint locale, also stick to the
6628             // previous behavior.
6629             return null;
6630         }
6631         return locales.get(0);
6632     }
6633 
6634     @UnsupportedAppUsage
setInputType(int type, boolean direct)6635     private void setInputType(int type, boolean direct) {
6636         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
6637         KeyListener input;
6638         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
6639             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
6640             TextKeyListener.Capitalize cap;
6641             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
6642                 cap = TextKeyListener.Capitalize.CHARACTERS;
6643             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
6644                 cap = TextKeyListener.Capitalize.WORDS;
6645             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
6646                 cap = TextKeyListener.Capitalize.SENTENCES;
6647             } else {
6648                 cap = TextKeyListener.Capitalize.NONE;
6649             }
6650             input = TextKeyListener.getInstance(autotext, cap);
6651         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
6652             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6653             input = DigitsKeyListener.getInstance(
6654                     locale,
6655                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
6656                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
6657             if (locale != null) {
6658                 // Override type, if necessary for i18n.
6659                 int newType = input.getInputType();
6660                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
6661                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
6662                     // The class is different from the original class. So we need to override
6663                     // 'type'. But we want to keep the password flag if it's there.
6664                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
6665                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
6666                     }
6667                     type = newType;
6668                 }
6669             }
6670         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
6671             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6672             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
6673                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
6674                     input = DateKeyListener.getInstance(locale);
6675                     break;
6676                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
6677                     input = TimeKeyListener.getInstance(locale);
6678                     break;
6679                 default:
6680                     input = DateTimeKeyListener.getInstance(locale);
6681                     break;
6682             }
6683             if (mUseInternationalizedInput) {
6684                 type = input.getInputType(); // Override type, if necessary for i18n.
6685             }
6686         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
6687             input = DialerKeyListener.getInstance();
6688         } else {
6689             input = TextKeyListener.getInstance();
6690         }
6691         setRawInputType(type);
6692         mListenerChanged = false;
6693         if (direct) {
6694             createEditorIfNeeded();
6695             mEditor.mKeyListener = input;
6696         } else {
6697             setKeyListenerOnly(input);
6698         }
6699     }
6700 
6701     /**
6702      * Get the type of the editable content.
6703      *
6704      * @see #setInputType(int)
6705      * @see android.text.InputType
6706      */
6707     @InspectableProperty(flagMapping = {
6708             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
6709             @FlagEntry(
6710                     name = "text",
6711                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6712                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
6713             @FlagEntry(
6714                     name = "textUri",
6715                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6716                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
6717             @FlagEntry(
6718                     name = "textEmailAddress",
6719                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6720                     target = InputType.TYPE_CLASS_TEXT
6721                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
6722             @FlagEntry(
6723                     name = "textEmailSubject",
6724                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6725                     target = InputType.TYPE_CLASS_TEXT
6726                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
6727             @FlagEntry(
6728                     name = "textShortMessage",
6729                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6730                     target = InputType.TYPE_CLASS_TEXT
6731                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
6732             @FlagEntry(
6733                     name = "textLongMessage",
6734                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6735                     target = InputType.TYPE_CLASS_TEXT
6736                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
6737             @FlagEntry(
6738                     name = "textPersonName",
6739                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6740                     target = InputType.TYPE_CLASS_TEXT
6741                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
6742             @FlagEntry(
6743                     name = "textPostalAddress",
6744                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6745                     target = InputType.TYPE_CLASS_TEXT
6746                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
6747             @FlagEntry(
6748                     name = "textPassword",
6749                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6750                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
6751             @FlagEntry(
6752                     name = "textVisiblePassword",
6753                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6754                     target = InputType.TYPE_CLASS_TEXT
6755                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
6756             @FlagEntry(
6757                     name = "textWebEditText",
6758                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6759                     target = InputType.TYPE_CLASS_TEXT
6760                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
6761             @FlagEntry(
6762                     name = "textFilter",
6763                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6764                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
6765             @FlagEntry(
6766                     name = "textPhonetic",
6767                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6768                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
6769             @FlagEntry(
6770                     name = "textWebEmailAddress",
6771                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6772                     target = InputType.TYPE_CLASS_TEXT
6773                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
6774             @FlagEntry(
6775                     name = "textWebPassword",
6776                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6777                     target = InputType.TYPE_CLASS_TEXT
6778                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
6779             @FlagEntry(
6780                     name = "number",
6781                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6782                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
6783             @FlagEntry(
6784                     name = "numberPassword",
6785                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6786                     target = InputType.TYPE_CLASS_NUMBER
6787                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
6788             @FlagEntry(
6789                     name = "phone",
6790                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6791                     target = InputType.TYPE_CLASS_PHONE),
6792             @FlagEntry(
6793                     name = "datetime",
6794                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6795                     target = InputType.TYPE_CLASS_DATETIME
6796                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
6797             @FlagEntry(
6798                     name = "date",
6799                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6800                     target = InputType.TYPE_CLASS_DATETIME
6801                             | InputType.TYPE_DATETIME_VARIATION_DATE),
6802             @FlagEntry(
6803                     name = "time",
6804                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6805                     target = InputType.TYPE_CLASS_DATETIME
6806                             | InputType.TYPE_DATETIME_VARIATION_TIME),
6807             @FlagEntry(
6808                     name = "textCapCharacters",
6809                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6810                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
6811             @FlagEntry(
6812                     name = "textCapWords",
6813                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6814                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
6815             @FlagEntry(
6816                     name = "textCapSentences",
6817                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6818                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
6819             @FlagEntry(
6820                     name = "textAutoCorrect",
6821                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6822                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
6823             @FlagEntry(
6824                     name = "textAutoComplete",
6825                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6826                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
6827             @FlagEntry(
6828                     name = "textMultiLine",
6829                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6830                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
6831             @FlagEntry(
6832                     name = "textImeMultiLine",
6833                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6834                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
6835             @FlagEntry(
6836                     name = "textNoSuggestions",
6837                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6838                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
6839             @FlagEntry(
6840                     name = "numberSigned",
6841                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6842                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
6843             @FlagEntry(
6844                     name = "numberDecimal",
6845                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6846                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
6847     })
getInputType()6848     public int getInputType() {
6849         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
6850     }
6851 
6852     /**
6853      * Change the editor type integer associated with the text view, which
6854      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
6855      * when it has focus.
6856      * @see #getImeOptions
6857      * @see android.view.inputmethod.EditorInfo
6858      * @attr ref android.R.styleable#TextView_imeOptions
6859      */
setImeOptions(int imeOptions)6860     public void setImeOptions(int imeOptions) {
6861         createEditorIfNeeded();
6862         mEditor.createInputContentTypeIfNeeded();
6863         mEditor.mInputContentType.imeOptions = imeOptions;
6864     }
6865 
6866     /**
6867      * Get the type of the Input Method Editor (IME).
6868      * @return the type of the IME
6869      * @see #setImeOptions(int)
6870      * @see EditorInfo
6871      */
6872     @InspectableProperty(flagMapping = {
6873             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
6874             @FlagEntry(
6875                     name = "actionUnspecified",
6876                     mask = EditorInfo.IME_MASK_ACTION,
6877                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
6878             @FlagEntry(
6879                     name = "actionNone",
6880                     mask = EditorInfo.IME_MASK_ACTION,
6881                     target = EditorInfo.IME_ACTION_NONE),
6882             @FlagEntry(
6883                     name = "actionGo",
6884                     mask = EditorInfo.IME_MASK_ACTION,
6885                     target = EditorInfo.IME_ACTION_GO),
6886             @FlagEntry(
6887                     name = "actionSearch",
6888                     mask = EditorInfo.IME_MASK_ACTION,
6889                     target = EditorInfo.IME_ACTION_SEARCH),
6890             @FlagEntry(
6891                     name = "actionSend",
6892                     mask = EditorInfo.IME_MASK_ACTION,
6893                     target = EditorInfo.IME_ACTION_SEND),
6894             @FlagEntry(
6895                     name = "actionNext",
6896                     mask = EditorInfo.IME_MASK_ACTION,
6897                     target = EditorInfo.IME_ACTION_NEXT),
6898             @FlagEntry(
6899                     name = "actionDone",
6900                     mask = EditorInfo.IME_MASK_ACTION,
6901                     target = EditorInfo.IME_ACTION_DONE),
6902             @FlagEntry(
6903                     name = "actionPrevious",
6904                     mask = EditorInfo.IME_MASK_ACTION,
6905                     target = EditorInfo.IME_ACTION_PREVIOUS),
6906             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
6907             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
6908             @FlagEntry(
6909                     name = "flagNavigatePrevious",
6910                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
6911             @FlagEntry(
6912                     name = "flagNoAccessoryAction",
6913                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
6914             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
6915             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
6916             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
6917             @FlagEntry(
6918                     name = "flagNoPersonalizedLearning",
6919                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
6920     })
getImeOptions()6921     public int getImeOptions() {
6922         return mEditor != null && mEditor.mInputContentType != null
6923                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
6924     }
6925 
6926     /**
6927      * Change the custom IME action associated with the text view, which
6928      * will be reported to an IME with {@link EditorInfo#actionLabel}
6929      * and {@link EditorInfo#actionId} when it has focus.
6930      * @see #getImeActionLabel
6931      * @see #getImeActionId
6932      * @see android.view.inputmethod.EditorInfo
6933      * @attr ref android.R.styleable#TextView_imeActionLabel
6934      * @attr ref android.R.styleable#TextView_imeActionId
6935      */
setImeActionLabel(CharSequence label, int actionId)6936     public void setImeActionLabel(CharSequence label, int actionId) {
6937         createEditorIfNeeded();
6938         mEditor.createInputContentTypeIfNeeded();
6939         mEditor.mInputContentType.imeActionLabel = label;
6940         mEditor.mInputContentType.imeActionId = actionId;
6941     }
6942 
6943     /**
6944      * Get the IME action label previous set with {@link #setImeActionLabel}.
6945      *
6946      * @see #setImeActionLabel
6947      * @see android.view.inputmethod.EditorInfo
6948      */
6949     @InspectableProperty
getImeActionLabel()6950     public CharSequence getImeActionLabel() {
6951         return mEditor != null && mEditor.mInputContentType != null
6952                 ? mEditor.mInputContentType.imeActionLabel : null;
6953     }
6954 
6955     /**
6956      * Get the IME action ID previous set with {@link #setImeActionLabel}.
6957      *
6958      * @see #setImeActionLabel
6959      * @see android.view.inputmethod.EditorInfo
6960      */
6961     @InspectableProperty
getImeActionId()6962     public int getImeActionId() {
6963         return mEditor != null && mEditor.mInputContentType != null
6964                 ? mEditor.mInputContentType.imeActionId : 0;
6965     }
6966 
6967     /**
6968      * Set a special listener to be called when an action is performed
6969      * on the text view.  This will be called when the enter key is pressed,
6970      * or when an action supplied to the IME is selected by the user.  Setting
6971      * this means that the normal hard key event will not insert a newline
6972      * into the text view, even if it is multi-line; holding down the ALT
6973      * modifier will, however, allow the user to insert a newline character.
6974      */
setOnEditorActionListener(OnEditorActionListener l)6975     public void setOnEditorActionListener(OnEditorActionListener l) {
6976         createEditorIfNeeded();
6977         mEditor.createInputContentTypeIfNeeded();
6978         mEditor.mInputContentType.onEditorActionListener = l;
6979     }
6980 
6981     /**
6982      * Called when an attached input method calls
6983      * {@link InputConnection#performEditorAction(int)
6984      * InputConnection.performEditorAction()}
6985      * for this text view.  The default implementation will call your action
6986      * listener supplied to {@link #setOnEditorActionListener}, or perform
6987      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
6988      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
6989      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
6990      * EditorInfo.IME_ACTION_DONE}.
6991      *
6992      * <p>For backwards compatibility, if no IME options have been set and the
6993      * text view would not normally advance focus on enter, then
6994      * the NEXT and DONE actions received here will be turned into an enter
6995      * key down/up pair to go through the normal key handling.
6996      *
6997      * @param actionCode The code of the action being performed.
6998      *
6999      * @see #setOnEditorActionListener
7000      */
onEditorAction(int actionCode)7001     public void onEditorAction(int actionCode) {
7002         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
7003         if (ict != null) {
7004             if (ict.onEditorActionListener != null) {
7005                 if (ict.onEditorActionListener.onEditorAction(this,
7006                         actionCode, null)) {
7007                     return;
7008                 }
7009             }
7010 
7011             // This is the handling for some default action.
7012             // Note that for backwards compatibility we don't do this
7013             // default handling if explicit ime options have not been given,
7014             // instead turning this into the normal enter key codes that an
7015             // app may be expecting.
7016             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
7017                 View v = focusSearch(FOCUS_FORWARD);
7018                 if (v != null) {
7019                     if (!v.requestFocus(FOCUS_FORWARD)) {
7020                         throw new IllegalStateException("focus search returned a view "
7021                                 + "that wasn't able to take focus!");
7022                     }
7023                 }
7024                 return;
7025 
7026             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
7027                 View v = focusSearch(FOCUS_BACKWARD);
7028                 if (v != null) {
7029                     if (!v.requestFocus(FOCUS_BACKWARD)) {
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_DONE) {
7037                 InputMethodManager imm = getInputMethodManager();
7038                 if (imm != null && imm.isActive(this)) {
7039                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7040                 }
7041                 return;
7042             }
7043         }
7044 
7045         ViewRootImpl viewRootImpl = getViewRootImpl();
7046         if (viewRootImpl != null) {
7047             long eventTime = SystemClock.uptimeMillis();
7048             viewRootImpl.dispatchKeyFromIme(
7049                     new KeyEvent(eventTime, eventTime,
7050                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
7051                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7052                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7053                     | KeyEvent.FLAG_EDITOR_ACTION));
7054             viewRootImpl.dispatchKeyFromIme(
7055                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
7056                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
7057                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7058                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7059                     | KeyEvent.FLAG_EDITOR_ACTION));
7060         }
7061     }
7062 
7063     /**
7064      * Set the private content type of the text, which is the
7065      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
7066      * field that will be filled in when creating an input connection.
7067      *
7068      * @see #getPrivateImeOptions()
7069      * @see EditorInfo#privateImeOptions
7070      * @attr ref android.R.styleable#TextView_privateImeOptions
7071      */
setPrivateImeOptions(String type)7072     public void setPrivateImeOptions(String type) {
7073         createEditorIfNeeded();
7074         mEditor.createInputContentTypeIfNeeded();
7075         mEditor.mInputContentType.privateImeOptions = type;
7076     }
7077 
7078     /**
7079      * Get the private type of the content.
7080      *
7081      * @see #setPrivateImeOptions(String)
7082      * @see EditorInfo#privateImeOptions
7083      */
7084     @InspectableProperty
getPrivateImeOptions()7085     public String getPrivateImeOptions() {
7086         return mEditor != null && mEditor.mInputContentType != null
7087                 ? mEditor.mInputContentType.privateImeOptions : null;
7088     }
7089 
7090     /**
7091      * Set the extra input data of the text, which is the
7092      * {@link EditorInfo#extras TextBoxAttribute.extras}
7093      * Bundle that will be filled in when creating an input connection.  The
7094      * given integer is the resource identifier of an XML resource holding an
7095      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
7096      *
7097      * @see #getInputExtras(boolean)
7098      * @see EditorInfo#extras
7099      * @attr ref android.R.styleable#TextView_editorExtras
7100      */
setInputExtras(@mlRes int xmlResId)7101     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
7102         createEditorIfNeeded();
7103         XmlResourceParser parser = getResources().getXml(xmlResId);
7104         mEditor.createInputContentTypeIfNeeded();
7105         mEditor.mInputContentType.extras = new Bundle();
7106         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
7107     }
7108 
7109     /**
7110      * Retrieve the input extras currently associated with the text view, which
7111      * can be viewed as well as modified.
7112      *
7113      * @param create If true, the extras will be created if they don't already
7114      * exist.  Otherwise, null will be returned if none have been created.
7115      * @see #setInputExtras(int)
7116      * @see EditorInfo#extras
7117      * @attr ref android.R.styleable#TextView_editorExtras
7118      */
getInputExtras(boolean create)7119     public Bundle getInputExtras(boolean create) {
7120         if (mEditor == null && !create) return null;
7121         createEditorIfNeeded();
7122         if (mEditor.mInputContentType == null) {
7123             if (!create) return null;
7124             mEditor.createInputContentTypeIfNeeded();
7125         }
7126         if (mEditor.mInputContentType.extras == null) {
7127             if (!create) return null;
7128             mEditor.mInputContentType.extras = new Bundle();
7129         }
7130         return mEditor.mInputContentType.extras;
7131     }
7132 
7133     /**
7134      * Change "hint" locales associated with the text view, which will be reported to an IME with
7135      * {@link EditorInfo#hintLocales} when it has focus.
7136      *
7137      * Starting with Android O, this also causes internationalized listeners to be created (or
7138      * change locale) based on the first locale in the input locale list.
7139      *
7140      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
7141      * call {@link InputMethodManager#restartInput(View)}.</p>
7142      * @param hintLocales List of the languages that the user is supposed to switch to no matter
7143      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
7144      * @see #getImeHintLocales()
7145      * @see android.view.inputmethod.EditorInfo#hintLocales
7146      */
setImeHintLocales(@ullable LocaleList hintLocales)7147     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
7148         createEditorIfNeeded();
7149         mEditor.createInputContentTypeIfNeeded();
7150         mEditor.mInputContentType.imeHintLocales = hintLocales;
7151         if (mUseInternationalizedInput) {
7152             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
7153         }
7154     }
7155 
7156     /**
7157      * @return The current languages list "hint". {@code null} when no "hint" is available.
7158      * @see #setImeHintLocales(LocaleList)
7159      * @see android.view.inputmethod.EditorInfo#hintLocales
7160      */
7161     @Nullable
getImeHintLocales()7162     public LocaleList getImeHintLocales() {
7163         if (mEditor == null) {
7164             return null;
7165         }
7166         if (mEditor.mInputContentType == null) {
7167             return null;
7168         }
7169         return mEditor.mInputContentType.imeHintLocales;
7170     }
7171 
7172     /**
7173      * Returns the error message that was set to be displayed with
7174      * {@link #setError}, or <code>null</code> if no error was set
7175      * or if it the error was cleared by the widget after user input.
7176      */
getError()7177     public CharSequence getError() {
7178         return mEditor == null ? null : mEditor.mError;
7179     }
7180 
7181     /**
7182      * Sets the right-hand compound drawable of the TextView to the "error"
7183      * icon and sets an error message that will be displayed in a popup when
7184      * the TextView has focus.  The icon and error message will be reset to
7185      * null when any key events cause changes to the TextView's text.  If the
7186      * <code>error</code> is <code>null</code>, the error message and icon
7187      * will be cleared.
7188      */
7189     @android.view.RemotableViewMethod
setError(CharSequence error)7190     public void setError(CharSequence error) {
7191         if (error == null) {
7192             setError(null, null);
7193         } else {
7194             Drawable dr = getContext().getDrawable(
7195                     com.android.internal.R.drawable.indicator_input_error);
7196 
7197             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
7198             setError(error, dr);
7199         }
7200     }
7201 
7202     /**
7203      * Sets the right-hand compound drawable of the TextView to the specified
7204      * icon and sets an error message that will be displayed in a popup when
7205      * the TextView has focus.  The icon and error message will be reset to
7206      * null when any key events cause changes to the TextView's text.  The
7207      * drawable must already have had {@link Drawable#setBounds} set on it.
7208      * If the <code>error</code> is <code>null</code>, the error message will
7209      * be cleared (and you should provide a <code>null</code> icon as well).
7210      */
setError(CharSequence error, Drawable icon)7211     public void setError(CharSequence error, Drawable icon) {
7212         createEditorIfNeeded();
7213         mEditor.setError(error, icon);
7214         notifyViewAccessibilityStateChangedIfNeeded(
7215                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7216     }
7217 
7218     @Override
setFrame(int l, int t, int r, int b)7219     protected boolean setFrame(int l, int t, int r, int b) {
7220         boolean result = super.setFrame(l, t, r, b);
7221 
7222         if (mEditor != null) mEditor.setFrame();
7223 
7224         restartMarqueeIfNeeded();
7225 
7226         return result;
7227     }
7228 
restartMarqueeIfNeeded()7229     private void restartMarqueeIfNeeded() {
7230         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7231             mRestartMarquee = false;
7232             startMarquee();
7233         }
7234     }
7235 
7236     /**
7237      * Sets the list of input filters that will be used if the buffer is
7238      * Editable. Has no effect otherwise.
7239      *
7240      * @attr ref android.R.styleable#TextView_maxLength
7241      */
setFilters(InputFilter[] filters)7242     public void setFilters(InputFilter[] filters) {
7243         if (filters == null) {
7244             throw new IllegalArgumentException();
7245         }
7246 
7247         mFilters = filters;
7248 
7249         if (mText instanceof Editable) {
7250             setFilters((Editable) mText, filters);
7251         }
7252     }
7253 
7254     /**
7255      * Sets the list of input filters on the specified Editable,
7256      * and includes mInput in the list if it is an InputFilter.
7257      */
setFilters(Editable e, InputFilter[] filters)7258     private void setFilters(Editable e, InputFilter[] filters) {
7259         if (mEditor != null) {
7260             final boolean undoFilter = mEditor.mUndoInputFilter != null;
7261             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
7262             int num = 0;
7263             if (undoFilter) num++;
7264             if (keyFilter) num++;
7265             if (num > 0) {
7266                 InputFilter[] nf = new InputFilter[filters.length + num];
7267 
7268                 System.arraycopy(filters, 0, nf, 0, filters.length);
7269                 num = 0;
7270                 if (undoFilter) {
7271                     nf[filters.length] = mEditor.mUndoInputFilter;
7272                     num++;
7273                 }
7274                 if (keyFilter) {
7275                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
7276                 }
7277 
7278                 e.setFilters(nf);
7279                 return;
7280             }
7281         }
7282         e.setFilters(filters);
7283     }
7284 
7285     /**
7286      * Returns the current list of input filters.
7287      *
7288      * @attr ref android.R.styleable#TextView_maxLength
7289      */
getFilters()7290     public InputFilter[] getFilters() {
7291         return mFilters;
7292     }
7293 
7294     /////////////////////////////////////////////////////////////////////////
7295 
getBoxHeight(Layout l)7296     private int getBoxHeight(Layout l) {
7297         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
7298         int padding = (l == mHintLayout)
7299                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
7300                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
7301         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
7302     }
7303 
7304     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)7305     int getVerticalOffset(boolean forceNormal) {
7306         int voffset = 0;
7307         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7308 
7309         Layout l = mLayout;
7310         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7311             l = mHintLayout;
7312         }
7313 
7314         if (gravity != Gravity.TOP) {
7315             int boxht = getBoxHeight(l);
7316             int textht = l.getHeight();
7317 
7318             if (textht < boxht) {
7319                 if (gravity == Gravity.BOTTOM) {
7320                     voffset = boxht - textht;
7321                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7322                     voffset = (boxht - textht) >> 1;
7323                 }
7324             }
7325         }
7326         return voffset;
7327     }
7328 
getBottomVerticalOffset(boolean forceNormal)7329     private int getBottomVerticalOffset(boolean forceNormal) {
7330         int voffset = 0;
7331         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7332 
7333         Layout l = mLayout;
7334         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7335             l = mHintLayout;
7336         }
7337 
7338         if (gravity != Gravity.BOTTOM) {
7339             int boxht = getBoxHeight(l);
7340             int textht = l.getHeight();
7341 
7342             if (textht < boxht) {
7343                 if (gravity == Gravity.TOP) {
7344                     voffset = boxht - textht;
7345                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7346                     voffset = (boxht - textht) >> 1;
7347                 }
7348             }
7349         }
7350         return voffset;
7351     }
7352 
invalidateCursorPath()7353     void invalidateCursorPath() {
7354         if (mHighlightPathBogus) {
7355             invalidateCursor();
7356         } else {
7357             final int horizontalPadding = getCompoundPaddingLeft();
7358             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7359 
7360             if (mEditor.mDrawableForCursor == null) {
7361                 synchronized (TEMP_RECTF) {
7362                     /*
7363                      * The reason for this concern about the thickness of the
7364                      * cursor and doing the floor/ceil on the coordinates is that
7365                      * some EditTexts (notably textfields in the Browser) have
7366                      * anti-aliased text where not all the characters are
7367                      * necessarily at integer-multiple locations.  This should
7368                      * make sure the entire cursor gets invalidated instead of
7369                      * sometimes missing half a pixel.
7370                      */
7371                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
7372                     if (thick < 1.0f) {
7373                         thick = 1.0f;
7374                     }
7375 
7376                     thick /= 2.0f;
7377 
7378                     // mHighlightPath is guaranteed to be non null at that point.
7379                     mHighlightPath.computeBounds(TEMP_RECTF, false);
7380 
7381                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
7382                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
7383                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
7384                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
7385                 }
7386             } else {
7387                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7388                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
7389                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
7390             }
7391         }
7392     }
7393 
invalidateCursor()7394     void invalidateCursor() {
7395         int where = getSelectionEnd();
7396 
7397         invalidateCursor(where, where, where);
7398     }
7399 
invalidateCursor(int a, int b, int c)7400     private void invalidateCursor(int a, int b, int c) {
7401         if (a >= 0 || b >= 0 || c >= 0) {
7402             int start = Math.min(Math.min(a, b), c);
7403             int end = Math.max(Math.max(a, b), c);
7404             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
7405         }
7406     }
7407 
7408     /**
7409      * Invalidates the region of text enclosed between the start and end text offsets.
7410      */
invalidateRegion(int start, int end, boolean invalidateCursor)7411     void invalidateRegion(int start, int end, boolean invalidateCursor) {
7412         if (mLayout == null) {
7413             invalidate();
7414         } else {
7415             int lineStart = mLayout.getLineForOffset(start);
7416             int top = mLayout.getLineTop(lineStart);
7417 
7418             // This is ridiculous, but the descent from the line above
7419             // can hang down into the line we really want to redraw,
7420             // so we have to invalidate part of the line above to make
7421             // sure everything that needs to be redrawn really is.
7422             // (But not the whole line above, because that would cause
7423             // the same problem with the descenders on the line above it!)
7424             if (lineStart > 0) {
7425                 top -= mLayout.getLineDescent(lineStart - 1);
7426             }
7427 
7428             int lineEnd;
7429 
7430             if (start == end) {
7431                 lineEnd = lineStart;
7432             } else {
7433                 lineEnd = mLayout.getLineForOffset(end);
7434             }
7435 
7436             int bottom = mLayout.getLineBottom(lineEnd);
7437 
7438             // mEditor can be null in case selection is set programmatically.
7439             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
7440                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7441                 top = Math.min(top, bounds.top);
7442                 bottom = Math.max(bottom, bounds.bottom);
7443             }
7444 
7445             final int compoundPaddingLeft = getCompoundPaddingLeft();
7446             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7447 
7448             int left, right;
7449             if (lineStart == lineEnd && !invalidateCursor) {
7450                 left = (int) mLayout.getPrimaryHorizontal(start);
7451                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
7452                 left += compoundPaddingLeft;
7453                 right += compoundPaddingLeft;
7454             } else {
7455                 // Rectangle bounding box when the region spans several lines
7456                 left = compoundPaddingLeft;
7457                 right = getWidth() - getCompoundPaddingRight();
7458             }
7459 
7460             invalidate(mScrollX + left, verticalPadding + top,
7461                     mScrollX + right, verticalPadding + bottom);
7462         }
7463     }
7464 
registerForPreDraw()7465     private void registerForPreDraw() {
7466         if (!mPreDrawRegistered) {
7467             getViewTreeObserver().addOnPreDrawListener(this);
7468             mPreDrawRegistered = true;
7469         }
7470     }
7471 
unregisterForPreDraw()7472     private void unregisterForPreDraw() {
7473         getViewTreeObserver().removeOnPreDrawListener(this);
7474         mPreDrawRegistered = false;
7475         mPreDrawListenerDetached = false;
7476     }
7477 
7478     /**
7479      * {@inheritDoc}
7480      */
7481     @Override
onPreDraw()7482     public boolean onPreDraw() {
7483         if (mLayout == null) {
7484             assumeLayout();
7485         }
7486 
7487         if (mMovement != null) {
7488             /* This code also provides auto-scrolling when a cursor is moved using a
7489              * CursorController (insertion point or selection limits).
7490              * For selection, ensure start or end is visible depending on controller's state.
7491              */
7492             int curs = getSelectionEnd();
7493             // Do not create the controller if it is not already created.
7494             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
7495                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
7496                 curs = getSelectionStart();
7497             }
7498 
7499             /*
7500              * TODO: This should really only keep the end in view if
7501              * it already was before the text changed.  I'm not sure
7502              * of a good way to tell from here if it was.
7503              */
7504             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7505                 curs = mText.length();
7506             }
7507 
7508             if (curs >= 0) {
7509                 bringPointIntoView(curs);
7510             }
7511         } else {
7512             bringTextIntoView();
7513         }
7514 
7515         // This has to be checked here since:
7516         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
7517         //   a screen rotation) since layout is not yet initialized at that point.
7518         if (mEditor != null && mEditor.mCreatedWithASelection) {
7519             mEditor.refreshTextActionMode();
7520             mEditor.mCreatedWithASelection = false;
7521         }
7522 
7523         unregisterForPreDraw();
7524 
7525         return true;
7526     }
7527 
7528     @Override
onAttachedToWindow()7529     protected void onAttachedToWindow() {
7530         super.onAttachedToWindow();
7531 
7532         if (mEditor != null) mEditor.onAttachedToWindow();
7533 
7534         if (mPreDrawListenerDetached) {
7535             getViewTreeObserver().addOnPreDrawListener(this);
7536             mPreDrawListenerDetached = false;
7537         }
7538     }
7539 
7540     /** @hide */
7541     @Override
onDetachedFromWindowInternal()7542     protected void onDetachedFromWindowInternal() {
7543         if (mPreDrawRegistered) {
7544             getViewTreeObserver().removeOnPreDrawListener(this);
7545             mPreDrawListenerDetached = true;
7546         }
7547 
7548         resetResolvedDrawables();
7549 
7550         if (mEditor != null) mEditor.onDetachedFromWindow();
7551 
7552         super.onDetachedFromWindowInternal();
7553     }
7554 
7555     @Override
onScreenStateChanged(int screenState)7556     public void onScreenStateChanged(int screenState) {
7557         super.onScreenStateChanged(screenState);
7558         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
7559     }
7560 
7561     @Override
isPaddingOffsetRequired()7562     protected boolean isPaddingOffsetRequired() {
7563         return mShadowRadius != 0 || mDrawables != null;
7564     }
7565 
7566     @Override
getLeftPaddingOffset()7567     protected int getLeftPaddingOffset() {
7568         return getCompoundPaddingLeft() - mPaddingLeft
7569                 + (int) Math.min(0, mShadowDx - mShadowRadius);
7570     }
7571 
7572     @Override
getTopPaddingOffset()7573     protected int getTopPaddingOffset() {
7574         return (int) Math.min(0, mShadowDy - mShadowRadius);
7575     }
7576 
7577     @Override
getBottomPaddingOffset()7578     protected int getBottomPaddingOffset() {
7579         return (int) Math.max(0, mShadowDy + mShadowRadius);
7580     }
7581 
7582     @Override
getRightPaddingOffset()7583     protected int getRightPaddingOffset() {
7584         return -(getCompoundPaddingRight() - mPaddingRight)
7585                 + (int) Math.max(0, mShadowDx + mShadowRadius);
7586     }
7587 
7588     @Override
verifyDrawable(@onNull Drawable who)7589     protected boolean verifyDrawable(@NonNull Drawable who) {
7590         final boolean verified = super.verifyDrawable(who);
7591         if (!verified && mDrawables != null) {
7592             for (Drawable dr : mDrawables.mShowing) {
7593                 if (who == dr) {
7594                     return true;
7595                 }
7596             }
7597         }
7598         return verified;
7599     }
7600 
7601     @Override
jumpDrawablesToCurrentState()7602     public void jumpDrawablesToCurrentState() {
7603         super.jumpDrawablesToCurrentState();
7604         if (mDrawables != null) {
7605             for (Drawable dr : mDrawables.mShowing) {
7606                 if (dr != null) {
7607                     dr.jumpToCurrentState();
7608                 }
7609             }
7610         }
7611     }
7612 
7613     @Override
invalidateDrawable(@onNull Drawable drawable)7614     public void invalidateDrawable(@NonNull Drawable drawable) {
7615         boolean handled = false;
7616 
7617         if (verifyDrawable(drawable)) {
7618             final Rect dirty = drawable.getBounds();
7619             int scrollX = mScrollX;
7620             int scrollY = mScrollY;
7621 
7622             // IMPORTANT: The coordinates below are based on the coordinates computed
7623             // for each compound drawable in onDraw(). Make sure to update each section
7624             // accordingly.
7625             final TextView.Drawables drawables = mDrawables;
7626             if (drawables != null) {
7627                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
7628                     final int compoundPaddingTop = getCompoundPaddingTop();
7629                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7630                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7631 
7632                     scrollX += mPaddingLeft;
7633                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
7634                     handled = true;
7635                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
7636                     final int compoundPaddingTop = getCompoundPaddingTop();
7637                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7638                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7639 
7640                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
7641                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
7642                     handled = true;
7643                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
7644                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7645                     final int compoundPaddingRight = getCompoundPaddingRight();
7646                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7647 
7648                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
7649                     scrollY += mPaddingTop;
7650                     handled = true;
7651                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
7652                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7653                     final int compoundPaddingRight = getCompoundPaddingRight();
7654                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7655 
7656                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
7657                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
7658                     handled = true;
7659                 }
7660             }
7661 
7662             if (handled) {
7663                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
7664                         dirty.right + scrollX, dirty.bottom + scrollY);
7665             }
7666         }
7667 
7668         if (!handled) {
7669             super.invalidateDrawable(drawable);
7670         }
7671     }
7672 
7673     @Override
hasOverlappingRendering()7674     public boolean hasOverlappingRendering() {
7675         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
7676         return ((getBackground() != null && getBackground().getCurrent() != null)
7677                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
7678                 || mShadowColor != 0);
7679     }
7680 
7681     /**
7682      *
7683      * Returns the state of the {@code textIsSelectable} flag (See
7684      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
7685      * to allow users to select and copy text in a non-editable TextView, the content of an
7686      * {@link EditText} can always be selected, independently of the value of this flag.
7687      * <p>
7688      *
7689      * @return True if the text displayed in this TextView can be selected by the user.
7690      *
7691      * @attr ref android.R.styleable#TextView_textIsSelectable
7692      */
7693     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()7694     public boolean isTextSelectable() {
7695         return mEditor == null ? false : mEditor.mTextIsSelectable;
7696     }
7697 
7698     /**
7699      * Sets whether the content of this view is selectable by the user. The default is
7700      * {@code false}, meaning that the content is not selectable.
7701      * <p>
7702      * When you use a TextView to display a useful piece of information to the user (such as a
7703      * contact's address), make it selectable, so that the user can select and copy its
7704      * content. You can also use set the XML attribute
7705      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
7706      * <p>
7707      * When you call this method to set the value of {@code textIsSelectable}, it sets
7708      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
7709      * and {@code longClickable} to the same value. These flags correspond to the attributes
7710      * {@link android.R.styleable#View_focusable android:focusable},
7711      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
7712      * {@link android.R.styleable#View_clickable android:clickable}, and
7713      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
7714      * flags to a state you had set previously, call one or more of the following methods:
7715      * {@link #setFocusable(boolean) setFocusable()},
7716      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
7717      * {@link #setClickable(boolean) setClickable()} or
7718      * {@link #setLongClickable(boolean) setLongClickable()}.
7719      *
7720      * @param selectable Whether the content of this TextView should be selectable.
7721      */
setTextIsSelectable(boolean selectable)7722     public void setTextIsSelectable(boolean selectable) {
7723         if (!selectable && mEditor == null) return; // false is default value with no edit data
7724 
7725         createEditorIfNeeded();
7726         if (mEditor.mTextIsSelectable == selectable) return;
7727 
7728         mEditor.mTextIsSelectable = selectable;
7729         setFocusableInTouchMode(selectable);
7730         setFocusable(FOCUSABLE_AUTO);
7731         setClickable(selectable);
7732         setLongClickable(selectable);
7733 
7734         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
7735 
7736         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
7737         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
7738 
7739         // Called by setText above, but safer in case of future code changes
7740         mEditor.prepareCursorControllers();
7741     }
7742 
7743     @Override
onCreateDrawableState(int extraSpace)7744     protected int[] onCreateDrawableState(int extraSpace) {
7745         final int[] drawableState;
7746 
7747         if (mSingleLine) {
7748             drawableState = super.onCreateDrawableState(extraSpace);
7749         } else {
7750             drawableState = super.onCreateDrawableState(extraSpace + 1);
7751             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
7752         }
7753 
7754         if (isTextSelectable()) {
7755             // Disable pressed state, which was introduced when TextView was made clickable.
7756             // Prevents text color change.
7757             // setClickable(false) would have a similar effect, but it also disables focus changes
7758             // and long press actions, which are both needed by text selection.
7759             final int length = drawableState.length;
7760             for (int i = 0; i < length; i++) {
7761                 if (drawableState[i] == R.attr.state_pressed) {
7762                     final int[] nonPressedState = new int[length - 1];
7763                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
7764                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
7765                     return nonPressedState;
7766                 }
7767             }
7768         }
7769 
7770         return drawableState;
7771     }
7772 
7773     @UnsupportedAppUsage
getUpdatedHighlightPath()7774     private Path getUpdatedHighlightPath() {
7775         Path highlight = null;
7776         Paint highlightPaint = mHighlightPaint;
7777 
7778         final int selStart = getSelectionStart();
7779         final int selEnd = getSelectionEnd();
7780         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
7781             if (selStart == selEnd) {
7782                 if (mEditor != null && mEditor.shouldRenderCursor()) {
7783                     if (mHighlightPathBogus) {
7784                         if (mHighlightPath == null) mHighlightPath = new Path();
7785                         mHighlightPath.reset();
7786                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
7787                         mEditor.updateCursorPosition();
7788                         mHighlightPathBogus = false;
7789                     }
7790 
7791                     // XXX should pass to skin instead of drawing directly
7792                     highlightPaint.setColor(mCurTextColor);
7793                     highlightPaint.setStyle(Paint.Style.STROKE);
7794                     highlight = mHighlightPath;
7795                 }
7796             } else {
7797                 if (mHighlightPathBogus) {
7798                     if (mHighlightPath == null) mHighlightPath = new Path();
7799                     mHighlightPath.reset();
7800                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7801                     mHighlightPathBogus = false;
7802                 }
7803 
7804                 // XXX should pass to skin instead of drawing directly
7805                 highlightPaint.setColor(mHighlightColor);
7806                 highlightPaint.setStyle(Paint.Style.FILL);
7807 
7808                 highlight = mHighlightPath;
7809             }
7810         }
7811         return highlight;
7812     }
7813 
7814     /**
7815      * @hide
7816      */
getHorizontalOffsetForDrawables()7817     public int getHorizontalOffsetForDrawables() {
7818         return 0;
7819     }
7820 
7821     @Override
onDraw(Canvas canvas)7822     protected void onDraw(Canvas canvas) {
7823         restartMarqueeIfNeeded();
7824 
7825         // Draw the background for this view
7826         super.onDraw(canvas);
7827 
7828         final int compoundPaddingLeft = getCompoundPaddingLeft();
7829         final int compoundPaddingTop = getCompoundPaddingTop();
7830         final int compoundPaddingRight = getCompoundPaddingRight();
7831         final int compoundPaddingBottom = getCompoundPaddingBottom();
7832         final int scrollX = mScrollX;
7833         final int scrollY = mScrollY;
7834         final int right = mRight;
7835         final int left = mLeft;
7836         final int bottom = mBottom;
7837         final int top = mTop;
7838         final boolean isLayoutRtl = isLayoutRtl();
7839         final int offset = getHorizontalOffsetForDrawables();
7840         final int leftOffset = isLayoutRtl ? 0 : offset;
7841         final int rightOffset = isLayoutRtl ? offset : 0;
7842 
7843         final Drawables dr = mDrawables;
7844         if (dr != null) {
7845             /*
7846              * Compound, not extended, because the icon is not clipped
7847              * if the text height is smaller.
7848              */
7849 
7850             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
7851             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
7852 
7853             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7854             // Make sure to update invalidateDrawable() when changing this code.
7855             if (dr.mShowing[Drawables.LEFT] != null) {
7856                 canvas.save();
7857                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
7858                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
7859                 dr.mShowing[Drawables.LEFT].draw(canvas);
7860                 canvas.restore();
7861             }
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.RIGHT] != null) {
7866                 canvas.save();
7867                 canvas.translate(scrollX + right - left - mPaddingRight
7868                         - dr.mDrawableSizeRight - rightOffset,
7869                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
7870                 dr.mShowing[Drawables.RIGHT].draw(canvas);
7871                 canvas.restore();
7872             }
7873 
7874             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7875             // Make sure to update invalidateDrawable() when changing this code.
7876             if (dr.mShowing[Drawables.TOP] != null) {
7877                 canvas.save();
7878                 canvas.translate(scrollX + compoundPaddingLeft
7879                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
7880                 dr.mShowing[Drawables.TOP].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.BOTTOM] != null) {
7887                 canvas.save();
7888                 canvas.translate(scrollX + compoundPaddingLeft
7889                         + (hspace - dr.mDrawableWidthBottom) / 2,
7890                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
7891                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
7892                 canvas.restore();
7893             }
7894         }
7895 
7896         int color = mCurTextColor;
7897 
7898         if (mLayout == null) {
7899             assumeLayout();
7900         }
7901 
7902         Layout layout = mLayout;
7903 
7904         if (mHint != null && mText.length() == 0) {
7905             if (mHintTextColor != null) {
7906                 color = mCurHintTextColor;
7907             }
7908 
7909             layout = mHintLayout;
7910         }
7911 
7912         mTextPaint.setColor(color);
7913         mTextPaint.drawableState = getDrawableState();
7914 
7915         canvas.save();
7916         /*  Would be faster if we didn't have to do this. Can we chop the
7917             (displayable) text so that we don't need to do this ever?
7918         */
7919 
7920         int extendedPaddingTop = getExtendedPaddingTop();
7921         int extendedPaddingBottom = getExtendedPaddingBottom();
7922 
7923         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7924         final int maxScrollY = mLayout.getHeight() - vspace;
7925 
7926         float clipLeft = compoundPaddingLeft + scrollX;
7927         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
7928         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
7929         float clipBottom = bottom - top + scrollY
7930                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
7931 
7932         if (mShadowRadius != 0) {
7933             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
7934             clipRight += Math.max(0, mShadowDx + mShadowRadius);
7935 
7936             clipTop += Math.min(0, mShadowDy - mShadowRadius);
7937             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
7938         }
7939 
7940         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
7941 
7942         int voffsetText = 0;
7943         int voffsetCursor = 0;
7944 
7945         // translate in by our padding
7946         /* shortcircuit calling getVerticaOffset() */
7947         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7948             voffsetText = getVerticalOffset(false);
7949             voffsetCursor = getVerticalOffset(true);
7950         }
7951         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
7952 
7953         final int layoutDirection = getLayoutDirection();
7954         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7955         if (isMarqueeFadeEnabled()) {
7956             if (!mSingleLine && getLineCount() == 1 && canMarquee()
7957                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
7958                 final int width = mRight - mLeft;
7959                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
7960                 final float dx = mLayout.getLineRight(0) - (width - padding);
7961                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7962             }
7963 
7964             if (mMarquee != null && mMarquee.isRunning()) {
7965                 final float dx = -mMarquee.getScroll();
7966                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7967             }
7968         }
7969 
7970         final int cursorOffsetVertical = voffsetCursor - voffsetText;
7971 
7972         Path highlight = getUpdatedHighlightPath();
7973         if (mEditor != null) {
7974             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
7975         } else {
7976             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7977         }
7978 
7979         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
7980             final float dx = mMarquee.getGhostOffset();
7981             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7982             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7983         }
7984 
7985         canvas.restore();
7986     }
7987 
7988     @Override
getFocusedRect(Rect r)7989     public void getFocusedRect(Rect r) {
7990         if (mLayout == null) {
7991             super.getFocusedRect(r);
7992             return;
7993         }
7994 
7995         int selEnd = getSelectionEnd();
7996         if (selEnd < 0) {
7997             super.getFocusedRect(r);
7998             return;
7999         }
8000 
8001         int selStart = getSelectionStart();
8002         if (selStart < 0 || selStart >= selEnd) {
8003             int line = mLayout.getLineForOffset(selEnd);
8004             r.top = mLayout.getLineTop(line);
8005             r.bottom = mLayout.getLineBottom(line);
8006             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
8007             r.right = r.left + 4;
8008         } else {
8009             int lineStart = mLayout.getLineForOffset(selStart);
8010             int lineEnd = mLayout.getLineForOffset(selEnd);
8011             r.top = mLayout.getLineTop(lineStart);
8012             r.bottom = mLayout.getLineBottom(lineEnd);
8013             if (lineStart == lineEnd) {
8014                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
8015                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
8016             } else {
8017                 // Selection extends across multiple lines -- make the focused
8018                 // rect cover the entire width.
8019                 if (mHighlightPathBogus) {
8020                     if (mHighlightPath == null) mHighlightPath = new Path();
8021                     mHighlightPath.reset();
8022                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8023                     mHighlightPathBogus = false;
8024                 }
8025                 synchronized (TEMP_RECTF) {
8026                     mHighlightPath.computeBounds(TEMP_RECTF, true);
8027                     r.left = (int) TEMP_RECTF.left - 1;
8028                     r.right = (int) TEMP_RECTF.right + 1;
8029                 }
8030             }
8031         }
8032 
8033         // Adjust for padding and gravity.
8034         int paddingLeft = getCompoundPaddingLeft();
8035         int paddingTop = getExtendedPaddingTop();
8036         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8037             paddingTop += getVerticalOffset(false);
8038         }
8039         r.offset(paddingLeft, paddingTop);
8040         int paddingBottom = getExtendedPaddingBottom();
8041         r.bottom += paddingBottom;
8042     }
8043 
8044     /**
8045      * Return the number of lines of text, or 0 if the internal Layout has not
8046      * been built.
8047      */
getLineCount()8048     public int getLineCount() {
8049         return mLayout != null ? mLayout.getLineCount() : 0;
8050     }
8051 
8052     /**
8053      * Return the baseline for the specified line (0...getLineCount() - 1)
8054      * If bounds is not null, return the top, left, right, bottom extents
8055      * of the specified line in it. If the internal Layout has not been built,
8056      * return 0 and set bounds to (0, 0, 0, 0)
8057      * @param line which line to examine (0..getLineCount() - 1)
8058      * @param bounds Optional. If not null, it returns the extent of the line
8059      * @return the Y-coordinate of the baseline
8060      */
getLineBounds(int line, Rect bounds)8061     public int getLineBounds(int line, Rect bounds) {
8062         if (mLayout == null) {
8063             if (bounds != null) {
8064                 bounds.set(0, 0, 0, 0);
8065             }
8066             return 0;
8067         } else {
8068             int baseline = mLayout.getLineBounds(line, bounds);
8069 
8070             int voffset = getExtendedPaddingTop();
8071             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8072                 voffset += getVerticalOffset(true);
8073             }
8074             if (bounds != null) {
8075                 bounds.offset(getCompoundPaddingLeft(), voffset);
8076             }
8077             return baseline + voffset;
8078         }
8079     }
8080 
8081     @Override
getBaseline()8082     public int getBaseline() {
8083         if (mLayout == null) {
8084             return super.getBaseline();
8085         }
8086 
8087         return getBaselineOffset() + mLayout.getLineBaseline(0);
8088     }
8089 
getBaselineOffset()8090     int getBaselineOffset() {
8091         int voffset = 0;
8092         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8093             voffset = getVerticalOffset(true);
8094         }
8095 
8096         if (isLayoutModeOptical(mParent)) {
8097             voffset -= getOpticalInsets().top;
8098         }
8099 
8100         return getExtendedPaddingTop() + voffset;
8101     }
8102 
8103     /**
8104      * @hide
8105      */
8106     @Override
getFadeTop(boolean offsetRequired)8107     protected int getFadeTop(boolean offsetRequired) {
8108         if (mLayout == null) return 0;
8109 
8110         int voffset = 0;
8111         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8112             voffset = getVerticalOffset(true);
8113         }
8114 
8115         if (offsetRequired) voffset += getTopPaddingOffset();
8116 
8117         return getExtendedPaddingTop() + voffset;
8118     }
8119 
8120     /**
8121      * @hide
8122      */
8123     @Override
getFadeHeight(boolean offsetRequired)8124     protected int getFadeHeight(boolean offsetRequired) {
8125         return mLayout != null ? mLayout.getHeight() : 0;
8126     }
8127 
8128     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)8129     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
8130         if (mSpannable != null && mLinksClickable) {
8131             final float x = event.getX(pointerIndex);
8132             final float y = event.getY(pointerIndex);
8133             final int offset = getOffsetForPosition(x, y);
8134             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
8135                     ClickableSpan.class);
8136             if (clickables.length > 0) {
8137                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
8138             }
8139         }
8140         if (isTextSelectable() || isTextEditable()) {
8141             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
8142         }
8143         return super.onResolvePointerIcon(event, pointerIndex);
8144     }
8145 
8146     @Override
onKeyPreIme(int keyCode, KeyEvent event)8147     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
8148         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
8149         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
8150         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
8151         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
8152             return true;
8153         }
8154         return super.onKeyPreIme(keyCode, event);
8155     }
8156 
8157     /**
8158      * @hide
8159      */
handleBackInTextActionModeIfNeeded(KeyEvent event)8160     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
8161         // Do nothing unless mEditor is in text action mode.
8162         if (mEditor == null || mEditor.getTextActionMode() == null) {
8163             return false;
8164         }
8165 
8166         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
8167             KeyEvent.DispatcherState state = getKeyDispatcherState();
8168             if (state != null) {
8169                 state.startTracking(event, this);
8170             }
8171             return true;
8172         } else if (event.getAction() == KeyEvent.ACTION_UP) {
8173             KeyEvent.DispatcherState state = getKeyDispatcherState();
8174             if (state != null) {
8175                 state.handleUpEvent(event);
8176             }
8177             if (event.isTracking() && !event.isCanceled()) {
8178                 stopTextActionMode();
8179                 return true;
8180             }
8181         }
8182         return false;
8183     }
8184 
8185     @Override
onKeyDown(int keyCode, KeyEvent event)8186     public boolean onKeyDown(int keyCode, KeyEvent event) {
8187         final int which = doKeyDown(keyCode, event, null);
8188         if (which == KEY_EVENT_NOT_HANDLED) {
8189             return super.onKeyDown(keyCode, event);
8190         }
8191 
8192         return true;
8193     }
8194 
8195     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8196     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
8197         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
8198         final int which = doKeyDown(keyCode, down, event);
8199         if (which == KEY_EVENT_NOT_HANDLED) {
8200             // Go through default dispatching.
8201             return super.onKeyMultiple(keyCode, repeatCount, event);
8202         }
8203         if (which == KEY_EVENT_HANDLED) {
8204             // Consumed the whole thing.
8205             return true;
8206         }
8207 
8208         repeatCount--;
8209 
8210         // We are going to dispatch the remaining events to either the input
8211         // or movement method.  To do this, we will just send a repeated stream
8212         // of down and up events until we have done the complete repeatCount.
8213         // It would be nice if those interfaces had an onKeyMultiple() method,
8214         // but adding that is a more complicated change.
8215         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
8216         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
8217             // mEditor and mEditor.mInput are not null from doKeyDown
8218             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8219             while (--repeatCount > 0) {
8220                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
8221                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8222             }
8223             hideErrorIfUnchanged();
8224 
8225         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
8226             // mMovement is not null from doKeyDown
8227             mMovement.onKeyUp(this, mSpannable, keyCode, up);
8228             while (--repeatCount > 0) {
8229                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
8230                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
8231             }
8232         }
8233 
8234         return true;
8235     }
8236 
8237     /**
8238      * Returns true if pressing ENTER in this field advances focus instead
8239      * of inserting the character.  This is true mostly in single-line fields,
8240      * but also in mail addresses and subjects which will display on multiple
8241      * lines but where it doesn't make sense to insert newlines.
8242      */
shouldAdvanceFocusOnEnter()8243     private boolean shouldAdvanceFocusOnEnter() {
8244         if (getKeyListener() == null) {
8245             return false;
8246         }
8247 
8248         if (mSingleLine) {
8249             return true;
8250         }
8251 
8252         if (mEditor != null
8253                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8254                         == EditorInfo.TYPE_CLASS_TEXT) {
8255             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8256             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
8257                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
8258                 return true;
8259             }
8260         }
8261 
8262         return false;
8263     }
8264 
8265     /**
8266      * Returns true if pressing TAB in this field advances focus instead
8267      * of inserting the character.  Insert tabs only in multi-line editors.
8268      */
shouldAdvanceFocusOnTab()8269     private boolean shouldAdvanceFocusOnTab() {
8270         if (getKeyListener() != null && !mSingleLine && mEditor != null
8271                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8272                         == EditorInfo.TYPE_CLASS_TEXT) {
8273             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8274             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
8275                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
8276                 return false;
8277             }
8278         }
8279         return true;
8280     }
8281 
isDirectionalNavigationKey(int keyCode)8282     private boolean isDirectionalNavigationKey(int keyCode) {
8283         switch(keyCode) {
8284             case KeyEvent.KEYCODE_DPAD_UP:
8285             case KeyEvent.KEYCODE_DPAD_DOWN:
8286             case KeyEvent.KEYCODE_DPAD_LEFT:
8287             case KeyEvent.KEYCODE_DPAD_RIGHT:
8288                 return true;
8289         }
8290         return false;
8291     }
8292 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8293     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
8294         if (!isEnabled()) {
8295             return KEY_EVENT_NOT_HANDLED;
8296         }
8297 
8298         // If this is the initial keydown, we don't want to prevent a movement away from this view.
8299         // While this shouldn't be necessary because any time we're preventing default movement we
8300         // should be restricting the focus to remain within this view, thus we'll also receive
8301         // the key up event, occasionally key up events will get dropped and we don't want to
8302         // prevent the user from traversing out of this on the next key down.
8303         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8304             mPreventDefaultMovement = false;
8305         }
8306 
8307         switch (keyCode) {
8308             case KeyEvent.KEYCODE_ENTER:
8309                 if (event.hasNoModifiers()) {
8310                     // When mInputContentType is set, we know that we are
8311                     // running in a "modern" cupcake environment, so don't need
8312                     // to worry about the application trying to capture
8313                     // enter key events.
8314                     if (mEditor != null && mEditor.mInputContentType != null) {
8315                         // If there is an action listener, given them a
8316                         // chance to consume the event.
8317                         if (mEditor.mInputContentType.onEditorActionListener != null
8318                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8319                                         this, EditorInfo.IME_NULL, event)) {
8320                             mEditor.mInputContentType.enterDown = true;
8321                             // We are consuming the enter key for them.
8322                             return KEY_EVENT_HANDLED;
8323                         }
8324                     }
8325 
8326                     // If our editor should move focus when enter is pressed, or
8327                     // this is a generated event from an IME action button, then
8328                     // don't let it be inserted into the text.
8329                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8330                             || shouldAdvanceFocusOnEnter()) {
8331                         if (hasOnClickListeners()) {
8332                             return KEY_EVENT_NOT_HANDLED;
8333                         }
8334                         return KEY_EVENT_HANDLED;
8335                     }
8336                 }
8337                 break;
8338 
8339             case KeyEvent.KEYCODE_DPAD_CENTER:
8340                 if (event.hasNoModifiers()) {
8341                     if (shouldAdvanceFocusOnEnter()) {
8342                         return KEY_EVENT_NOT_HANDLED;
8343                     }
8344                 }
8345                 break;
8346 
8347             case KeyEvent.KEYCODE_TAB:
8348                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
8349                     if (shouldAdvanceFocusOnTab()) {
8350                         return KEY_EVENT_NOT_HANDLED;
8351                     }
8352                 }
8353                 break;
8354 
8355                 // Has to be done on key down (and not on key up) to correctly be intercepted.
8356             case KeyEvent.KEYCODE_BACK:
8357                 if (mEditor != null && mEditor.getTextActionMode() != null) {
8358                     stopTextActionMode();
8359                     return KEY_EVENT_HANDLED;
8360                 }
8361                 break;
8362 
8363             case KeyEvent.KEYCODE_CUT:
8364                 if (event.hasNoModifiers() && canCut()) {
8365                     if (onTextContextMenuItem(ID_CUT)) {
8366                         return KEY_EVENT_HANDLED;
8367                     }
8368                 }
8369                 break;
8370 
8371             case KeyEvent.KEYCODE_COPY:
8372                 if (event.hasNoModifiers() && canCopy()) {
8373                     if (onTextContextMenuItem(ID_COPY)) {
8374                         return KEY_EVENT_HANDLED;
8375                     }
8376                 }
8377                 break;
8378 
8379             case KeyEvent.KEYCODE_PASTE:
8380                 if (event.hasNoModifiers() && canPaste()) {
8381                     if (onTextContextMenuItem(ID_PASTE)) {
8382                         return KEY_EVENT_HANDLED;
8383                     }
8384                 }
8385                 break;
8386 
8387             case KeyEvent.KEYCODE_FORWARD_DEL:
8388                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
8389                     if (onTextContextMenuItem(ID_CUT)) {
8390                         return KEY_EVENT_HANDLED;
8391                     }
8392                 }
8393                 break;
8394 
8395             case KeyEvent.KEYCODE_INSERT:
8396                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
8397                     if (onTextContextMenuItem(ID_COPY)) {
8398                         return KEY_EVENT_HANDLED;
8399                     }
8400                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
8401                     if (onTextContextMenuItem(ID_PASTE)) {
8402                         return KEY_EVENT_HANDLED;
8403                     }
8404                 }
8405                 break;
8406         }
8407 
8408         if (mEditor != null && mEditor.mKeyListener != null) {
8409             boolean doDown = true;
8410             if (otherEvent != null) {
8411                 try {
8412                     beginBatchEdit();
8413                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
8414                             otherEvent);
8415                     hideErrorIfUnchanged();
8416                     doDown = false;
8417                     if (handled) {
8418                         return KEY_EVENT_HANDLED;
8419                     }
8420                 } catch (AbstractMethodError e) {
8421                     // onKeyOther was added after 1.0, so if it isn't
8422                     // implemented we need to try to dispatch as a regular down.
8423                 } finally {
8424                     endBatchEdit();
8425                 }
8426             }
8427 
8428             if (doDown) {
8429                 beginBatchEdit();
8430                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
8431                         keyCode, event);
8432                 endBatchEdit();
8433                 hideErrorIfUnchanged();
8434                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
8435             }
8436         }
8437 
8438         // bug 650865: sometimes we get a key event before a layout.
8439         // don't try to move around if we don't know the layout.
8440 
8441         if (mMovement != null && mLayout != null) {
8442             boolean doDown = true;
8443             if (otherEvent != null) {
8444                 try {
8445                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
8446                     doDown = false;
8447                     if (handled) {
8448                         return KEY_EVENT_HANDLED;
8449                     }
8450                 } catch (AbstractMethodError e) {
8451                     // onKeyOther was added after 1.0, so if it isn't
8452                     // implemented we need to try to dispatch as a regular down.
8453                 }
8454             }
8455             if (doDown) {
8456                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
8457                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8458                         mPreventDefaultMovement = true;
8459                     }
8460                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
8461                 }
8462             }
8463             // Consume arrows from keyboard devices to prevent focus leaving the editor.
8464             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
8465             // to move focus with arrows.
8466             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
8467                     && isDirectionalNavigationKey(keyCode)) {
8468                 return KEY_EVENT_HANDLED;
8469             }
8470         }
8471 
8472         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
8473                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
8474     }
8475 
8476     /**
8477      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
8478      * can be recorded.
8479      * @hide
8480      */
resetErrorChangedFlag()8481     public void resetErrorChangedFlag() {
8482         /*
8483          * Keep track of what the error was before doing the input
8484          * so that if an input filter changed the error, we leave
8485          * that error showing.  Otherwise, we take down whatever
8486          * error was showing when the user types something.
8487          */
8488         if (mEditor != null) mEditor.mErrorWasChanged = false;
8489     }
8490 
8491     /**
8492      * @hide
8493      */
hideErrorIfUnchanged()8494     public void hideErrorIfUnchanged() {
8495         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
8496             setError(null, null);
8497         }
8498     }
8499 
8500     @Override
onKeyUp(int keyCode, KeyEvent event)8501     public boolean onKeyUp(int keyCode, KeyEvent event) {
8502         if (!isEnabled()) {
8503             return super.onKeyUp(keyCode, event);
8504         }
8505 
8506         if (!KeyEvent.isModifierKey(keyCode)) {
8507             mPreventDefaultMovement = false;
8508         }
8509 
8510         switch (keyCode) {
8511             case KeyEvent.KEYCODE_DPAD_CENTER:
8512                 if (event.hasNoModifiers()) {
8513                     /*
8514                      * If there is a click listener, just call through to
8515                      * super, which will invoke it.
8516                      *
8517                      * If there isn't a click listener, try to show the soft
8518                      * input method.  (It will also
8519                      * call performClick(), but that won't do anything in
8520                      * this case.)
8521                      */
8522                     if (!hasOnClickListeners()) {
8523                         if (mMovement != null && mText instanceof Editable
8524                                 && mLayout != null && onCheckIsTextEditor()) {
8525                             InputMethodManager imm = getInputMethodManager();
8526                             viewClicked(imm);
8527                             if (imm != null && getShowSoftInputOnFocus()) {
8528                                 imm.showSoftInput(this, 0);
8529                             }
8530                         }
8531                     }
8532                 }
8533                 return super.onKeyUp(keyCode, event);
8534 
8535             case KeyEvent.KEYCODE_ENTER:
8536                 if (event.hasNoModifiers()) {
8537                     if (mEditor != null && mEditor.mInputContentType != null
8538                             && mEditor.mInputContentType.onEditorActionListener != null
8539                             && mEditor.mInputContentType.enterDown) {
8540                         mEditor.mInputContentType.enterDown = false;
8541                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8542                                 this, EditorInfo.IME_NULL, event)) {
8543                             return true;
8544                         }
8545                     }
8546 
8547                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8548                             || shouldAdvanceFocusOnEnter()) {
8549                         /*
8550                          * If there is a click listener, just call through to
8551                          * super, which will invoke it.
8552                          *
8553                          * If there isn't a click listener, try to advance focus,
8554                          * but still call through to super, which will reset the
8555                          * pressed state and longpress state.  (It will also
8556                          * call performClick(), but that won't do anything in
8557                          * this case.)
8558                          */
8559                         if (!hasOnClickListeners()) {
8560                             View v = focusSearch(FOCUS_DOWN);
8561 
8562                             if (v != null) {
8563                                 if (!v.requestFocus(FOCUS_DOWN)) {
8564                                     throw new IllegalStateException("focus search returned a view "
8565                                             + "that wasn't able to take focus!");
8566                                 }
8567 
8568                                 /*
8569                                  * Return true because we handled the key; super
8570                                  * will return false because there was no click
8571                                  * listener.
8572                                  */
8573                                 super.onKeyUp(keyCode, event);
8574                                 return true;
8575                             } else if ((event.getFlags()
8576                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
8577                                 // No target for next focus, but make sure the IME
8578                                 // if this came from it.
8579                                 InputMethodManager imm = getInputMethodManager();
8580                                 if (imm != null && imm.isActive(this)) {
8581                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
8582                                 }
8583                             }
8584                         }
8585                     }
8586                     return super.onKeyUp(keyCode, event);
8587                 }
8588                 break;
8589         }
8590 
8591         if (mEditor != null && mEditor.mKeyListener != null) {
8592             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
8593                 return true;
8594             }
8595         }
8596 
8597         if (mMovement != null && mLayout != null) {
8598             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
8599                 return true;
8600             }
8601         }
8602 
8603         return super.onKeyUp(keyCode, event);
8604     }
8605 
8606     @Override
onCheckIsTextEditor()8607     public boolean onCheckIsTextEditor() {
8608         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
8609     }
8610 
8611     @Override
onCreateInputConnection(EditorInfo outAttrs)8612     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
8613         if (onCheckIsTextEditor() && isEnabled()) {
8614             mEditor.createInputMethodStateIfNeeded();
8615             outAttrs.inputType = getInputType();
8616             if (mEditor.mInputContentType != null) {
8617                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
8618                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
8619                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
8620                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
8621                 outAttrs.extras = mEditor.mInputContentType.extras;
8622                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
8623             } else {
8624                 outAttrs.imeOptions = EditorInfo.IME_NULL;
8625                 outAttrs.hintLocales = null;
8626             }
8627             if (focusSearch(FOCUS_DOWN) != null) {
8628                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
8629             }
8630             if (focusSearch(FOCUS_UP) != null) {
8631                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
8632             }
8633             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
8634                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
8635                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
8636                     // An action has not been set, but the enter key will move to
8637                     // the next focus, so set the action to that.
8638                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
8639                 } else {
8640                     // An action has not been set, and there is no focus to move
8641                     // to, so let's just supply a "done" action.
8642                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
8643                 }
8644                 if (!shouldAdvanceFocusOnEnter()) {
8645                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8646                 }
8647             }
8648             if (isMultilineInputType(outAttrs.inputType)) {
8649                 // Multi-line text editors should always show an enter key.
8650                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8651             }
8652             outAttrs.hintText = mHint;
8653             outAttrs.targetInputMethodUser = mTextOperationUser;
8654             if (mText instanceof Editable) {
8655                 InputConnection ic = new EditableInputConnection(this);
8656                 outAttrs.initialSelStart = getSelectionStart();
8657                 outAttrs.initialSelEnd = getSelectionEnd();
8658                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
8659                 return ic;
8660             }
8661         }
8662         return null;
8663     }
8664 
8665     /**
8666      * If this TextView contains editable content, extract a portion of it
8667      * based on the information in <var>request</var> in to <var>outText</var>.
8668      * @return Returns true if the text was successfully extracted, else false.
8669      */
extractText(ExtractedTextRequest request, ExtractedText outText)8670     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
8671         createEditorIfNeeded();
8672         return mEditor.extractText(request, outText);
8673     }
8674 
8675     /**
8676      * This is used to remove all style-impacting spans from text before new
8677      * extracted text is being replaced into it, so that we don't have any
8678      * lingering spans applied during the replace.
8679      */
removeParcelableSpans(Spannable spannable, int start, int end)8680     static void removeParcelableSpans(Spannable spannable, int start, int end) {
8681         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
8682         int i = spans.length;
8683         while (i > 0) {
8684             i--;
8685             spannable.removeSpan(spans[i]);
8686         }
8687     }
8688 
8689     /**
8690      * Apply to this text view the given extracted text, as previously
8691      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
8692      */
setExtractedText(ExtractedText text)8693     public void setExtractedText(ExtractedText text) {
8694         Editable content = getEditableText();
8695         if (text.text != null) {
8696             if (content == null) {
8697                 setText(text.text, TextView.BufferType.EDITABLE);
8698             } else {
8699                 int start = 0;
8700                 int end = content.length();
8701 
8702                 if (text.partialStartOffset >= 0) {
8703                     final int N = content.length();
8704                     start = text.partialStartOffset;
8705                     if (start > N) start = N;
8706                     end = text.partialEndOffset;
8707                     if (end > N) end = N;
8708                 }
8709 
8710                 removeParcelableSpans(content, start, end);
8711                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
8712                     if (text.text instanceof Spanned) {
8713                         // OK to copy spans only.
8714                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
8715                                 Object.class, content, start);
8716                     }
8717                 } else {
8718                     content.replace(start, end, text.text);
8719                 }
8720             }
8721         }
8722 
8723         // Now set the selection position...  make sure it is in range, to
8724         // avoid crashes.  If this is a partial update, it is possible that
8725         // the underlying text may have changed, causing us problems here.
8726         // Also we just don't want to trust clients to do the right thing.
8727         Spannable sp = (Spannable) getText();
8728         final int N = sp.length();
8729         int start = text.selectionStart;
8730         if (start < 0) {
8731             start = 0;
8732         } else if (start > N) {
8733             start = N;
8734         }
8735         int end = text.selectionEnd;
8736         if (end < 0) {
8737             end = 0;
8738         } else if (end > N) {
8739             end = N;
8740         }
8741         Selection.setSelection(sp, start, end);
8742 
8743         // Finally, update the selection mode.
8744         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
8745             MetaKeyKeyListener.startSelecting(this, sp);
8746         } else {
8747             MetaKeyKeyListener.stopSelecting(this, sp);
8748         }
8749 
8750         setHintInternal(text.hint);
8751     }
8752 
8753     /**
8754      * @hide
8755      */
setExtracting(ExtractedTextRequest req)8756     public void setExtracting(ExtractedTextRequest req) {
8757         if (mEditor.mInputMethodState != null) {
8758             mEditor.mInputMethodState.mExtractedTextRequest = req;
8759         }
8760         // This would stop a possible selection mode, but no such mode is started in case
8761         // extracted mode will start. Some text is selected though, and will trigger an action mode
8762         // in the extracted view.
8763         mEditor.hideCursorAndSpanControllers();
8764         stopTextActionMode();
8765         if (mEditor.mSelectionModifierCursorController != null) {
8766             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
8767         }
8768     }
8769 
8770     /**
8771      * Called by the framework in response to a text completion from
8772      * the current input method, provided by it calling
8773      * {@link InputConnection#commitCompletion
8774      * InputConnection.commitCompletion()}.  The default implementation does
8775      * nothing; text views that are supporting auto-completion should override
8776      * this to do their desired behavior.
8777      *
8778      * @param text The auto complete text the user has selected.
8779      */
onCommitCompletion(CompletionInfo text)8780     public void onCommitCompletion(CompletionInfo text) {
8781         // intentionally empty
8782     }
8783 
8784     /**
8785      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
8786      * dictionary) from the current input method, provided by it calling
8787      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
8788      * The default implementation flashes the background of the corrected word to provide
8789      * feedback to the user.
8790      *
8791      * @param info The auto correct info about the text that was corrected.
8792      */
onCommitCorrection(CorrectionInfo info)8793     public void onCommitCorrection(CorrectionInfo info) {
8794         if (mEditor != null) mEditor.onCommitCorrection(info);
8795     }
8796 
beginBatchEdit()8797     public void beginBatchEdit() {
8798         if (mEditor != null) mEditor.beginBatchEdit();
8799     }
8800 
endBatchEdit()8801     public void endBatchEdit() {
8802         if (mEditor != null) mEditor.endBatchEdit();
8803     }
8804 
8805     /**
8806      * Called by the framework in response to a request to begin a batch
8807      * of edit operations through a call to link {@link #beginBatchEdit()}.
8808      */
onBeginBatchEdit()8809     public void onBeginBatchEdit() {
8810         // intentionally empty
8811     }
8812 
8813     /**
8814      * Called by the framework in response to a request to end a batch
8815      * of edit operations through a call to link {@link #endBatchEdit}.
8816      */
onEndBatchEdit()8817     public void onEndBatchEdit() {
8818         // intentionally empty
8819     }
8820 
8821     /**
8822      * Called by the framework in response to a private command from the
8823      * current method, provided by it calling
8824      * {@link InputConnection#performPrivateCommand
8825      * InputConnection.performPrivateCommand()}.
8826      *
8827      * @param action The action name of the command.
8828      * @param data Any additional data for the command.  This may be null.
8829      * @return Return true if you handled the command, else false.
8830      */
onPrivateIMECommand(String action, Bundle data)8831     public boolean onPrivateIMECommand(String action, Bundle data) {
8832         return false;
8833     }
8834 
8835     /** @hide */
8836     @VisibleForTesting
8837     @UnsupportedAppUsage
nullLayouts()8838     public void nullLayouts() {
8839         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
8840             mSavedLayout = (BoringLayout) mLayout;
8841         }
8842         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
8843             mSavedHintLayout = (BoringLayout) mHintLayout;
8844         }
8845 
8846         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
8847 
8848         mBoring = mHintBoring = null;
8849 
8850         // Since it depends on the value of mLayout
8851         if (mEditor != null) mEditor.prepareCursorControllers();
8852     }
8853 
8854     /**
8855      * Make a new Layout based on the already-measured size of the view,
8856      * on the assumption that it was measured correctly at some point.
8857      */
8858     @UnsupportedAppUsage
assumeLayout()8859     private void assumeLayout() {
8860         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8861 
8862         if (width < 1) {
8863             width = 0;
8864         }
8865 
8866         int physicalWidth = width;
8867 
8868         if (mHorizontallyScrolling) {
8869             width = VERY_WIDE;
8870         }
8871 
8872         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
8873                       physicalWidth, false);
8874     }
8875 
8876     @UnsupportedAppUsage
getLayoutAlignment()8877     private Layout.Alignment getLayoutAlignment() {
8878         Layout.Alignment alignment;
8879         switch (getTextAlignment()) {
8880             case TEXT_ALIGNMENT_GRAVITY:
8881                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
8882                     case Gravity.START:
8883                         alignment = Layout.Alignment.ALIGN_NORMAL;
8884                         break;
8885                     case Gravity.END:
8886                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
8887                         break;
8888                     case Gravity.LEFT:
8889                         alignment = Layout.Alignment.ALIGN_LEFT;
8890                         break;
8891                     case Gravity.RIGHT:
8892                         alignment = Layout.Alignment.ALIGN_RIGHT;
8893                         break;
8894                     case Gravity.CENTER_HORIZONTAL:
8895                         alignment = Layout.Alignment.ALIGN_CENTER;
8896                         break;
8897                     default:
8898                         alignment = Layout.Alignment.ALIGN_NORMAL;
8899                         break;
8900                 }
8901                 break;
8902             case TEXT_ALIGNMENT_TEXT_START:
8903                 alignment = Layout.Alignment.ALIGN_NORMAL;
8904                 break;
8905             case TEXT_ALIGNMENT_TEXT_END:
8906                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
8907                 break;
8908             case TEXT_ALIGNMENT_CENTER:
8909                 alignment = Layout.Alignment.ALIGN_CENTER;
8910                 break;
8911             case TEXT_ALIGNMENT_VIEW_START:
8912                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8913                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8914                 break;
8915             case TEXT_ALIGNMENT_VIEW_END:
8916                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8917                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8918                 break;
8919             case TEXT_ALIGNMENT_INHERIT:
8920                 // This should never happen as we have already resolved the text alignment
8921                 // but better safe than sorry so we just fall through
8922             default:
8923                 alignment = Layout.Alignment.ALIGN_NORMAL;
8924                 break;
8925         }
8926         return alignment;
8927     }
8928 
8929     /**
8930      * The width passed in is now the desired layout width,
8931      * not the full view width with padding.
8932      * {@hide}
8933      */
8934     @VisibleForTesting
8935     @UnsupportedAppUsage
makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8936     public void makeNewLayout(int wantWidth, int hintWidth,
8937                                  BoringLayout.Metrics boring,
8938                                  BoringLayout.Metrics hintBoring,
8939                                  int ellipsisWidth, boolean bringIntoView) {
8940         stopMarquee();
8941 
8942         // Update "old" cached values
8943         mOldMaximum = mMaximum;
8944         mOldMaxMode = mMaxMode;
8945 
8946         mHighlightPathBogus = true;
8947 
8948         if (wantWidth < 0) {
8949             wantWidth = 0;
8950         }
8951         if (hintWidth < 0) {
8952             hintWidth = 0;
8953         }
8954 
8955         Layout.Alignment alignment = getLayoutAlignment();
8956         final boolean testDirChange = mSingleLine && mLayout != null
8957                 && (alignment == Layout.Alignment.ALIGN_NORMAL
8958                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
8959         int oldDir = 0;
8960         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
8961         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
8962         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
8963                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
8964         TruncateAt effectiveEllipsize = mEllipsize;
8965         if (mEllipsize == TruncateAt.MARQUEE
8966                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8967             effectiveEllipsize = TruncateAt.END_SMALL;
8968         }
8969 
8970         if (mTextDir == null) {
8971             mTextDir = getTextDirectionHeuristic();
8972         }
8973 
8974         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
8975                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
8976         if (switchEllipsize) {
8977             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
8978                     ? TruncateAt.END : TruncateAt.MARQUEE;
8979             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
8980                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
8981         }
8982 
8983         shouldEllipsize = mEllipsize != null;
8984         mHintLayout = null;
8985 
8986         if (mHint != null) {
8987             if (shouldEllipsize) hintWidth = wantWidth;
8988 
8989             if (hintBoring == UNKNOWN_BORING) {
8990                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
8991                                                    mHintBoring);
8992                 if (hintBoring != null) {
8993                     mHintBoring = hintBoring;
8994                 }
8995             }
8996 
8997             if (hintBoring != null) {
8998                 if (hintBoring.width <= hintWidth
8999                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
9000                     if (mSavedHintLayout != null) {
9001                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9002                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9003                                 hintBoring, mIncludePad);
9004                     } else {
9005                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9006                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9007                                 hintBoring, mIncludePad);
9008                     }
9009 
9010                     mSavedHintLayout = (BoringLayout) mHintLayout;
9011                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
9012                     if (mSavedHintLayout != null) {
9013                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9014                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9015                                 hintBoring, mIncludePad, mEllipsize,
9016                                 ellipsisWidth);
9017                     } else {
9018                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9019                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9020                                 hintBoring, mIncludePad, mEllipsize,
9021                                 ellipsisWidth);
9022                     }
9023                 }
9024             }
9025             // TODO: code duplication with makeSingleLayout()
9026             if (mHintLayout == null) {
9027                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
9028                         mHint.length(), mTextPaint, hintWidth)
9029                         .setAlignment(alignment)
9030                         .setTextDirection(mTextDir)
9031                         .setLineSpacing(mSpacingAdd, mSpacingMult)
9032                         .setIncludePad(mIncludePad)
9033                         .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9034                         .setBreakStrategy(mBreakStrategy)
9035                         .setHyphenationFrequency(mHyphenationFrequency)
9036                         .setJustificationMode(mJustificationMode)
9037                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9038                 if (shouldEllipsize) {
9039                     builder.setEllipsize(mEllipsize)
9040                             .setEllipsizedWidth(ellipsisWidth);
9041                 }
9042                 mHintLayout = builder.build();
9043             }
9044         }
9045 
9046         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
9047             registerForPreDraw();
9048         }
9049 
9050         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9051             if (!compressText(ellipsisWidth)) {
9052                 final int height = mLayoutParams.height;
9053                 // If the size of the view does not depend on the size of the text, try to
9054                 // start the marquee immediately
9055                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
9056                     startMarquee();
9057                 } else {
9058                     // Defer the start of the marquee until we know our width (see setFrame())
9059                     mRestartMarquee = true;
9060                 }
9061             }
9062         }
9063 
9064         // CursorControllers need a non-null mLayout
9065         if (mEditor != null) mEditor.prepareCursorControllers();
9066     }
9067 
9068     /**
9069      * Returns true if DynamicLayout is required
9070      *
9071      * @hide
9072      */
9073     @VisibleForTesting
useDynamicLayout()9074     public boolean useDynamicLayout() {
9075         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
9076     }
9077 
9078     /**
9079      * @hide
9080      */
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9081     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
9082             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
9083             boolean useSaved) {
9084         Layout result = null;
9085         if (useDynamicLayout()) {
9086             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
9087                     wantWidth)
9088                     .setDisplayText(mTransformed)
9089                     .setAlignment(alignment)
9090                     .setTextDirection(mTextDir)
9091                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9092                     .setIncludePad(mIncludePad)
9093                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9094                     .setBreakStrategy(mBreakStrategy)
9095                     .setHyphenationFrequency(mHyphenationFrequency)
9096                     .setJustificationMode(mJustificationMode)
9097                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
9098                     .setEllipsizedWidth(ellipsisWidth);
9099             result = builder.build();
9100         } else {
9101             if (boring == UNKNOWN_BORING) {
9102                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9103                 if (boring != null) {
9104                     mBoring = boring;
9105                 }
9106             }
9107 
9108             if (boring != null) {
9109                 if (boring.width <= wantWidth
9110                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
9111                     if (useSaved && mSavedLayout != null) {
9112                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9113                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9114                                 boring, mIncludePad);
9115                     } else {
9116                         result = BoringLayout.make(mTransformed, mTextPaint,
9117                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9118                                 boring, mIncludePad);
9119                     }
9120 
9121                     if (useSaved) {
9122                         mSavedLayout = (BoringLayout) result;
9123                     }
9124                 } else if (shouldEllipsize && boring.width <= wantWidth) {
9125                     if (useSaved && mSavedLayout != null) {
9126                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9127                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9128                                 boring, mIncludePad, effectiveEllipsize,
9129                                 ellipsisWidth);
9130                     } else {
9131                         result = BoringLayout.make(mTransformed, mTextPaint,
9132                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9133                                 boring, mIncludePad, effectiveEllipsize,
9134                                 ellipsisWidth);
9135                     }
9136                 }
9137             }
9138         }
9139         if (result == null) {
9140             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
9141                     0, mTransformed.length(), mTextPaint, wantWidth)
9142                     .setAlignment(alignment)
9143                     .setTextDirection(mTextDir)
9144                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9145                     .setIncludePad(mIncludePad)
9146                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9147                     .setBreakStrategy(mBreakStrategy)
9148                     .setHyphenationFrequency(mHyphenationFrequency)
9149                     .setJustificationMode(mJustificationMode)
9150                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9151             if (shouldEllipsize) {
9152                 builder.setEllipsize(effectiveEllipsize)
9153                         .setEllipsizedWidth(ellipsisWidth);
9154             }
9155             result = builder.build();
9156         }
9157         return result;
9158     }
9159 
9160     @UnsupportedAppUsage
compressText(float width)9161     private boolean compressText(float width) {
9162         if (isHardwareAccelerated()) return false;
9163 
9164         // Only compress the text if it hasn't been compressed by the previous pass
9165         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
9166                 && mTextPaint.getTextScaleX() == 1.0f) {
9167             final float textWidth = mLayout.getLineWidth(0);
9168             final float overflow = (textWidth + 1.0f - width) / width;
9169             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
9170                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
9171                 post(new Runnable() {
9172                     public void run() {
9173                         requestLayout();
9174                     }
9175                 });
9176                 return true;
9177             }
9178         }
9179 
9180         return false;
9181     }
9182 
desired(Layout layout)9183     private static int desired(Layout layout) {
9184         int n = layout.getLineCount();
9185         CharSequence text = layout.getText();
9186         float max = 0;
9187 
9188         // if any line was wrapped, we can't use it.
9189         // but it's ok for the last line not to have a newline
9190 
9191         for (int i = 0; i < n - 1; i++) {
9192             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
9193                 return -1;
9194             }
9195         }
9196 
9197         for (int i = 0; i < n; i++) {
9198             max = Math.max(max, layout.getLineWidth(i));
9199         }
9200 
9201         return (int) Math.ceil(max);
9202     }
9203 
9204     /**
9205      * Set whether the TextView includes extra top and bottom padding to make
9206      * room for accents that go above the normal ascent and descent.
9207      * The default is true.
9208      *
9209      * @see #getIncludeFontPadding()
9210      *
9211      * @attr ref android.R.styleable#TextView_includeFontPadding
9212      */
setIncludeFontPadding(boolean includepad)9213     public void setIncludeFontPadding(boolean includepad) {
9214         if (mIncludePad != includepad) {
9215             mIncludePad = includepad;
9216 
9217             if (mLayout != null) {
9218                 nullLayouts();
9219                 requestLayout();
9220                 invalidate();
9221             }
9222         }
9223     }
9224 
9225     /**
9226      * Gets whether the TextView includes extra top and bottom padding to make
9227      * room for accents that go above the normal ascent and descent.
9228      *
9229      * @see #setIncludeFontPadding(boolean)
9230      *
9231      * @attr ref android.R.styleable#TextView_includeFontPadding
9232      */
9233     @InspectableProperty
getIncludeFontPadding()9234     public boolean getIncludeFontPadding() {
9235         return mIncludePad;
9236     }
9237 
9238     /** @hide */
9239     @VisibleForTesting
9240     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
9241 
9242     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)9243     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
9244         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
9245         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
9246         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
9247         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
9248 
9249         int width;
9250         int height;
9251 
9252         BoringLayout.Metrics boring = UNKNOWN_BORING;
9253         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
9254 
9255         if (mTextDir == null) {
9256             mTextDir = getTextDirectionHeuristic();
9257         }
9258 
9259         int des = -1;
9260         boolean fromexisting = false;
9261         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
9262                 ?  (float) widthSize : Float.MAX_VALUE;
9263 
9264         if (widthMode == MeasureSpec.EXACTLY) {
9265             // Parent has told us how big to be. So be it.
9266             width = widthSize;
9267         } else {
9268             if (mLayout != null && mEllipsize == null) {
9269                 des = desired(mLayout);
9270             }
9271 
9272             if (des < 0) {
9273                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9274                 if (boring != null) {
9275                     mBoring = boring;
9276                 }
9277             } else {
9278                 fromexisting = true;
9279             }
9280 
9281             if (boring == null || boring == UNKNOWN_BORING) {
9282                 if (des < 0) {
9283                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
9284                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
9285                 }
9286                 width = des;
9287             } else {
9288                 width = boring.width;
9289             }
9290 
9291             final Drawables dr = mDrawables;
9292             if (dr != null) {
9293                 width = Math.max(width, dr.mDrawableWidthTop);
9294                 width = Math.max(width, dr.mDrawableWidthBottom);
9295             }
9296 
9297             if (mHint != null) {
9298                 int hintDes = -1;
9299                 int hintWidth;
9300 
9301                 if (mHintLayout != null && mEllipsize == null) {
9302                     hintDes = desired(mHintLayout);
9303                 }
9304 
9305                 if (hintDes < 0) {
9306                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
9307                     if (hintBoring != null) {
9308                         mHintBoring = hintBoring;
9309                     }
9310                 }
9311 
9312                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
9313                     if (hintDes < 0) {
9314                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
9315                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
9316                     }
9317                     hintWidth = hintDes;
9318                 } else {
9319                     hintWidth = hintBoring.width;
9320                 }
9321 
9322                 if (hintWidth > width) {
9323                     width = hintWidth;
9324                 }
9325             }
9326 
9327             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
9328 
9329             if (mMaxWidthMode == EMS) {
9330                 width = Math.min(width, mMaxWidth * getLineHeight());
9331             } else {
9332                 width = Math.min(width, mMaxWidth);
9333             }
9334 
9335             if (mMinWidthMode == EMS) {
9336                 width = Math.max(width, mMinWidth * getLineHeight());
9337             } else {
9338                 width = Math.max(width, mMinWidth);
9339             }
9340 
9341             // Check against our minimum width
9342             width = Math.max(width, getSuggestedMinimumWidth());
9343 
9344             if (widthMode == MeasureSpec.AT_MOST) {
9345                 width = Math.min(widthSize, width);
9346             }
9347         }
9348 
9349         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
9350         int unpaddedWidth = want;
9351 
9352         if (mHorizontallyScrolling) want = VERY_WIDE;
9353 
9354         int hintWant = want;
9355         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
9356 
9357         if (mLayout == null) {
9358             makeNewLayout(want, hintWant, boring, hintBoring,
9359                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9360         } else {
9361             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
9362                     || (mLayout.getEllipsizedWidth()
9363                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
9364 
9365             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
9366                     && (want > mLayout.getWidth())
9367                     && (mLayout instanceof BoringLayout
9368                             || (fromexisting && des >= 0 && des <= want));
9369 
9370             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
9371 
9372             if (layoutChanged || maximumChanged) {
9373                 if (!maximumChanged && widthChanged) {
9374                     mLayout.increaseWidthTo(want);
9375                 } else {
9376                     makeNewLayout(want, hintWant, boring, hintBoring,
9377                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9378                 }
9379             } else {
9380                 // Nothing has changed
9381             }
9382         }
9383 
9384         if (heightMode == MeasureSpec.EXACTLY) {
9385             // Parent has told us how big to be. So be it.
9386             height = heightSize;
9387             mDesiredHeightAtMeasure = -1;
9388         } else {
9389             int desired = getDesiredHeight();
9390 
9391             height = desired;
9392             mDesiredHeightAtMeasure = desired;
9393 
9394             if (heightMode == MeasureSpec.AT_MOST) {
9395                 height = Math.min(desired, heightSize);
9396             }
9397         }
9398 
9399         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
9400         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
9401             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
9402         }
9403 
9404         /*
9405          * We didn't let makeNewLayout() register to bring the cursor into view,
9406          * so do it here if there is any possibility that it is needed.
9407          */
9408         if (mMovement != null
9409                 || mLayout.getWidth() > unpaddedWidth
9410                 || mLayout.getHeight() > unpaddedHeight) {
9411             registerForPreDraw();
9412         } else {
9413             scrollTo(0, 0);
9414         }
9415 
9416         setMeasuredDimension(width, height);
9417     }
9418 
9419     /**
9420      * Automatically computes and sets the text size.
9421      */
autoSizeText()9422     private void autoSizeText() {
9423         if (!isAutoSizeEnabled()) {
9424             return;
9425         }
9426 
9427         if (mNeedsAutoSizeText) {
9428             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
9429                 return;
9430             }
9431 
9432             final int availableWidth = mHorizontallyScrolling
9433                     ? VERY_WIDE
9434                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
9435             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
9436                     - getExtendedPaddingTop();
9437 
9438             if (availableWidth <= 0 || availableHeight <= 0) {
9439                 return;
9440             }
9441 
9442             synchronized (TEMP_RECTF) {
9443                 TEMP_RECTF.setEmpty();
9444                 TEMP_RECTF.right = availableWidth;
9445                 TEMP_RECTF.bottom = availableHeight;
9446                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
9447 
9448                 if (optimalTextSize != getTextSize()) {
9449                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
9450                             false /* shouldRequestLayout */);
9451 
9452                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
9453                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9454                             false /* bringIntoView */);
9455                 }
9456             }
9457         }
9458         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
9459         // after the next layout pass should set this to false.
9460         mNeedsAutoSizeText = true;
9461     }
9462 
9463     /**
9464      * Performs a binary search to find the largest text size that will still fit within the size
9465      * available to this view.
9466      */
findLargestTextSizeWhichFits(RectF availableSpace)9467     private int findLargestTextSizeWhichFits(RectF availableSpace) {
9468         final int sizesCount = mAutoSizeTextSizesInPx.length;
9469         if (sizesCount == 0) {
9470             throw new IllegalStateException("No available text sizes to choose from.");
9471         }
9472 
9473         int bestSizeIndex = 0;
9474         int lowIndex = bestSizeIndex + 1;
9475         int highIndex = sizesCount - 1;
9476         int sizeToTryIndex;
9477         while (lowIndex <= highIndex) {
9478             sizeToTryIndex = (lowIndex + highIndex) / 2;
9479             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
9480                 bestSizeIndex = lowIndex;
9481                 lowIndex = sizeToTryIndex + 1;
9482             } else {
9483                 highIndex = sizeToTryIndex - 1;
9484                 bestSizeIndex = highIndex;
9485             }
9486         }
9487 
9488         return mAutoSizeTextSizesInPx[bestSizeIndex];
9489     }
9490 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9491     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
9492         final CharSequence text = mTransformed != null
9493                 ? mTransformed
9494                 : getText();
9495         final int maxLines = getMaxLines();
9496         if (mTempTextPaint == null) {
9497             mTempTextPaint = new TextPaint();
9498         } else {
9499             mTempTextPaint.reset();
9500         }
9501         mTempTextPaint.set(getPaint());
9502         mTempTextPaint.setTextSize(suggestedSizeInPx);
9503 
9504         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
9505                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
9506 
9507         layoutBuilder.setAlignment(getLayoutAlignment())
9508                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
9509                 .setIncludePad(getIncludeFontPadding())
9510                 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9511                 .setBreakStrategy(getBreakStrategy())
9512                 .setHyphenationFrequency(getHyphenationFrequency())
9513                 .setJustificationMode(getJustificationMode())
9514                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9515                 .setTextDirection(getTextDirectionHeuristic());
9516 
9517         final StaticLayout layout = layoutBuilder.build();
9518 
9519         // Lines overflow.
9520         if (maxLines != -1 && layout.getLineCount() > maxLines) {
9521             return false;
9522         }
9523 
9524         // Height overflow.
9525         if (layout.getHeight() > availableSpace.bottom) {
9526             return false;
9527         }
9528 
9529         return true;
9530     }
9531 
getDesiredHeight()9532     private int getDesiredHeight() {
9533         return Math.max(
9534                 getDesiredHeight(mLayout, true),
9535                 getDesiredHeight(mHintLayout, mEllipsize != null));
9536     }
9537 
getDesiredHeight(Layout layout, boolean cap)9538     private int getDesiredHeight(Layout layout, boolean cap) {
9539         if (layout == null) {
9540             return 0;
9541         }
9542 
9543         /*
9544         * Don't cap the hint to a certain number of lines.
9545         * (Do cap it, though, if we have a maximum pixel height.)
9546         */
9547         int desired = layout.getHeight(cap);
9548 
9549         final Drawables dr = mDrawables;
9550         if (dr != null) {
9551             desired = Math.max(desired, dr.mDrawableHeightLeft);
9552             desired = Math.max(desired, dr.mDrawableHeightRight);
9553         }
9554 
9555         int linecount = layout.getLineCount();
9556         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
9557         desired += padding;
9558 
9559         if (mMaxMode != LINES) {
9560             desired = Math.min(desired, mMaximum);
9561         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
9562                 || layout instanceof BoringLayout)) {
9563             desired = layout.getLineTop(mMaximum);
9564 
9565             if (dr != null) {
9566                 desired = Math.max(desired, dr.mDrawableHeightLeft);
9567                 desired = Math.max(desired, dr.mDrawableHeightRight);
9568             }
9569 
9570             desired += padding;
9571             linecount = mMaximum;
9572         }
9573 
9574         if (mMinMode == LINES) {
9575             if (linecount < mMinimum) {
9576                 desired += getLineHeight() * (mMinimum - linecount);
9577             }
9578         } else {
9579             desired = Math.max(desired, mMinimum);
9580         }
9581 
9582         // Check against our minimum height
9583         desired = Math.max(desired, getSuggestedMinimumHeight());
9584 
9585         return desired;
9586     }
9587 
9588     /**
9589      * Check whether a change to the existing text layout requires a
9590      * new view layout.
9591      */
checkForResize()9592     private void checkForResize() {
9593         boolean sizeChanged = false;
9594 
9595         if (mLayout != null) {
9596             // Check if our width changed
9597             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
9598                 sizeChanged = true;
9599                 invalidate();
9600             }
9601 
9602             // Check if our height changed
9603             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
9604                 int desiredHeight = getDesiredHeight();
9605 
9606                 if (desiredHeight != this.getHeight()) {
9607                     sizeChanged = true;
9608                 }
9609             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
9610                 if (mDesiredHeightAtMeasure >= 0) {
9611                     int desiredHeight = getDesiredHeight();
9612 
9613                     if (desiredHeight != mDesiredHeightAtMeasure) {
9614                         sizeChanged = true;
9615                     }
9616                 }
9617             }
9618         }
9619 
9620         if (sizeChanged) {
9621             requestLayout();
9622             // caller will have already invalidated
9623         }
9624     }
9625 
9626     /**
9627      * Check whether entirely new text requires a new view layout
9628      * or merely a new text layout.
9629      */
9630     @UnsupportedAppUsage
checkForRelayout()9631     private void checkForRelayout() {
9632         // If we have a fixed width, we can just swap in a new text layout
9633         // if the text height stays the same or if the view height is fixed.
9634 
9635         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
9636                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
9637                 && (mHint == null || mHintLayout != null)
9638                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
9639             // Static width, so try making a new text layout.
9640 
9641             int oldht = mLayout.getHeight();
9642             int want = mLayout.getWidth();
9643             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
9644 
9645             /*
9646              * No need to bring the text into view, since the size is not
9647              * changing (unless we do the requestLayout(), in which case it
9648              * will happen at measure).
9649              */
9650             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
9651                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9652                           false);
9653 
9654             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
9655                 // In a fixed-height view, so use our new text layout.
9656                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
9657                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
9658                     autoSizeText();
9659                     invalidate();
9660                     return;
9661                 }
9662 
9663                 // Dynamic height, but height has stayed the same,
9664                 // so use our new text layout.
9665                 if (mLayout.getHeight() == oldht
9666                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
9667                     autoSizeText();
9668                     invalidate();
9669                     return;
9670                 }
9671             }
9672 
9673             // We lose: the height has changed and we have a dynamic height.
9674             // Request a new view layout using our new text layout.
9675             requestLayout();
9676             invalidate();
9677         } else {
9678             // Dynamic width, so we have no choice but to request a new
9679             // view layout with a new text layout.
9680             nullLayouts();
9681             requestLayout();
9682             invalidate();
9683         }
9684     }
9685 
9686     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)9687     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
9688         super.onLayout(changed, left, top, right, bottom);
9689         if (mDeferScroll >= 0) {
9690             int curs = mDeferScroll;
9691             mDeferScroll = -1;
9692             bringPointIntoView(Math.min(curs, mText.length()));
9693         }
9694         // Call auto-size after the width and height have been calculated.
9695         autoSizeText();
9696     }
9697 
isShowingHint()9698     private boolean isShowingHint() {
9699         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
9700     }
9701 
9702     /**
9703      * Returns true if anything changed.
9704      */
9705     @UnsupportedAppUsage
bringTextIntoView()9706     private boolean bringTextIntoView() {
9707         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9708         int line = 0;
9709         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9710             line = layout.getLineCount() - 1;
9711         }
9712 
9713         Layout.Alignment a = layout.getParagraphAlignment(line);
9714         int dir = layout.getParagraphDirection(line);
9715         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9716         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9717         int ht = layout.getHeight();
9718 
9719         int scrollx, scrolly;
9720 
9721         // Convert to left, center, or right alignment.
9722         if (a == Layout.Alignment.ALIGN_NORMAL) {
9723             a = dir == Layout.DIR_LEFT_TO_RIGHT
9724                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
9725         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
9726             a = dir == Layout.DIR_LEFT_TO_RIGHT
9727                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
9728         }
9729 
9730         if (a == Layout.Alignment.ALIGN_CENTER) {
9731             /*
9732              * Keep centered if possible, or, if it is too wide to fit,
9733              * keep leading edge in view.
9734              */
9735 
9736             int left = (int) Math.floor(layout.getLineLeft(line));
9737             int right = (int) Math.ceil(layout.getLineRight(line));
9738 
9739             if (right - left < hspace) {
9740                 scrollx = (right + left) / 2 - hspace / 2;
9741             } else {
9742                 if (dir < 0) {
9743                     scrollx = right - hspace;
9744                 } else {
9745                     scrollx = left;
9746                 }
9747             }
9748         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
9749             int right = (int) Math.ceil(layout.getLineRight(line));
9750             scrollx = right - hspace;
9751         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
9752             scrollx = (int) Math.floor(layout.getLineLeft(line));
9753         }
9754 
9755         if (ht < vspace) {
9756             scrolly = 0;
9757         } else {
9758             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9759                 scrolly = ht - vspace;
9760             } else {
9761                 scrolly = 0;
9762             }
9763         }
9764 
9765         if (scrollx != mScrollX || scrolly != mScrollY) {
9766             scrollTo(scrollx, scrolly);
9767             return true;
9768         } else {
9769             return false;
9770         }
9771     }
9772 
9773     /**
9774      * Move the point, specified by the offset, into the view if it is needed.
9775      * This has to be called after layout. Returns true if anything changed.
9776      */
bringPointIntoView(int offset)9777     public boolean bringPointIntoView(int offset) {
9778         if (isLayoutRequested()) {
9779             mDeferScroll = offset;
9780             return false;
9781         }
9782         boolean changed = false;
9783 
9784         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9785 
9786         if (layout == null) return changed;
9787 
9788         int line = layout.getLineForOffset(offset);
9789 
9790         int grav;
9791 
9792         switch (layout.getParagraphAlignment(line)) {
9793             case ALIGN_LEFT:
9794                 grav = 1;
9795                 break;
9796             case ALIGN_RIGHT:
9797                 grav = -1;
9798                 break;
9799             case ALIGN_NORMAL:
9800                 grav = layout.getParagraphDirection(line);
9801                 break;
9802             case ALIGN_OPPOSITE:
9803                 grav = -layout.getParagraphDirection(line);
9804                 break;
9805             case ALIGN_CENTER:
9806             default:
9807                 grav = 0;
9808                 break;
9809         }
9810 
9811         // We only want to clamp the cursor to fit within the layout width
9812         // in left-to-right modes, because in a right to left alignment,
9813         // we want to scroll to keep the line-right on the screen, as other
9814         // lines are likely to have text flush with the right margin, which
9815         // we want to keep visible.
9816         // A better long-term solution would probably be to measure both
9817         // the full line and a blank-trimmed version, and, for example, use
9818         // the latter measurement for centering and right alignment, but for
9819         // the time being we only implement the cursor clamping in left to
9820         // right where it is most likely to be annoying.
9821         final boolean clamped = grav > 0;
9822         // FIXME: Is it okay to truncate this, or should we round?
9823         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
9824         final int top = layout.getLineTop(line);
9825         final int bottom = layout.getLineTop(line + 1);
9826 
9827         int left = (int) Math.floor(layout.getLineLeft(line));
9828         int right = (int) Math.ceil(layout.getLineRight(line));
9829         int ht = layout.getHeight();
9830 
9831         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9832         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9833         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
9834             // If cursor has been clamped, make sure we don't scroll.
9835             right = Math.max(x, left + hspace);
9836         }
9837 
9838         int hslack = (bottom - top) / 2;
9839         int vslack = hslack;
9840 
9841         if (vslack > vspace / 4) {
9842             vslack = vspace / 4;
9843         }
9844         if (hslack > hspace / 4) {
9845             hslack = hspace / 4;
9846         }
9847 
9848         int hs = mScrollX;
9849         int vs = mScrollY;
9850 
9851         if (top - vs < vslack) {
9852             vs = top - vslack;
9853         }
9854         if (bottom - vs > vspace - vslack) {
9855             vs = bottom - (vspace - vslack);
9856         }
9857         if (ht - vs < vspace) {
9858             vs = ht - vspace;
9859         }
9860         if (0 - vs > 0) {
9861             vs = 0;
9862         }
9863 
9864         if (grav != 0) {
9865             if (x - hs < hslack) {
9866                 hs = x - hslack;
9867             }
9868             if (x - hs > hspace - hslack) {
9869                 hs = x - (hspace - hslack);
9870             }
9871         }
9872 
9873         if (grav < 0) {
9874             if (left - hs > 0) {
9875                 hs = left;
9876             }
9877             if (right - hs < hspace) {
9878                 hs = right - hspace;
9879             }
9880         } else if (grav > 0) {
9881             if (right - hs < hspace) {
9882                 hs = right - hspace;
9883             }
9884             if (left - hs > 0) {
9885                 hs = left;
9886             }
9887         } else /* grav == 0 */ {
9888             if (right - left <= hspace) {
9889                 /*
9890                  * If the entire text fits, center it exactly.
9891                  */
9892                 hs = left - (hspace - (right - left)) / 2;
9893             } else if (x > right - hslack) {
9894                 /*
9895                  * If we are near the right edge, keep the right edge
9896                  * at the edge of the view.
9897                  */
9898                 hs = right - hspace;
9899             } else if (x < left + hslack) {
9900                 /*
9901                  * If we are near the left edge, keep the left edge
9902                  * at the edge of the view.
9903                  */
9904                 hs = left;
9905             } else if (left > hs) {
9906                 /*
9907                  * Is there whitespace visible at the left?  Fix it if so.
9908                  */
9909                 hs = left;
9910             } else if (right < hs + hspace) {
9911                 /*
9912                  * Is there whitespace visible at the right?  Fix it if so.
9913                  */
9914                 hs = right - hspace;
9915             } else {
9916                 /*
9917                  * Otherwise, float as needed.
9918                  */
9919                 if (x - hs < hslack) {
9920                     hs = x - hslack;
9921                 }
9922                 if (x - hs > hspace - hslack) {
9923                     hs = x - (hspace - hslack);
9924                 }
9925             }
9926         }
9927 
9928         if (hs != mScrollX || vs != mScrollY) {
9929             if (mScroller == null) {
9930                 scrollTo(hs, vs);
9931             } else {
9932                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
9933                 int dx = hs - mScrollX;
9934                 int dy = vs - mScrollY;
9935 
9936                 if (duration > ANIMATED_SCROLL_GAP) {
9937                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
9938                     awakenScrollBars(mScroller.getDuration());
9939                     invalidate();
9940                 } else {
9941                     if (!mScroller.isFinished()) {
9942                         mScroller.abortAnimation();
9943                     }
9944 
9945                     scrollBy(dx, dy);
9946                 }
9947 
9948                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
9949             }
9950 
9951             changed = true;
9952         }
9953 
9954         if (isFocused()) {
9955             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
9956             // requestRectangleOnScreen() is in terms of content coordinates.
9957 
9958             // The offsets here are to ensure the rectangle we are using is
9959             // within our view bounds, in case the cursor is on the far left
9960             // or right.  If it isn't withing the bounds, then this request
9961             // will be ignored.
9962             if (mTempRect == null) mTempRect = new Rect();
9963             mTempRect.set(x - 2, top, x + 2, bottom);
9964             getInterestingRect(mTempRect, line);
9965             mTempRect.offset(mScrollX, mScrollY);
9966 
9967             if (requestRectangleOnScreen(mTempRect)) {
9968                 changed = true;
9969             }
9970         }
9971 
9972         return changed;
9973     }
9974 
9975     /**
9976      * Move the cursor, if needed, so that it is at an offset that is visible
9977      * to the user.  This will not move the cursor if it represents more than
9978      * one character (a selection range).  This will only work if the
9979      * TextView contains spannable text; otherwise it will do nothing.
9980      *
9981      * @return True if the cursor was actually moved, false otherwise.
9982      */
moveCursorToVisibleOffset()9983     public boolean moveCursorToVisibleOffset() {
9984         if (!(mText instanceof Spannable)) {
9985             return false;
9986         }
9987         int start = getSelectionStart();
9988         int end = getSelectionEnd();
9989         if (start != end) {
9990             return false;
9991         }
9992 
9993         // First: make sure the line is visible on screen:
9994 
9995         int line = mLayout.getLineForOffset(start);
9996 
9997         final int top = mLayout.getLineTop(line);
9998         final int bottom = mLayout.getLineTop(line + 1);
9999         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10000         int vslack = (bottom - top) / 2;
10001         if (vslack > vspace / 4) {
10002             vslack = vspace / 4;
10003         }
10004         final int vs = mScrollY;
10005 
10006         if (top < (vs + vslack)) {
10007             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
10008         } else if (bottom > (vspace + vs - vslack)) {
10009             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
10010         }
10011 
10012         // Next: make sure the character is visible on screen:
10013 
10014         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10015         final int hs = mScrollX;
10016         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
10017         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
10018 
10019         // line might contain bidirectional text
10020         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
10021         final int highChar = leftChar > rightChar ? leftChar : rightChar;
10022 
10023         int newStart = start;
10024         if (newStart < lowChar) {
10025             newStart = lowChar;
10026         } else if (newStart > highChar) {
10027             newStart = highChar;
10028         }
10029 
10030         if (newStart != start) {
10031             Selection.setSelection(mSpannable, newStart);
10032             return true;
10033         }
10034 
10035         return false;
10036     }
10037 
10038     @Override
computeScroll()10039     public void computeScroll() {
10040         if (mScroller != null) {
10041             if (mScroller.computeScrollOffset()) {
10042                 mScrollX = mScroller.getCurrX();
10043                 mScrollY = mScroller.getCurrY();
10044                 invalidateParentCaches();
10045                 postInvalidate();  // So we draw again
10046             }
10047         }
10048     }
10049 
getInterestingRect(Rect r, int line)10050     private void getInterestingRect(Rect r, int line) {
10051         convertFromViewportToContentCoordinates(r);
10052 
10053         // Rectangle can can be expanded on first and last line to take
10054         // padding into account.
10055         // TODO Take left/right padding into account too?
10056         if (line == 0) r.top -= getExtendedPaddingTop();
10057         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
10058     }
10059 
convertFromViewportToContentCoordinates(Rect r)10060     private void convertFromViewportToContentCoordinates(Rect r) {
10061         final int horizontalOffset = viewportToContentHorizontalOffset();
10062         r.left += horizontalOffset;
10063         r.right += horizontalOffset;
10064 
10065         final int verticalOffset = viewportToContentVerticalOffset();
10066         r.top += verticalOffset;
10067         r.bottom += verticalOffset;
10068     }
10069 
viewportToContentHorizontalOffset()10070     int viewportToContentHorizontalOffset() {
10071         return getCompoundPaddingLeft() - mScrollX;
10072     }
10073 
10074     @UnsupportedAppUsage
viewportToContentVerticalOffset()10075     int viewportToContentVerticalOffset() {
10076         int offset = getExtendedPaddingTop() - mScrollY;
10077         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
10078             offset += getVerticalOffset(false);
10079         }
10080         return offset;
10081     }
10082 
10083     @Override
debug(int depth)10084     public void debug(int depth) {
10085         super.debug(depth);
10086 
10087         String output = debugIndent(depth);
10088         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
10089                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
10090                 + "} ";
10091 
10092         if (mText != null) {
10093 
10094             output += "mText=\"" + mText + "\" ";
10095             if (mLayout != null) {
10096                 output += "mLayout width=" + mLayout.getWidth()
10097                         + " height=" + mLayout.getHeight();
10098             }
10099         } else {
10100             output += "mText=NULL";
10101         }
10102         Log.d(VIEW_LOG_TAG, output);
10103     }
10104 
10105     /**
10106      * Convenience for {@link Selection#getSelectionStart}.
10107      */
10108     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()10109     public int getSelectionStart() {
10110         return Selection.getSelectionStart(getText());
10111     }
10112 
10113     /**
10114      * Convenience for {@link Selection#getSelectionEnd}.
10115      */
10116     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()10117     public int getSelectionEnd() {
10118         return Selection.getSelectionEnd(getText());
10119     }
10120 
10121     /**
10122      * Return true iff there is a selection of nonzero length inside this text view.
10123      */
hasSelection()10124     public boolean hasSelection() {
10125         final int selectionStart = getSelectionStart();
10126         final int selectionEnd = getSelectionEnd();
10127 
10128         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
10129     }
10130 
getSelectedText()10131     String getSelectedText() {
10132         if (!hasSelection()) {
10133             return null;
10134         }
10135 
10136         final int start = getSelectionStart();
10137         final int end = getSelectionEnd();
10138         return String.valueOf(
10139                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
10140     }
10141 
10142     /**
10143      * Sets the properties of this field (lines, horizontally scrolling,
10144      * transformation method) to be for a single-line input.
10145      *
10146      * @attr ref android.R.styleable#TextView_singleLine
10147      */
setSingleLine()10148     public void setSingleLine() {
10149         setSingleLine(true);
10150     }
10151 
10152     /**
10153      * Sets the properties of this field to transform input to ALL CAPS
10154      * display. This may use a "small caps" formatting if available.
10155      * This setting will be ignored if this field is editable or selectable.
10156      *
10157      * This call replaces the current transformation method. Disabling this
10158      * will not necessarily restore the previous behavior from before this
10159      * was enabled.
10160      *
10161      * @see #setTransformationMethod(TransformationMethod)
10162      * @attr ref android.R.styleable#TextView_textAllCaps
10163      */
setAllCaps(boolean allCaps)10164     public void setAllCaps(boolean allCaps) {
10165         if (allCaps) {
10166             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
10167         } else {
10168             setTransformationMethod(null);
10169         }
10170     }
10171 
10172     /**
10173      *
10174      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
10175      * @return Whether the current transformation method is for ALL CAPS.
10176      *
10177      * @see #setAllCaps(boolean)
10178      * @see #setTransformationMethod(TransformationMethod)
10179      */
10180     @InspectableProperty(name = "textAllCaps")
isAllCaps()10181     public boolean isAllCaps() {
10182         final TransformationMethod method = getTransformationMethod();
10183         return method != null && method instanceof AllCapsTransformationMethod;
10184     }
10185 
10186     /**
10187      * If true, sets the properties of this field (number of lines, horizontally scrolling,
10188      * transformation method) to be for a single-line input; if false, restores these to the default
10189      * conditions.
10190      *
10191      * Note that the default conditions are not necessarily those that were in effect prior this
10192      * method, and you may want to reset these properties to your custom values.
10193      *
10194      * @attr ref android.R.styleable#TextView_singleLine
10195      */
10196     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)10197     public void setSingleLine(boolean singleLine) {
10198         // Could be used, but may break backward compatibility.
10199         // if (mSingleLine == singleLine) return;
10200         setInputTypeSingleLine(singleLine);
10201         applySingleLine(singleLine, true, true);
10202     }
10203 
10204     /**
10205      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
10206      * @param singleLine
10207      */
setInputTypeSingleLine(boolean singleLine)10208     private void setInputTypeSingleLine(boolean singleLine) {
10209         if (mEditor != null
10210                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
10211                         == EditorInfo.TYPE_CLASS_TEXT) {
10212             if (singleLine) {
10213                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10214             } else {
10215                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10216             }
10217         }
10218     }
10219 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)10220     private void applySingleLine(boolean singleLine, boolean applyTransformation,
10221             boolean changeMaxLines) {
10222         mSingleLine = singleLine;
10223         if (singleLine) {
10224             setLines(1);
10225             setHorizontallyScrolling(true);
10226             if (applyTransformation) {
10227                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
10228             }
10229         } else {
10230             if (changeMaxLines) {
10231                 setMaxLines(Integer.MAX_VALUE);
10232             }
10233             setHorizontallyScrolling(false);
10234             if (applyTransformation) {
10235                 setTransformationMethod(null);
10236             }
10237         }
10238     }
10239 
10240     /**
10241      * Causes words in the text that are longer than the view's width
10242      * to be ellipsized instead of broken in the middle.  You may also
10243      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
10244      * to constrain the text to a single line.  Use <code>null</code>
10245      * to turn off ellipsizing.
10246      *
10247      * If {@link #setMaxLines} has been used to set two or more lines,
10248      * only {@link android.text.TextUtils.TruncateAt#END} and
10249      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
10250      * (other ellipsizing types will not do anything).
10251      *
10252      * @attr ref android.R.styleable#TextView_ellipsize
10253      */
setEllipsize(TextUtils.TruncateAt where)10254     public void setEllipsize(TextUtils.TruncateAt where) {
10255         // TruncateAt is an enum. != comparison is ok between these singleton objects.
10256         if (mEllipsize != where) {
10257             mEllipsize = where;
10258 
10259             if (mLayout != null) {
10260                 nullLayouts();
10261                 requestLayout();
10262                 invalidate();
10263             }
10264         }
10265     }
10266 
10267     /**
10268      * Sets how many times to repeat the marquee animation. Only applied if the
10269      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
10270      *
10271      * @see #getMarqueeRepeatLimit()
10272      *
10273      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10274      */
setMarqueeRepeatLimit(int marqueeLimit)10275     public void setMarqueeRepeatLimit(int marqueeLimit) {
10276         mMarqueeRepeatLimit = marqueeLimit;
10277     }
10278 
10279     /**
10280      * Gets the number of times the marquee animation is repeated. Only meaningful if the
10281      * TextView has marquee enabled.
10282      *
10283      * @return the number of times the marquee animation is repeated. -1 if the animation
10284      * repeats indefinitely
10285      *
10286      * @see #setMarqueeRepeatLimit(int)
10287      *
10288      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10289      */
10290     @InspectableProperty
getMarqueeRepeatLimit()10291     public int getMarqueeRepeatLimit() {
10292         return mMarqueeRepeatLimit;
10293     }
10294 
10295     /**
10296      * Returns where, if anywhere, words that are longer than the view
10297      * is wide should be ellipsized.
10298      */
10299     @InspectableProperty
10300     @ViewDebug.ExportedProperty
getEllipsize()10301     public TextUtils.TruncateAt getEllipsize() {
10302         return mEllipsize;
10303     }
10304 
10305     /**
10306      * Set the TextView so that when it takes focus, all the text is
10307      * selected.
10308      *
10309      * @attr ref android.R.styleable#TextView_selectAllOnFocus
10310      */
10311     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)10312     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
10313         createEditorIfNeeded();
10314         mEditor.mSelectAllOnFocus = selectAllOnFocus;
10315 
10316         if (selectAllOnFocus && !(mText instanceof Spannable)) {
10317             setText(mText, BufferType.SPANNABLE);
10318         }
10319     }
10320 
10321     /**
10322      * Set whether the cursor is visible. The default is true. Note that this property only
10323      * makes sense for editable TextView.
10324      *
10325      * @see #isCursorVisible()
10326      *
10327      * @attr ref android.R.styleable#TextView_cursorVisible
10328      */
10329     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)10330     public void setCursorVisible(boolean visible) {
10331         if (visible && mEditor == null) return; // visible is the default value with no edit data
10332         createEditorIfNeeded();
10333         if (mEditor.mCursorVisible != visible) {
10334             mEditor.mCursorVisible = visible;
10335             invalidate();
10336 
10337             mEditor.makeBlink();
10338 
10339             // InsertionPointCursorController depends on mCursorVisible
10340             mEditor.prepareCursorControllers();
10341         }
10342     }
10343 
10344     /**
10345      * @return whether or not the cursor is visible (assuming this TextView is editable)
10346      *
10347      * @see #setCursorVisible(boolean)
10348      *
10349      * @attr ref android.R.styleable#TextView_cursorVisible
10350      */
10351     @InspectableProperty
isCursorVisible()10352     public boolean isCursorVisible() {
10353         // true is the default value
10354         return mEditor == null ? true : mEditor.mCursorVisible;
10355     }
10356 
canMarquee()10357     private boolean canMarquee() {
10358         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10359         return width > 0 && (mLayout.getLineWidth(0) > width
10360                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
10361                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
10362     }
10363 
10364     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()10365     private void startMarquee() {
10366         // Do not ellipsize EditText
10367         if (getKeyListener() != null) return;
10368 
10369         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
10370             return;
10371         }
10372 
10373         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
10374                 && getLineCount() == 1 && canMarquee()) {
10375 
10376             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10377                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
10378                 final Layout tmp = mLayout;
10379                 mLayout = mSavedMarqueeModeLayout;
10380                 mSavedMarqueeModeLayout = tmp;
10381                 setHorizontalFadingEdgeEnabled(true);
10382                 requestLayout();
10383                 invalidate();
10384             }
10385 
10386             if (mMarquee == null) mMarquee = new Marquee(this);
10387             mMarquee.start(mMarqueeRepeatLimit);
10388         }
10389     }
10390 
stopMarquee()10391     private void stopMarquee() {
10392         if (mMarquee != null && !mMarquee.isStopped()) {
10393             mMarquee.stop();
10394         }
10395 
10396         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
10397             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10398             final Layout tmp = mSavedMarqueeModeLayout;
10399             mSavedMarqueeModeLayout = mLayout;
10400             mLayout = tmp;
10401             setHorizontalFadingEdgeEnabled(false);
10402             requestLayout();
10403             invalidate();
10404         }
10405     }
10406 
10407     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)10408     private void startStopMarquee(boolean start) {
10409         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10410             if (start) {
10411                 startMarquee();
10412             } else {
10413                 stopMarquee();
10414             }
10415         }
10416     }
10417 
10418     /**
10419      * This method is called when the text is changed, in case any subclasses
10420      * would like to know.
10421      *
10422      * Within <code>text</code>, the <code>lengthAfter</code> characters
10423      * beginning at <code>start</code> have just replaced old text that had
10424      * length <code>lengthBefore</code>. It is an error to attempt to make
10425      * changes to <code>text</code> from this callback.
10426      *
10427      * @param text The text the TextView is displaying
10428      * @param start The offset of the start of the range of the text that was
10429      * modified
10430      * @param lengthBefore The length of the former text that has been replaced
10431      * @param lengthAfter The length of the replacement modified text
10432      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10433     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
10434         // intentionally empty, template pattern method can be overridden by subclasses
10435     }
10436 
10437     /**
10438      * This method is called when the selection has changed, in case any
10439      * subclasses would like to know.
10440      *
10441      * @param selStart The new selection start location.
10442      * @param selEnd The new selection end location.
10443      */
onSelectionChanged(int selStart, int selEnd)10444     protected void onSelectionChanged(int selStart, int selEnd) {
10445         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
10446     }
10447 
10448     /**
10449      * Adds a TextWatcher to the list of those whose methods are called
10450      * whenever this TextView's text changes.
10451      * <p>
10452      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
10453      * not called after {@link #setText} calls.  Now, doing {@link #setText}
10454      * if there are any text changed listeners forces the buffer type to
10455      * Editable if it would not otherwise be and does call this method.
10456      */
addTextChangedListener(TextWatcher watcher)10457     public void addTextChangedListener(TextWatcher watcher) {
10458         if (mListeners == null) {
10459             mListeners = new ArrayList<TextWatcher>();
10460         }
10461 
10462         mListeners.add(watcher);
10463     }
10464 
10465     /**
10466      * Removes the specified TextWatcher from the list of those whose
10467      * methods are called
10468      * whenever this TextView's text changes.
10469      */
removeTextChangedListener(TextWatcher watcher)10470     public void removeTextChangedListener(TextWatcher watcher) {
10471         if (mListeners != null) {
10472             int i = mListeners.indexOf(watcher);
10473 
10474             if (i >= 0) {
10475                 mListeners.remove(i);
10476             }
10477         }
10478     }
10479 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)10480     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
10481         if (mListeners != null) {
10482             final ArrayList<TextWatcher> list = mListeners;
10483             final int count = list.size();
10484             for (int i = 0; i < count; i++) {
10485                 list.get(i).beforeTextChanged(text, start, before, after);
10486             }
10487         }
10488 
10489         // The spans that are inside or intersect the modified region no longer make sense
10490         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
10491         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
10492     }
10493 
10494     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10495     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
10496         if (!(mText instanceof Editable)) return;
10497         Editable text = (Editable) mText;
10498 
10499         T[] spans = text.getSpans(start, end, type);
10500         final int length = spans.length;
10501         for (int i = 0; i < length; i++) {
10502             final int spanStart = text.getSpanStart(spans[i]);
10503             final int spanEnd = text.getSpanEnd(spans[i]);
10504             if (spanEnd == start || spanStart == end) break;
10505             text.removeSpan(spans[i]);
10506         }
10507     }
10508 
removeAdjacentSuggestionSpans(final int pos)10509     void removeAdjacentSuggestionSpans(final int pos) {
10510         if (!(mText instanceof Editable)) return;
10511         final Editable text = (Editable) mText;
10512 
10513         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
10514         final int length = spans.length;
10515         for (int i = 0; i < length; i++) {
10516             final int spanStart = text.getSpanStart(spans[i]);
10517             final int spanEnd = text.getSpanEnd(spans[i]);
10518             if (spanEnd == pos || spanStart == pos) {
10519                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
10520                     text.removeSpan(spans[i]);
10521                 }
10522             }
10523         }
10524     }
10525 
10526     /**
10527      * Not private so it can be called from an inner class without going
10528      * through a thunk.
10529      */
sendOnTextChanged(CharSequence text, int start, int before, int after)10530     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
10531         if (mListeners != null) {
10532             final ArrayList<TextWatcher> list = mListeners;
10533             final int count = list.size();
10534             for (int i = 0; i < count; i++) {
10535                 list.get(i).onTextChanged(text, start, before, after);
10536             }
10537         }
10538 
10539         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
10540     }
10541 
10542     /**
10543      * Not private so it can be called from an inner class without going
10544      * through a thunk.
10545      */
sendAfterTextChanged(Editable text)10546     void sendAfterTextChanged(Editable text) {
10547         if (mListeners != null) {
10548             final ArrayList<TextWatcher> list = mListeners;
10549             final int count = list.size();
10550             for (int i = 0; i < count; i++) {
10551                 list.get(i).afterTextChanged(text);
10552             }
10553         }
10554 
10555         notifyListeningManagersAfterTextChanged();
10556 
10557         hideErrorIfUnchanged();
10558     }
10559 
10560     /**
10561      * Notify managers (such as {@link AutofillManager}) that are interested in text changes.
10562      */
notifyListeningManagersAfterTextChanged()10563     private void notifyListeningManagersAfterTextChanged() {
10564 
10565         // Autofill
10566         if (isAutofillable()) {
10567             // It is important to not check whether the view is important for autofill
10568             // since the user can trigger autofill manually on not important views.
10569             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10570             if (afm != null) {
10571                 if (android.view.autofill.Helper.sVerbose) {
10572                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
10573                 }
10574                 afm.notifyValueChanged(TextView.this);
10575             }
10576         }
10577     }
10578 
isAutofillable()10579     private boolean isAutofillable() {
10580         // It is important to not check whether the view is important for autofill
10581         // since the user can trigger autofill manually on not important views.
10582         return getAutofillType() != AUTOFILL_TYPE_NONE;
10583     }
10584 
updateAfterEdit()10585     void updateAfterEdit() {
10586         invalidate();
10587         int curs = getSelectionStart();
10588 
10589         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
10590             registerForPreDraw();
10591         }
10592 
10593         checkForResize();
10594 
10595         if (curs >= 0) {
10596             mHighlightPathBogus = true;
10597             if (mEditor != null) mEditor.makeBlink();
10598             bringPointIntoView(curs);
10599         }
10600     }
10601 
10602     /**
10603      * Not private so it can be called from an inner class without going
10604      * through a thunk.
10605      */
handleTextChanged(CharSequence buffer, int start, int before, int after)10606     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
10607         sLastCutCopyOrTextChangedTime = 0;
10608 
10609         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10610         if (ims == null || ims.mBatchEditNesting == 0) {
10611             updateAfterEdit();
10612         }
10613         if (ims != null) {
10614             ims.mContentChanged = true;
10615             if (ims.mChangedStart < 0) {
10616                 ims.mChangedStart = start;
10617                 ims.mChangedEnd = start + before;
10618             } else {
10619                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
10620                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
10621             }
10622             ims.mChangedDelta += after - before;
10623         }
10624         resetErrorChangedFlag();
10625         sendOnTextChanged(buffer, start, before, after);
10626         onTextChanged(buffer, start, before, after);
10627     }
10628 
10629     /**
10630      * Not private so it can be called from an inner class without going
10631      * through a thunk.
10632      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10633     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
10634         // XXX Make the start and end move together if this ends up
10635         // spending too much time invalidating.
10636 
10637         boolean selChanged = false;
10638         int newSelStart = -1, newSelEnd = -1;
10639 
10640         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10641 
10642         if (what == Selection.SELECTION_END) {
10643             selChanged = true;
10644             newSelEnd = newStart;
10645 
10646             if (oldStart >= 0 || newStart >= 0) {
10647                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
10648                 checkForResize();
10649                 registerForPreDraw();
10650                 if (mEditor != null) mEditor.makeBlink();
10651             }
10652         }
10653 
10654         if (what == Selection.SELECTION_START) {
10655             selChanged = true;
10656             newSelStart = newStart;
10657 
10658             if (oldStart >= 0 || newStart >= 0) {
10659                 int end = Selection.getSelectionEnd(buf);
10660                 invalidateCursor(end, oldStart, newStart);
10661             }
10662         }
10663 
10664         if (selChanged) {
10665             mHighlightPathBogus = true;
10666             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
10667 
10668             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
10669                 if (newSelStart < 0) {
10670                     newSelStart = Selection.getSelectionStart(buf);
10671                 }
10672                 if (newSelEnd < 0) {
10673                     newSelEnd = Selection.getSelectionEnd(buf);
10674                 }
10675 
10676                 if (mEditor != null) {
10677                     mEditor.refreshTextActionMode();
10678                     if (!hasSelection()
10679                             && mEditor.getTextActionMode() == null && hasTransientState()) {
10680                         // User generated selection has been removed.
10681                         setHasTransientState(false);
10682                     }
10683                 }
10684                 onSelectionChanged(newSelStart, newSelEnd);
10685             }
10686         }
10687 
10688         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
10689                 || what instanceof CharacterStyle) {
10690             if (ims == null || ims.mBatchEditNesting == 0) {
10691                 invalidate();
10692                 mHighlightPathBogus = true;
10693                 checkForResize();
10694             } else {
10695                 ims.mContentChanged = true;
10696             }
10697             if (mEditor != null) {
10698                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
10699                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
10700                 mEditor.invalidateHandlesAndActionMode();
10701             }
10702         }
10703 
10704         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
10705             mHighlightPathBogus = true;
10706             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
10707                 ims.mSelectionModeChanged = true;
10708             }
10709 
10710             if (Selection.getSelectionStart(buf) >= 0) {
10711                 if (ims == null || ims.mBatchEditNesting == 0) {
10712                     invalidateCursor();
10713                 } else {
10714                     ims.mCursorChanged = true;
10715                 }
10716             }
10717         }
10718 
10719         if (what instanceof ParcelableSpan) {
10720             // If this is a span that can be sent to a remote process,
10721             // the current extract editor would be interested in it.
10722             if (ims != null && ims.mExtractedTextRequest != null) {
10723                 if (ims.mBatchEditNesting != 0) {
10724                     if (oldStart >= 0) {
10725                         if (ims.mChangedStart > oldStart) {
10726                             ims.mChangedStart = oldStart;
10727                         }
10728                         if (ims.mChangedStart > oldEnd) {
10729                             ims.mChangedStart = oldEnd;
10730                         }
10731                     }
10732                     if (newStart >= 0) {
10733                         if (ims.mChangedStart > newStart) {
10734                             ims.mChangedStart = newStart;
10735                         }
10736                         if (ims.mChangedStart > newEnd) {
10737                             ims.mChangedStart = newEnd;
10738                         }
10739                     }
10740                 } else {
10741                     if (DEBUG_EXTRACT) {
10742                         Log.v(LOG_TAG, "Span change outside of batch: "
10743                                 + oldStart + "-" + oldEnd + ","
10744                                 + newStart + "-" + newEnd + " " + what);
10745                     }
10746                     ims.mContentChanged = true;
10747                 }
10748             }
10749         }
10750 
10751         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
10752                 && what instanceof SpellCheckSpan) {
10753             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
10754         }
10755     }
10756 
10757     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10758     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
10759         if (isTemporarilyDetached()) {
10760             // If we are temporarily in the detach state, then do nothing.
10761             super.onFocusChanged(focused, direction, previouslyFocusedRect);
10762             return;
10763         }
10764 
10765         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
10766 
10767         if (focused) {
10768             if (mSpannable != null) {
10769                 MetaKeyKeyListener.resetMetaState(mSpannable);
10770             }
10771         }
10772 
10773         startStopMarquee(focused);
10774 
10775         if (mTransformation != null) {
10776             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
10777         }
10778 
10779         super.onFocusChanged(focused, direction, previouslyFocusedRect);
10780     }
10781 
10782     @Override
onWindowFocusChanged(boolean hasWindowFocus)10783     public void onWindowFocusChanged(boolean hasWindowFocus) {
10784         super.onWindowFocusChanged(hasWindowFocus);
10785 
10786         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
10787 
10788         startStopMarquee(hasWindowFocus);
10789     }
10790 
10791     @Override
onVisibilityChanged(View changedView, int visibility)10792     protected void onVisibilityChanged(View changedView, int visibility) {
10793         super.onVisibilityChanged(changedView, visibility);
10794         if (mEditor != null && visibility != VISIBLE) {
10795             mEditor.hideCursorAndSpanControllers();
10796             stopTextActionMode();
10797         }
10798     }
10799 
10800     /**
10801      * Use {@link BaseInputConnection#removeComposingSpans
10802      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
10803      * state from this text view.
10804      */
clearComposingText()10805     public void clearComposingText() {
10806         if (mText instanceof Spannable) {
10807             BaseInputConnection.removeComposingSpans(mSpannable);
10808         }
10809     }
10810 
10811     @Override
setSelected(boolean selected)10812     public void setSelected(boolean selected) {
10813         boolean wasSelected = isSelected();
10814 
10815         super.setSelected(selected);
10816 
10817         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10818             if (selected) {
10819                 startMarquee();
10820             } else {
10821                 stopMarquee();
10822             }
10823         }
10824     }
10825 
10826     @Override
onTouchEvent(MotionEvent event)10827     public boolean onTouchEvent(MotionEvent event) {
10828         final int action = event.getActionMasked();
10829         if (mEditor != null) {
10830             mEditor.onTouchEvent(event);
10831 
10832             if (mEditor.mSelectionModifierCursorController != null
10833                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
10834                 return true;
10835             }
10836         }
10837 
10838         final boolean superResult = super.onTouchEvent(event);
10839 
10840         /*
10841          * Don't handle the release after a long press, because it will move the selection away from
10842          * whatever the menu action was trying to affect. If the long press should have triggered an
10843          * insertion action mode, we can now actually show it.
10844          */
10845         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
10846             mEditor.mDiscardNextActionUp = false;
10847 
10848             if (mEditor.mIsInsertionActionModeStartPending) {
10849                 mEditor.startInsertionActionMode();
10850                 mEditor.mIsInsertionActionModeStartPending = false;
10851             }
10852             return superResult;
10853         }
10854 
10855         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
10856                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
10857 
10858         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
10859                 && mText instanceof Spannable && mLayout != null) {
10860             boolean handled = false;
10861 
10862             if (mMovement != null) {
10863                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
10864             }
10865 
10866             final boolean textIsSelectable = isTextSelectable();
10867             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
10868                 // The LinkMovementMethod which should handle taps on links has not been installed
10869                 // on non editable text that support text selection.
10870                 // We reproduce its behavior here to open links for these.
10871                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
10872                     getSelectionEnd(), ClickableSpan.class);
10873 
10874                 if (links.length > 0) {
10875                     links[0].onClick(this);
10876                     handled = true;
10877                 }
10878             }
10879 
10880             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
10881                 // Show the IME, except when selecting in read-only text.
10882                 final InputMethodManager imm = getInputMethodManager();
10883                 viewClicked(imm);
10884                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10885                     imm.showSoftInput(this, 0);
10886                 }
10887 
10888                 // The above condition ensures that the mEditor is not null
10889                 mEditor.onTouchUpEvent(event);
10890 
10891                 handled = true;
10892             }
10893 
10894             if (handled) {
10895                 return true;
10896             }
10897         }
10898 
10899         return superResult;
10900     }
10901 
10902     @Override
onGenericMotionEvent(MotionEvent event)10903     public boolean onGenericMotionEvent(MotionEvent event) {
10904         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
10905             try {
10906                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
10907                     return true;
10908                 }
10909             } catch (AbstractMethodError ex) {
10910                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
10911                 // Ignore its absence in case third party applications implemented the
10912                 // interface directly.
10913             }
10914         }
10915         return super.onGenericMotionEvent(event);
10916     }
10917 
10918     @Override
onCreateContextMenu(ContextMenu menu)10919     protected void onCreateContextMenu(ContextMenu menu) {
10920         if (mEditor != null) {
10921             mEditor.onCreateContextMenu(menu);
10922         }
10923     }
10924 
10925     @Override
showContextMenu()10926     public boolean showContextMenu() {
10927         if (mEditor != null) {
10928             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
10929         }
10930         return super.showContextMenu();
10931     }
10932 
10933     @Override
showContextMenu(float x, float y)10934     public boolean showContextMenu(float x, float y) {
10935         if (mEditor != null) {
10936             mEditor.setContextMenuAnchor(x, y);
10937         }
10938         return super.showContextMenu(x, y);
10939     }
10940 
10941     /**
10942      * @return True iff this TextView contains a text that can be edited, or if this is
10943      * a selectable TextView.
10944      */
10945     @UnsupportedAppUsage
isTextEditable()10946     boolean isTextEditable() {
10947         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
10948     }
10949 
10950     /**
10951      * Returns true, only while processing a touch gesture, if the initial
10952      * touch down event caused focus to move to the text view and as a result
10953      * its selection changed.  Only valid while processing the touch gesture
10954      * of interest, in an editable text view.
10955      */
didTouchFocusSelect()10956     public boolean didTouchFocusSelect() {
10957         return mEditor != null && mEditor.mTouchFocusSelected;
10958     }
10959 
10960     @Override
cancelLongPress()10961     public void cancelLongPress() {
10962         super.cancelLongPress();
10963         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
10964     }
10965 
10966     @Override
onTrackballEvent(MotionEvent event)10967     public boolean onTrackballEvent(MotionEvent event) {
10968         if (mMovement != null && mSpannable != null && mLayout != null) {
10969             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
10970                 return true;
10971             }
10972         }
10973 
10974         return super.onTrackballEvent(event);
10975     }
10976 
10977     /**
10978      * Sets the Scroller used for producing a scrolling animation
10979      *
10980      * @param s A Scroller instance
10981      */
setScroller(Scroller s)10982     public void setScroller(Scroller s) {
10983         mScroller = s;
10984     }
10985 
10986     @Override
getLeftFadingEdgeStrength()10987     protected float getLeftFadingEdgeStrength() {
10988         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
10989             final Marquee marquee = mMarquee;
10990             if (marquee.shouldDrawLeftFade()) {
10991                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
10992             } else {
10993                 return 0.0f;
10994             }
10995         } else if (getLineCount() == 1) {
10996             final float lineLeft = getLayout().getLineLeft(0);
10997             if (lineLeft > mScrollX) return 0.0f;
10998             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
10999         }
11000         return super.getLeftFadingEdgeStrength();
11001     }
11002 
11003     @Override
getRightFadingEdgeStrength()11004     protected float getRightFadingEdgeStrength() {
11005         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11006             final Marquee marquee = mMarquee;
11007             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
11008         } else if (getLineCount() == 1) {
11009             final float rightEdge = mScrollX +
11010                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
11011             final float lineRight = getLayout().getLineRight(0);
11012             if (lineRight < rightEdge) return 0.0f;
11013             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
11014         }
11015         return super.getRightFadingEdgeStrength();
11016     }
11017 
11018     /**
11019      * Calculates the fading edge strength as the ratio of the distance between two
11020      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
11021      * value for the distance calculation.
11022      *
11023      * @param position1 A horizontal position.
11024      * @param position2 A horizontal position.
11025      * @return Fading edge strength between [0.0f, 1.0f].
11026      */
11027     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)11028     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
11029         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
11030         if (horizontalFadingEdgeLength == 0) return 0.0f;
11031         final float diff = Math.abs(position1 - position2);
11032         if (diff > horizontalFadingEdgeLength) return 1.0f;
11033         return diff / horizontalFadingEdgeLength;
11034     }
11035 
isMarqueeFadeEnabled()11036     private boolean isMarqueeFadeEnabled() {
11037         return mEllipsize == TextUtils.TruncateAt.MARQUEE
11038                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
11039     }
11040 
11041     @Override
computeHorizontalScrollRange()11042     protected int computeHorizontalScrollRange() {
11043         if (mLayout != null) {
11044             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
11045                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
11046         }
11047 
11048         return super.computeHorizontalScrollRange();
11049     }
11050 
11051     @Override
computeVerticalScrollRange()11052     protected int computeVerticalScrollRange() {
11053         if (mLayout != null) {
11054             return mLayout.getHeight();
11055         }
11056         return super.computeVerticalScrollRange();
11057     }
11058 
11059     @Override
computeVerticalScrollExtent()11060     protected int computeVerticalScrollExtent() {
11061         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
11062     }
11063 
11064     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11065     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
11066         super.findViewsWithText(outViews, searched, flags);
11067         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
11068                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
11069             String searchedLowerCase = searched.toString().toLowerCase();
11070             String textLowerCase = mText.toString().toLowerCase();
11071             if (textLowerCase.contains(searchedLowerCase)) {
11072                 outViews.add(this);
11073             }
11074         }
11075     }
11076 
11077     /**
11078      * Type of the text buffer that defines the characteristics of the text such as static,
11079      * styleable, or editable.
11080      */
11081     public enum BufferType {
11082         NORMAL, SPANNABLE, EDITABLE
11083     }
11084 
11085     /**
11086      * Returns the TextView_textColor attribute from the TypedArray, if set, or
11087      * the TextAppearance_textColor from the TextView_textAppearance attribute,
11088      * if TextView_textColor was not set directly.
11089      *
11090      * @removed
11091      */
getTextColors(Context context, TypedArray attrs)11092     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
11093         if (attrs == null) {
11094             // Preserve behavior prior to removal of this API.
11095             throw new NullPointerException();
11096         }
11097 
11098         // It's not safe to use this method from apps. The parameter 'attrs'
11099         // must have been obtained using the TextView filter array which is not
11100         // available to the SDK. As such, we grab a default TypedArray with the
11101         // right filter instead here.
11102         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
11103         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
11104         if (colors == null) {
11105             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
11106             if (ap != 0) {
11107                 final TypedArray appearance = context.obtainStyledAttributes(
11108                         ap, R.styleable.TextAppearance);
11109                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
11110                 appearance.recycle();
11111             }
11112         }
11113         a.recycle();
11114 
11115         return colors;
11116     }
11117 
11118     /**
11119      * Returns the default color from the TextView_textColor attribute from the
11120      * AttributeSet, if set, or the default color from the
11121      * TextAppearance_textColor from the TextView_textAppearance attribute, if
11122      * TextView_textColor was not set directly.
11123      *
11124      * @removed
11125      */
getTextColor(Context context, TypedArray attrs, int def)11126     public static int getTextColor(Context context, TypedArray attrs, int def) {
11127         final ColorStateList colors = getTextColors(context, attrs);
11128         if (colors == null) {
11129             return def;
11130         } else {
11131             return colors.getDefaultColor();
11132         }
11133     }
11134 
11135     @Override
onKeyShortcut(int keyCode, KeyEvent event)11136     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
11137         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
11138             // Handle Ctrl-only shortcuts.
11139             switch (keyCode) {
11140                 case KeyEvent.KEYCODE_A:
11141                     if (canSelectText()) {
11142                         return onTextContextMenuItem(ID_SELECT_ALL);
11143                     }
11144                     break;
11145                 case KeyEvent.KEYCODE_Z:
11146                     if (canUndo()) {
11147                         return onTextContextMenuItem(ID_UNDO);
11148                     }
11149                     break;
11150                 case KeyEvent.KEYCODE_X:
11151                     if (canCut()) {
11152                         return onTextContextMenuItem(ID_CUT);
11153                     }
11154                     break;
11155                 case KeyEvent.KEYCODE_C:
11156                     if (canCopy()) {
11157                         return onTextContextMenuItem(ID_COPY);
11158                     }
11159                     break;
11160                 case KeyEvent.KEYCODE_V:
11161                     if (canPaste()) {
11162                         return onTextContextMenuItem(ID_PASTE);
11163                     }
11164                     break;
11165             }
11166         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
11167             // Handle Ctrl-Shift shortcuts.
11168             switch (keyCode) {
11169                 case KeyEvent.KEYCODE_Z:
11170                     if (canRedo()) {
11171                         return onTextContextMenuItem(ID_REDO);
11172                     }
11173                     break;
11174                 case KeyEvent.KEYCODE_V:
11175                     if (canPaste()) {
11176                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
11177                     }
11178             }
11179         }
11180         return super.onKeyShortcut(keyCode, event);
11181     }
11182 
11183     /**
11184      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
11185      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
11186      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
11187      * sufficient.
11188      */
canSelectText()11189     boolean canSelectText() {
11190         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
11191     }
11192 
11193     /**
11194      * Test based on the <i>intrinsic</i> charateristics of the TextView.
11195      * The text must be spannable and the movement method must allow for arbitary selection.
11196      *
11197      * See also {@link #canSelectText()}.
11198      */
textCanBeSelected()11199     boolean textCanBeSelected() {
11200         // prepareCursorController() relies on this method.
11201         // If you change this condition, make sure prepareCursorController is called anywhere
11202         // the value of this condition might be changed.
11203         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
11204         return isTextEditable()
11205                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
11206     }
11207 
11208     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)11209     private Locale getTextServicesLocale(boolean allowNullLocale) {
11210         // Start fetching the text services locale asynchronously.
11211         updateTextServicesLocaleAsync();
11212         // If !allowNullLocale and there is no cached text services locale, just return the default
11213         // locale.
11214         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
11215                 : mCurrentSpellCheckerLocaleCache;
11216     }
11217 
11218     /**
11219      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
11220      * this {@link TextView}.
11221      *
11222      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
11223      * other apps may need to set this so that the system can user right user's resources and
11224      * services such as input methods and spell checkers.</p>
11225      *
11226      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
11227      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
11228      * @hide
11229      */
11230     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)11231     public final void setTextOperationUser(@Nullable UserHandle user) {
11232         if (Objects.equals(mTextOperationUser, user)) {
11233             return;
11234         }
11235         if (user != null && !Process.myUserHandle().equals(user)) {
11236             // Just for preventing people from accidentally using this hidden API without
11237             // the required permission.  The same permission is also checked in the system server.
11238             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
11239                     != PackageManager.PERMISSION_GRANTED) {
11240                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
11241                         + " userId=" + user.getIdentifier()
11242                         + " callingUserId" + UserHandle.myUserId());
11243             }
11244         }
11245         mTextOperationUser = user;
11246         // Invalidate some resources
11247         mCurrentSpellCheckerLocaleCache = null;
11248         if (mEditor != null) {
11249             mEditor.onTextOperationUserChanged();
11250         }
11251     }
11252 
11253     @Nullable
getTextServicesManagerForUser()11254     final TextServicesManager getTextServicesManagerForUser() {
11255         return getServiceManagerForUser("android", TextServicesManager.class);
11256     }
11257 
11258     @Nullable
getClipboardManagerForUser()11259     final ClipboardManager getClipboardManagerForUser() {
11260         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
11261     }
11262 
11263     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)11264     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
11265         if (mTextOperationUser == null) {
11266             return getContext().getSystemService(managerClazz);
11267         }
11268         try {
11269             Context context = getContext().createPackageContextAsUser(
11270                     packageName, 0 /* flags */, mTextOperationUser);
11271             return context.getSystemService(managerClazz);
11272         } catch (PackageManager.NameNotFoundException e) {
11273             return null;
11274         }
11275     }
11276 
11277     /**
11278      * Starts {@link Activity} as a text-operation user if it is specified with
11279      * {@link #setTextOperationUser(UserHandle)}.
11280      *
11281      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
11282      *
11283      * @param intent The description of the activity to start.
11284      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11285     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
11286         if (mTextOperationUser != null) {
11287             getContext().startActivityAsUser(intent, mTextOperationUser);
11288         } else {
11289             getContext().startActivity(intent);
11290         }
11291     }
11292 
11293     /**
11294      * This is a temporary method. Future versions may support multi-locale text.
11295      * Caveat: This method may not return the latest text services locale, but this should be
11296      * acceptable and it's more important to make this method asynchronous.
11297      *
11298      * @return The locale that should be used for a word iterator
11299      * in this TextView, based on the current spell checker settings,
11300      * the current IME's locale, or the system default locale.
11301      * Please note that a word iterator in this TextView is different from another word iterator
11302      * used by SpellChecker.java of TextView. This method should be used for the former.
11303      * @hide
11304      */
11305     // TODO: Support multi-locale
11306     // TODO: Update the text services locale immediately after the keyboard locale is switched
11307     // by catching intent of keyboard switch event
getTextServicesLocale()11308     public Locale getTextServicesLocale() {
11309         return getTextServicesLocale(false /* allowNullLocale */);
11310     }
11311 
11312     /**
11313      * @return {@code true} if this TextView is specialized for showing and interacting with the
11314      * extracted text in a full-screen input method.
11315      * @hide
11316      */
isInExtractedMode()11317     public boolean isInExtractedMode() {
11318         return false;
11319     }
11320 
11321     /**
11322      * @return {@code true} if this widget supports auto-sizing text and has been configured to
11323      * auto-size.
11324      */
isAutoSizeEnabled()11325     private boolean isAutoSizeEnabled() {
11326         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
11327     }
11328 
11329     /**
11330      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
11331      * @hide
11332      */
supportsAutoSizeText()11333     protected boolean supportsAutoSizeText() {
11334         return true;
11335     }
11336 
11337     /**
11338      * This is a temporary method. Future versions may support multi-locale text.
11339      * Caveat: This method may not return the latest spell checker locale, but this should be
11340      * acceptable and it's more important to make this method asynchronous.
11341      *
11342      * @return The locale that should be used for a spell checker in this TextView,
11343      * based on the current spell checker settings, the current IME's locale, or the system default
11344      * locale.
11345      * @hide
11346      */
getSpellCheckerLocale()11347     public Locale getSpellCheckerLocale() {
11348         return getTextServicesLocale(true /* allowNullLocale */);
11349     }
11350 
updateTextServicesLocaleAsync()11351     private void updateTextServicesLocaleAsync() {
11352         // AsyncTask.execute() uses a serial executor which means we don't have
11353         // to lock around updateTextServicesLocaleLocked() to prevent it from
11354         // being executed n times in parallel.
11355         AsyncTask.execute(new Runnable() {
11356             @Override
11357             public void run() {
11358                 updateTextServicesLocaleLocked();
11359             }
11360         });
11361     }
11362 
11363     @UnsupportedAppUsage
updateTextServicesLocaleLocked()11364     private void updateTextServicesLocaleLocked() {
11365         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
11366         if (textServicesManager == null) {
11367             return;
11368         }
11369         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
11370         final Locale locale;
11371         if (subtype != null) {
11372             locale = subtype.getLocaleObject();
11373         } else {
11374             locale = null;
11375         }
11376         mCurrentSpellCheckerLocaleCache = locale;
11377     }
11378 
onLocaleChanged()11379     void onLocaleChanged() {
11380         mEditor.onLocaleChanged();
11381     }
11382 
11383     /**
11384      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
11385      * Made available to achieve a consistent behavior.
11386      * @hide
11387      */
getWordIterator()11388     public WordIterator getWordIterator() {
11389         if (mEditor != null) {
11390             return mEditor.getWordIterator();
11391         } else {
11392             return null;
11393         }
11394     }
11395 
11396     /** @hide */
11397     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)11398     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
11399         super.onPopulateAccessibilityEventInternal(event);
11400 
11401         final CharSequence text = getTextForAccessibility();
11402         if (!TextUtils.isEmpty(text)) {
11403             event.getText().add(text);
11404         }
11405     }
11406 
11407     @Override
getAccessibilityClassName()11408     public CharSequence getAccessibilityClassName() {
11409         return TextView.class.getName();
11410     }
11411 
11412     /** @hide */
11413     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11414     protected void onProvideStructure(@NonNull ViewStructure structure,
11415             @ViewStructureType int viewFor, int flags) {
11416         super.onProvideStructure(structure, viewFor, flags);
11417 
11418         final boolean isPassword = hasPasswordTransformationMethod()
11419                 || isPasswordInputType(getInputType());
11420         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11421             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11422                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
11423             }
11424             if (mTextId != Resources.ID_NULL) {
11425                 try {
11426                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
11427                 } catch (Resources.NotFoundException e) {
11428                     if (android.view.autofill.Helper.sVerbose) {
11429                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
11430                                 + mTextId + ": " + e.getMessage());
11431                     }
11432                 }
11433             }
11434         }
11435 
11436         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11437             if (mLayout == null) {
11438                 assumeLayout();
11439             }
11440             Layout layout = mLayout;
11441             final int lineCount = layout.getLineCount();
11442             if (lineCount <= 1) {
11443                 // Simple case: this is a single line.
11444                 final CharSequence text = getText();
11445                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11446                     structure.setText(text);
11447                 } else {
11448                     structure.setText(text, getSelectionStart(), getSelectionEnd());
11449                 }
11450             } else {
11451                 // Complex case: multi-line, could be scrolled or within a scroll container
11452                 // so some lines are not visible.
11453                 final int[] tmpCords = new int[2];
11454                 getLocationInWindow(tmpCords);
11455                 final int topWindowLocation = tmpCords[1];
11456                 View root = this;
11457                 ViewParent viewParent = getParent();
11458                 while (viewParent instanceof View) {
11459                     root = (View) viewParent;
11460                     viewParent = root.getParent();
11461                 }
11462                 final int windowHeight = root.getHeight();
11463                 final int topLine;
11464                 final int bottomLine;
11465                 if (topWindowLocation >= 0) {
11466                     // The top of the view is fully within its window; start text at line 0.
11467                     topLine = getLineAtCoordinateUnclamped(0);
11468                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
11469                 } else {
11470                     // The top of hte window has scrolled off the top of the window; figure out
11471                     // the starting line for this.
11472                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
11473                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
11474                 }
11475                 // We want to return some contextual lines above/below the lines that are
11476                 // actually visible.
11477                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
11478                 if (expandedTopLine < 0) {
11479                     expandedTopLine = 0;
11480                 }
11481                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
11482                 if (expandedBottomLine >= lineCount) {
11483                     expandedBottomLine = lineCount - 1;
11484                 }
11485 
11486                 // Convert lines into character offsets.
11487                 int expandedTopChar = layout.getLineStart(expandedTopLine);
11488                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
11489 
11490                 // Take into account selection -- if there is a selection, we need to expand
11491                 // the text we are returning to include that selection.
11492                 final int selStart = getSelectionStart();
11493                 final int selEnd = getSelectionEnd();
11494                 if (selStart < selEnd) {
11495                     if (selStart < expandedTopChar) {
11496                         expandedTopChar = selStart;
11497                     }
11498                     if (selEnd > expandedBottomChar) {
11499                         expandedBottomChar = selEnd;
11500                     }
11501                 }
11502 
11503                 // Get the text and trim it to the range we are reporting.
11504                 CharSequence text = getText();
11505                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
11506                     text = text.subSequence(expandedTopChar, expandedBottomChar);
11507                 }
11508 
11509                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11510                     structure.setText(text);
11511                 } else {
11512                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
11513 
11514                     final int[] lineOffsets = new int[bottomLine - topLine + 1];
11515                     final int[] lineBaselines = new int[bottomLine - topLine + 1];
11516                     final int baselineOffset = getBaselineOffset();
11517                     for (int i = topLine; i <= bottomLine; i++) {
11518                         lineOffsets[i - topLine] = layout.getLineStart(i);
11519                         lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
11520                     }
11521                     structure.setTextLines(lineOffsets, lineBaselines);
11522                 }
11523             }
11524 
11525             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST) {
11526                 // Extract style information that applies to the TextView as a whole.
11527                 int style = 0;
11528                 int typefaceStyle = getTypefaceStyle();
11529                 if ((typefaceStyle & Typeface.BOLD) != 0) {
11530                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11531                 }
11532                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
11533                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
11534                 }
11535 
11536                 // Global styles can also be set via TextView.setPaintFlags().
11537                 int paintFlags = mTextPaint.getFlags();
11538                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
11539                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11540                 }
11541                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
11542                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
11543                 }
11544                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
11545                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
11546                 }
11547 
11548                 // TextView does not have its own text background color. A background is either part
11549                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
11550                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
11551                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
11552             }
11553             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11554                 structure.setMinTextEms(getMinEms());
11555                 structure.setMaxTextEms(getMaxEms());
11556                 int maxLength = -1;
11557                 for (InputFilter filter: getFilters()) {
11558                     if (filter instanceof InputFilter.LengthFilter) {
11559                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
11560                         break;
11561                     }
11562                 }
11563                 structure.setMaxTextLength(maxLength);
11564             }
11565         }
11566         structure.setHint(getHint());
11567         structure.setInputType(getInputType());
11568     }
11569 
canRequestAutofill()11570     boolean canRequestAutofill() {
11571         if (!isAutofillable()) {
11572             return false;
11573         }
11574         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11575         if (afm != null) {
11576             return afm.isEnabled();
11577         }
11578         return false;
11579     }
11580 
requestAutofill()11581     private void requestAutofill() {
11582         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11583         if (afm != null) {
11584             afm.requestAutofill(this);
11585         }
11586     }
11587 
11588     @Override
autofill(AutofillValue value)11589     public void autofill(AutofillValue value) {
11590         if (!value.isText() || !isTextEditable()) {
11591             Log.w(LOG_TAG, value + " could not be autofilled into " + this);
11592             return;
11593         }
11594 
11595         final CharSequence autofilledValue = value.getTextValue();
11596 
11597         // First autofill it...
11598         setText(autofilledValue, mBufferType, true, 0);
11599 
11600         // ...then move cursor to the end.
11601         final CharSequence text = getText();
11602         if ((text instanceof Spannable)) {
11603             Selection.setSelection((Spannable) text, text.length());
11604         }
11605     }
11606 
11607     @Override
getAutofillType()11608     public @AutofillType int getAutofillType() {
11609         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
11610     }
11611 
11612     /**
11613      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
11614      * {@code char}s if longer.
11615      *
11616      * @return current text, {@code null} if the text is not editable
11617      *
11618      * @see View#getAutofillValue()
11619      */
11620     @Override
11621     @Nullable
getAutofillValue()11622     public AutofillValue getAutofillValue() {
11623         if (isTextEditable()) {
11624             final CharSequence text = TextUtils.trimToParcelableSize(getText());
11625             return AutofillValue.forText(text);
11626         }
11627         return null;
11628     }
11629 
11630     /** @hide */
11631     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)11632     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
11633         super.onInitializeAccessibilityEventInternal(event);
11634 
11635         final boolean isPassword = hasPasswordTransformationMethod();
11636         event.setPassword(isPassword);
11637 
11638         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
11639             event.setFromIndex(Selection.getSelectionStart(mText));
11640             event.setToIndex(Selection.getSelectionEnd(mText));
11641             event.setItemCount(mText.length());
11642         }
11643     }
11644 
11645     /** @hide */
11646     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11647     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
11648         super.onInitializeAccessibilityNodeInfoInternal(info);
11649 
11650         final boolean isPassword = hasPasswordTransformationMethod();
11651         info.setPassword(isPassword);
11652         info.setText(getTextForAccessibility());
11653         info.setHintText(mHint);
11654         info.setShowingHintText(isShowingHint());
11655 
11656         if (mBufferType == BufferType.EDITABLE) {
11657             info.setEditable(true);
11658             if (isEnabled()) {
11659                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
11660             }
11661         }
11662 
11663         if (mEditor != null) {
11664             info.setInputType(mEditor.mInputType);
11665 
11666             if (mEditor.mError != null) {
11667                 info.setContentInvalid(true);
11668                 info.setError(mEditor.mError);
11669             }
11670         }
11671 
11672         if (!TextUtils.isEmpty(mText)) {
11673             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
11674             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
11675             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
11676                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
11677                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
11678                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
11679                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
11680             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
11681             info.setAvailableExtraData(
11682                     Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
11683         }
11684 
11685         if (isFocused()) {
11686             if (canCopy()) {
11687                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
11688             }
11689             if (canPaste()) {
11690                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
11691             }
11692             if (canCut()) {
11693                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
11694             }
11695             if (canShare()) {
11696                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
11697                         ACCESSIBILITY_ACTION_SHARE,
11698                         getResources().getString(com.android.internal.R.string.share)));
11699             }
11700             if (canProcessText()) {  // also implies mEditor is not null.
11701                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
11702             }
11703         }
11704 
11705         // Check for known input filter types.
11706         final int numFilters = mFilters.length;
11707         for (int i = 0; i < numFilters; i++) {
11708             final InputFilter filter = mFilters[i];
11709             if (filter instanceof InputFilter.LengthFilter) {
11710                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
11711             }
11712         }
11713 
11714         if (!isSingleLine()) {
11715             info.setMultiLine(true);
11716         }
11717     }
11718 
11719     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11720     public void addExtraDataToAccessibilityNodeInfo(
11721             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
11722         // The only extra data we support requires arguments.
11723         if (arguments == null) {
11724             return;
11725         }
11726         if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
11727             int positionInfoStartIndex = arguments.getInt(
11728                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
11729             int positionInfoLength = arguments.getInt(
11730                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
11731             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
11732                     || (positionInfoStartIndex >= mText.length())) {
11733                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
11734                 return;
11735             }
11736             RectF[] boundingRects = new RectF[positionInfoLength];
11737             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
11738             populateCharacterBounds(builder, positionInfoStartIndex,
11739                     positionInfoStartIndex + positionInfoLength,
11740                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
11741             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
11742             for (int i = 0; i < positionInfoLength; i++) {
11743                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
11744                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
11745                     RectF bounds = cursorAnchorInfo
11746                             .getCharacterBounds(positionInfoStartIndex + i);
11747                     if (bounds != null) {
11748                         mapRectFromViewToScreenCoords(bounds, true);
11749                         boundingRects[i] = bounds;
11750                     }
11751                 }
11752             }
11753             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
11754         }
11755     }
11756 
11757     /**
11758      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
11759      *
11760      * @param builder The builder to populate
11761      * @param startIndex The starting character index to populate
11762      * @param endIndex The ending character index to populate
11763      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
11764      * content
11765      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
11766      * @hide
11767      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)11768     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
11769             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
11770             float viewportToContentVerticalOffset) {
11771         final int minLine = mLayout.getLineForOffset(startIndex);
11772         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
11773         for (int line = minLine; line <= maxLine; ++line) {
11774             final int lineStart = mLayout.getLineStart(line);
11775             final int lineEnd = mLayout.getLineEnd(line);
11776             final int offsetStart = Math.max(lineStart, startIndex);
11777             final int offsetEnd = Math.min(lineEnd, endIndex);
11778             final boolean ltrLine =
11779                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
11780             final float[] widths = new float[offsetEnd - offsetStart];
11781             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
11782             final float top = mLayout.getLineTop(line);
11783             final float bottom = mLayout.getLineBottom(line);
11784             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
11785                 final float charWidth = widths[offset - offsetStart];
11786                 final boolean isRtl = mLayout.isRtlCharAt(offset);
11787                 final float primary = mLayout.getPrimaryHorizontal(offset);
11788                 final float secondary = mLayout.getSecondaryHorizontal(offset);
11789                 // TODO: This doesn't work perfectly for text with custom styles and
11790                 // TAB chars.
11791                 final float left;
11792                 final float right;
11793                 if (ltrLine) {
11794                     if (isRtl) {
11795                         left = secondary - charWidth;
11796                         right = secondary;
11797                     } else {
11798                         left = primary;
11799                         right = primary + charWidth;
11800                     }
11801                 } else {
11802                     if (!isRtl) {
11803                         left = secondary;
11804                         right = secondary + charWidth;
11805                     } else {
11806                         left = primary - charWidth;
11807                         right = primary;
11808                     }
11809                 }
11810                 // TODO: Check top-right and bottom-left as well.
11811                 final float localLeft = left + viewportToContentHorizontalOffset;
11812                 final float localRight = right + viewportToContentHorizontalOffset;
11813                 final float localTop = top + viewportToContentVerticalOffset;
11814                 final float localBottom = bottom + viewportToContentVerticalOffset;
11815                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
11816                 final boolean isBottomRightVisible =
11817                         isPositionVisible(localRight, localBottom);
11818                 int characterBoundsFlags = 0;
11819                 if (isTopLeftVisible || isBottomRightVisible) {
11820                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
11821                 }
11822                 if (!isTopLeftVisible || !isBottomRightVisible) {
11823                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
11824                 }
11825                 if (isRtl) {
11826                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
11827                 }
11828                 // Here offset is the index in Java chars.
11829                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
11830                         localBottom, characterBoundsFlags);
11831             }
11832         }
11833     }
11834 
11835     /**
11836      * @hide
11837      */
isPositionVisible(final float positionX, final float positionY)11838     public boolean isPositionVisible(final float positionX, final float positionY) {
11839         synchronized (TEMP_POSITION) {
11840             final float[] position = TEMP_POSITION;
11841             position[0] = positionX;
11842             position[1] = positionY;
11843             View view = this;
11844 
11845             while (view != null) {
11846                 if (view != this) {
11847                     // Local scroll is already taken into account in positionX/Y
11848                     position[0] -= view.getScrollX();
11849                     position[1] -= view.getScrollY();
11850                 }
11851 
11852                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
11853                         || position[1] > view.getHeight()) {
11854                     return false;
11855                 }
11856 
11857                 if (!view.getMatrix().isIdentity()) {
11858                     view.getMatrix().mapPoints(position);
11859                 }
11860 
11861                 position[0] += view.getLeft();
11862                 position[1] += view.getTop();
11863 
11864                 final ViewParent parent = view.getParent();
11865                 if (parent instanceof View) {
11866                     view = (View) parent;
11867                 } else {
11868                     // We've reached the ViewRoot, stop iterating
11869                     view = null;
11870                 }
11871             }
11872         }
11873 
11874         // We've been able to walk up the view hierarchy and the position was never clipped
11875         return true;
11876     }
11877 
11878     /**
11879      * Performs an accessibility action after it has been offered to the
11880      * delegate.
11881      *
11882      * @hide
11883      */
11884     @Override
performAccessibilityActionInternal(int action, Bundle arguments)11885     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
11886         if (mEditor != null
11887                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
11888             return true;
11889         }
11890         switch (action) {
11891             case AccessibilityNodeInfo.ACTION_CLICK: {
11892                 return performAccessibilityActionClick(arguments);
11893             }
11894             case AccessibilityNodeInfo.ACTION_COPY: {
11895                 if (isFocused() && canCopy()) {
11896                     if (onTextContextMenuItem(ID_COPY)) {
11897                         return true;
11898                     }
11899                 }
11900             } return false;
11901             case AccessibilityNodeInfo.ACTION_PASTE: {
11902                 if (isFocused() && canPaste()) {
11903                     if (onTextContextMenuItem(ID_PASTE)) {
11904                         return true;
11905                     }
11906                 }
11907             } return false;
11908             case AccessibilityNodeInfo.ACTION_CUT: {
11909                 if (isFocused() && canCut()) {
11910                     if (onTextContextMenuItem(ID_CUT)) {
11911                         return true;
11912                     }
11913                 }
11914             } return false;
11915             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
11916                 ensureIterableTextForAccessibilitySelectable();
11917                 CharSequence text = getIterableTextForAccessibility();
11918                 if (text == null) {
11919                     return false;
11920                 }
11921                 final int start = (arguments != null) ? arguments.getInt(
11922                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
11923                 final int end = (arguments != null) ? arguments.getInt(
11924                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
11925                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
11926                     // No arguments clears the selection.
11927                     if (start == end && end == -1) {
11928                         Selection.removeSelection((Spannable) text);
11929                         return true;
11930                     }
11931                     if (start >= 0 && start <= end && end <= text.length()) {
11932                         Selection.setSelection((Spannable) text, start, end);
11933                         // Make sure selection mode is engaged.
11934                         if (mEditor != null) {
11935                             mEditor.startSelectionActionModeAsync(false);
11936                         }
11937                         return true;
11938                     }
11939                 }
11940             } return false;
11941             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
11942             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
11943                 ensureIterableTextForAccessibilitySelectable();
11944                 return super.performAccessibilityActionInternal(action, arguments);
11945             }
11946             case ACCESSIBILITY_ACTION_SHARE: {
11947                 if (isFocused() && canShare()) {
11948                     if (onTextContextMenuItem(ID_SHARE)) {
11949                         return true;
11950                     }
11951                 }
11952             } return false;
11953             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
11954                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
11955                     return false;
11956                 }
11957                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
11958                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
11959                 setText(text);
11960                 if (mText != null) {
11961                     int updatedTextLength = mText.length();
11962                     if (updatedTextLength > 0) {
11963                         Selection.setSelection(mSpannable, updatedTextLength);
11964                     }
11965                 }
11966             } return true;
11967             default: {
11968                 return super.performAccessibilityActionInternal(action, arguments);
11969             }
11970         }
11971     }
11972 
performAccessibilityActionClick(Bundle arguments)11973     private boolean performAccessibilityActionClick(Bundle arguments) {
11974         boolean handled = false;
11975 
11976         if (!isEnabled()) {
11977             return false;
11978         }
11979 
11980         if (isClickable() || isLongClickable()) {
11981             // Simulate View.onTouchEvent for an ACTION_UP event
11982             if (isFocusable() && !isFocused()) {
11983                 requestFocus();
11984             }
11985 
11986             performClick();
11987             handled = true;
11988         }
11989 
11990         // Show the IME, except when selecting in read-only text.
11991         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
11992                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
11993             final InputMethodManager imm = getInputMethodManager();
11994             viewClicked(imm);
11995             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
11996                 handled |= imm.showSoftInput(this, 0);
11997             }
11998         }
11999 
12000         return handled;
12001     }
12002 
hasSpannableText()12003     private boolean hasSpannableText() {
12004         return mText != null && mText instanceof Spannable;
12005     }
12006 
12007     /** @hide */
12008     @Override
sendAccessibilityEventInternal(int eventType)12009     public void sendAccessibilityEventInternal(int eventType) {
12010         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
12011             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
12012         }
12013 
12014         super.sendAccessibilityEventInternal(eventType);
12015     }
12016 
12017     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)12018     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
12019         // Do not send scroll events since first they are not interesting for
12020         // accessibility and second such events a generated too frequently.
12021         // For details see the implementation of bringTextIntoView().
12022         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
12023             return;
12024         }
12025         super.sendAccessibilityEventUnchecked(event);
12026     }
12027 
12028     /**
12029      * Returns the text that should be exposed to accessibility services.
12030      * <p>
12031      * This approximates what is displayed visually. If the user has specified
12032      * that accessibility services should speak passwords, this method will
12033      * bypass any password transformation method and return unobscured text.
12034      *
12035      * @return the text that should be exposed to accessibility services, may
12036      *         be {@code null} if no text is set
12037      */
12038     @Nullable
12039     @UnsupportedAppUsage
getTextForAccessibility()12040     private CharSequence getTextForAccessibility() {
12041         // If the text is empty, we must be showing the hint text.
12042         if (TextUtils.isEmpty(mText)) {
12043             return mHint;
12044         }
12045 
12046         // Otherwise, return whatever text is being displayed.
12047         return TextUtils.trimToParcelableSize(mTransformed);
12048     }
12049 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12050     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
12051             int fromIndex, int removedCount, int addedCount) {
12052         AccessibilityEvent event =
12053                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
12054         event.setFromIndex(fromIndex);
12055         event.setRemovedCount(removedCount);
12056         event.setAddedCount(addedCount);
12057         event.setBeforeText(beforeText);
12058         sendAccessibilityEventUnchecked(event);
12059     }
12060 
getInputMethodManager()12061     private InputMethodManager getInputMethodManager() {
12062         return getContext().getSystemService(InputMethodManager.class);
12063     }
12064 
12065     /**
12066      * Returns whether this text view is a current input method target.  The
12067      * default implementation just checks with {@link InputMethodManager}.
12068      * @return True if the TextView is a current input method target; false otherwise.
12069      */
isInputMethodTarget()12070     public boolean isInputMethodTarget() {
12071         InputMethodManager imm = getInputMethodManager();
12072         return imm != null && imm.isActive(this);
12073     }
12074 
12075     static final int ID_SELECT_ALL = android.R.id.selectAll;
12076     static final int ID_UNDO = android.R.id.undo;
12077     static final int ID_REDO = android.R.id.redo;
12078     static final int ID_CUT = android.R.id.cut;
12079     static final int ID_COPY = android.R.id.copy;
12080     static final int ID_PASTE = android.R.id.paste;
12081     static final int ID_SHARE = android.R.id.shareText;
12082     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
12083     static final int ID_REPLACE = android.R.id.replaceText;
12084     static final int ID_ASSIST = android.R.id.textAssist;
12085     static final int ID_AUTOFILL = android.R.id.autofill;
12086 
12087     /**
12088      * Called when a context menu option for the text view is selected.  Currently
12089      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
12090      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
12091      *
12092      * @return true if the context menu item action was performed.
12093      */
onTextContextMenuItem(int id)12094     public boolean onTextContextMenuItem(int id) {
12095         int min = 0;
12096         int max = mText.length();
12097 
12098         if (isFocused()) {
12099             final int selStart = getSelectionStart();
12100             final int selEnd = getSelectionEnd();
12101 
12102             min = Math.max(0, Math.min(selStart, selEnd));
12103             max = Math.max(0, Math.max(selStart, selEnd));
12104         }
12105 
12106         switch (id) {
12107             case ID_SELECT_ALL:
12108                 final boolean hadSelection = hasSelection();
12109                 selectAllText();
12110                 if (mEditor != null && hadSelection) {
12111                     mEditor.invalidateActionModeAsync();
12112                 }
12113                 return true;
12114 
12115             case ID_UNDO:
12116                 if (mEditor != null) {
12117                     mEditor.undo();
12118                 }
12119                 return true;  // Returns true even if nothing was undone.
12120 
12121             case ID_REDO:
12122                 if (mEditor != null) {
12123                     mEditor.redo();
12124                 }
12125                 return true;  // Returns true even if nothing was undone.
12126 
12127             case ID_PASTE:
12128                 paste(min, max, true /* withFormatting */);
12129                 return true;
12130 
12131             case ID_PASTE_AS_PLAIN_TEXT:
12132                 paste(min, max, false /* withFormatting */);
12133                 return true;
12134 
12135             case ID_CUT:
12136                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
12137                 if (setPrimaryClip(cutData)) {
12138                     deleteText_internal(min, max);
12139                 } else {
12140                     Toast.makeText(getContext(),
12141                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12142                             Toast.LENGTH_SHORT).show();
12143                 }
12144                 return true;
12145 
12146             case ID_COPY:
12147                 // For link action mode in a non-selectable/non-focusable TextView,
12148                 // make sure that we set the appropriate min/max.
12149                 final int selStart = getSelectionStart();
12150                 final int selEnd = getSelectionEnd();
12151                 min = Math.max(0, Math.min(selStart, selEnd));
12152                 max = Math.max(0, Math.max(selStart, selEnd));
12153                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
12154                 if (setPrimaryClip(copyData)) {
12155                     stopTextActionMode();
12156                 } else {
12157                     Toast.makeText(getContext(),
12158                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12159                             Toast.LENGTH_SHORT).show();
12160                 }
12161                 return true;
12162 
12163             case ID_REPLACE:
12164                 if (mEditor != null) {
12165                     mEditor.replace();
12166                 }
12167                 return true;
12168 
12169             case ID_SHARE:
12170                 shareSelectedText();
12171                 return true;
12172 
12173             case ID_AUTOFILL:
12174                 requestAutofill();
12175                 stopTextActionMode();
12176                 return true;
12177         }
12178         return false;
12179     }
12180 
12181     @UnsupportedAppUsage
getTransformedText(int start, int end)12182     CharSequence getTransformedText(int start, int end) {
12183         return removeSuggestionSpans(mTransformed.subSequence(start, end));
12184     }
12185 
12186     @Override
performLongClick()12187     public boolean performLongClick() {
12188         boolean handled = false;
12189         boolean performedHapticFeedback = false;
12190 
12191         if (mEditor != null) {
12192             mEditor.mIsBeingLongClicked = true;
12193         }
12194 
12195         if (super.performLongClick()) {
12196             handled = true;
12197             performedHapticFeedback = true;
12198         }
12199 
12200         if (mEditor != null) {
12201             handled |= mEditor.performLongClick(handled);
12202             mEditor.mIsBeingLongClicked = false;
12203         }
12204 
12205         if (handled) {
12206             if (!performedHapticFeedback) {
12207               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
12208             }
12209             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
12210         } else {
12211             MetricsLogger.action(
12212                     mContext,
12213                     MetricsEvent.TEXT_LONGPRESS,
12214                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
12215         }
12216 
12217         return handled;
12218     }
12219 
12220     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12221     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
12222         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
12223         if (mEditor != null) {
12224             mEditor.onScrollChanged();
12225         }
12226     }
12227 
12228     /**
12229      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
12230      * by the IME or by the spell checker as the user types. This is done by adding
12231      * {@link SuggestionSpan}s to the text.
12232      *
12233      * When suggestions are enabled (default), this list of suggestions will be displayed when the
12234      * user asks for them on these parts of the text. This value depends on the inputType of this
12235      * TextView.
12236      *
12237      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
12238      *
12239      * In addition, the type variation must be one of
12240      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
12241      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
12242      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
12243      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
12244      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
12245      *
12246      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
12247      *
12248      * @return true if the suggestions popup window is enabled, based on the inputType.
12249      */
isSuggestionsEnabled()12250     public boolean isSuggestionsEnabled() {
12251         if (mEditor == null) return false;
12252         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
12253             return false;
12254         }
12255         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
12256 
12257         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
12258         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
12259                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
12260                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
12261                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
12262                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
12263     }
12264 
12265     /**
12266      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12267      * selection is initiated in this View.
12268      *
12269      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
12270      * Paste, Replace and Share actions, depending on what this View supports.
12271      *
12272      * <p>A custom implementation can add new entries in the default menu in its
12273      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
12274      * method. The default actions can also be removed from the menu using
12275      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12276      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
12277      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
12278      *
12279      * <p>Returning false from
12280      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
12281      * will prevent the action mode from being started.
12282      *
12283      * <p>Action click events should be handled by the custom implementation of
12284      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
12285      * android.view.MenuItem)}.
12286      *
12287      * <p>Note that text selection mode is not started when a TextView receives focus and the
12288      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
12289      * that case, to allow for quick replacement.
12290      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12291     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
12292         createEditorIfNeeded();
12293         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
12294     }
12295 
12296     /**
12297      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
12298      *
12299      * @return The current custom selection callback.
12300      */
getCustomSelectionActionModeCallback()12301     public ActionMode.Callback getCustomSelectionActionModeCallback() {
12302         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
12303     }
12304 
12305     /**
12306      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12307      * insertion is initiated in this View.
12308      * The standard implementation populates the menu with a subset of Select All,
12309      * Paste and Replace actions, depending on what this View supports.
12310      *
12311      * <p>A custom implementation can add new entries in the default menu in its
12312      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
12313      * android.view.Menu)} method. The default actions can also be removed from the menu using
12314      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12315      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
12316      *
12317      * <p>Returning false from
12318      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
12319      * android.view.Menu)} will prevent the action mode from being started.</p>
12320      *
12321      * <p>Action click events should be handled by the custom implementation of
12322      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
12323      * android.view.MenuItem)}.</p>
12324      *
12325      * <p>Note that text insertion mode is not started when a TextView receives focus and the
12326      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
12327      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12328     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
12329         createEditorIfNeeded();
12330         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
12331     }
12332 
12333     /**
12334      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
12335      *
12336      * @return The current custom insertion callback.
12337      */
getCustomInsertionActionModeCallback()12338     public ActionMode.Callback getCustomInsertionActionModeCallback() {
12339         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
12340     }
12341 
12342     /**
12343      * Sets the {@link TextClassifier} for this TextView.
12344      */
setTextClassifier(@ullable TextClassifier textClassifier)12345     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
12346         mTextClassifier = textClassifier;
12347     }
12348 
12349     /**
12350      * Returns the {@link TextClassifier} used by this TextView.
12351      * If no TextClassifier has been set, this TextView uses the default set by the
12352      * {@link TextClassificationManager}.
12353      */
12354     @NonNull
getTextClassifier()12355     public TextClassifier getTextClassifier() {
12356         if (mTextClassifier == null) {
12357             final TextClassificationManager tcm =
12358                     mContext.getSystemService(TextClassificationManager.class);
12359             if (tcm != null) {
12360                 return tcm.getTextClassifier();
12361             }
12362             return TextClassifier.NO_OP;
12363         }
12364         return mTextClassifier;
12365     }
12366 
12367     /**
12368      * Returns a session-aware text classifier.
12369      * This method creates one if none already exists or the current one is destroyed.
12370      */
12371     @NonNull
getTextClassificationSession()12372     TextClassifier getTextClassificationSession() {
12373         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
12374             final TextClassificationManager tcm =
12375                     mContext.getSystemService(TextClassificationManager.class);
12376             if (tcm != null) {
12377                 final String widgetType;
12378                 if (isTextEditable()) {
12379                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
12380                 } else if (isTextSelectable()) {
12381                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
12382                 } else {
12383                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
12384                 }
12385                 mTextClassificationContext = new TextClassificationContext.Builder(
12386                         mContext.getPackageName(), widgetType)
12387                         .build();
12388                 if (mTextClassifier != null) {
12389                     mTextClassificationSession = tcm.createTextClassificationSession(
12390                             mTextClassificationContext, mTextClassifier);
12391                 } else {
12392                     mTextClassificationSession = tcm.createTextClassificationSession(
12393                             mTextClassificationContext);
12394                 }
12395             } else {
12396                 mTextClassificationSession = TextClassifier.NO_OP;
12397             }
12398         }
12399         return mTextClassificationSession;
12400     }
12401 
12402     /**
12403      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
12404      * @see #getTextClassificationSession()
12405      */
12406     @Nullable
getTextClassificationContext()12407     TextClassificationContext getTextClassificationContext() {
12408         return mTextClassificationContext;
12409     }
12410 
12411     /**
12412      * Returns true if this TextView uses a no-op TextClassifier.
12413      */
usesNoOpTextClassifier()12414     boolean usesNoOpTextClassifier() {
12415         return getTextClassifier() == TextClassifier.NO_OP;
12416     }
12417 
12418 
12419     /**
12420      * Starts an ActionMode for the specified TextLinkSpan.
12421      *
12422      * @return Whether or not we're attempting to start the action mode.
12423      * @hide
12424      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12425     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12426         Preconditions.checkNotNull(clickedSpan);
12427 
12428         if (!(mText instanceof Spanned)) {
12429             return false;
12430         }
12431 
12432         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
12433         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
12434 
12435         if (start < 0 || end > mText.length() || start >= end) {
12436             return false;
12437         }
12438 
12439         createEditorIfNeeded();
12440         mEditor.startLinkActionModeAsync(start, end);
12441         return true;
12442     }
12443 
12444     /**
12445      * Handles a click on the specified TextLinkSpan.
12446      *
12447      * @return Whether or not the click is being handled.
12448      * @hide
12449      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12450     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12451         Preconditions.checkNotNull(clickedSpan);
12452         if (mText instanceof Spanned) {
12453             final Spanned spanned = (Spanned) mText;
12454             final int start = spanned.getSpanStart(clickedSpan);
12455             final int end = spanned.getSpanEnd(clickedSpan);
12456             if (start >= 0 && end <= mText.length() && start < end) {
12457                 final TextClassification.Request request = new TextClassification.Request.Builder(
12458                         mText, start, end)
12459                         .setDefaultLocales(getTextLocales())
12460                         .build();
12461                 final Supplier<TextClassification> supplier = () ->
12462                         getTextClassifier().classifyText(request);
12463                 final Consumer<TextClassification> consumer = classification -> {
12464                     if (classification != null) {
12465                         if (!classification.getActions().isEmpty()) {
12466                             try {
12467                                 classification.getActions().get(0).getActionIntent().send();
12468                             } catch (PendingIntent.CanceledException e) {
12469                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
12470                             }
12471                         } else {
12472                             Log.d(LOG_TAG, "No link action to perform");
12473                         }
12474                     } else {
12475                         // classification == null
12476                         Log.d(LOG_TAG, "Timeout while classifying text");
12477                     }
12478                 };
12479                 CompletableFuture.supplyAsync(supplier)
12480                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
12481                         .thenAccept(consumer);
12482                 return true;
12483             }
12484         }
12485         return false;
12486     }
12487 
12488     /**
12489      * @hide
12490      */
12491     @UnsupportedAppUsage
stopTextActionMode()12492     protected void stopTextActionMode() {
12493         if (mEditor != null) {
12494             mEditor.stopTextActionMode();
12495         }
12496     }
12497 
12498     /** @hide */
hideFloatingToolbar(int durationMs)12499     public void hideFloatingToolbar(int durationMs) {
12500         if (mEditor != null) {
12501             mEditor.hideFloatingToolbar(durationMs);
12502         }
12503     }
12504 
canUndo()12505     boolean canUndo() {
12506         return mEditor != null && mEditor.canUndo();
12507     }
12508 
canRedo()12509     boolean canRedo() {
12510         return mEditor != null && mEditor.canRedo();
12511     }
12512 
canCut()12513     boolean canCut() {
12514         if (hasPasswordTransformationMethod()) {
12515             return false;
12516         }
12517 
12518         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
12519                 && mEditor.mKeyListener != null) {
12520             return true;
12521         }
12522 
12523         return false;
12524     }
12525 
canCopy()12526     boolean canCopy() {
12527         if (hasPasswordTransformationMethod()) {
12528             return false;
12529         }
12530 
12531         if (mText.length() > 0 && hasSelection() && mEditor != null) {
12532             return true;
12533         }
12534 
12535         return false;
12536     }
12537 
canShare()12538     boolean canShare() {
12539         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
12540             return false;
12541         }
12542         return canCopy();
12543     }
12544 
isDeviceProvisioned()12545     boolean isDeviceProvisioned() {
12546         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
12547             mDeviceProvisionedState = Settings.Global.getInt(
12548                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
12549                     ? DEVICE_PROVISIONED_YES
12550                     : DEVICE_PROVISIONED_NO;
12551         }
12552         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
12553     }
12554 
12555     @UnsupportedAppUsage
canPaste()12556     boolean canPaste() {
12557         return (mText instanceof Editable
12558                 && mEditor != null && mEditor.mKeyListener != null
12559                 && getSelectionStart() >= 0
12560                 && getSelectionEnd() >= 0
12561                 && getClipboardManagerForUser().hasPrimaryClip());
12562     }
12563 
canPasteAsPlainText()12564     boolean canPasteAsPlainText() {
12565         if (!canPaste()) {
12566             return false;
12567         }
12568 
12569         final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
12570         final ClipDescription description = clipData.getDescription();
12571         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
12572         final CharSequence text = clipData.getItemAt(0).getText();
12573         if (isPlainType && (text instanceof Spanned)) {
12574             Spanned spanned = (Spanned) text;
12575             if (TextUtils.hasStyleSpan(spanned)) {
12576                 return true;
12577             }
12578         }
12579         return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
12580     }
12581 
canProcessText()12582     boolean canProcessText() {
12583         if (getId() == View.NO_ID) {
12584             return false;
12585         }
12586         return canShare();
12587     }
12588 
canSelectAllText()12589     boolean canSelectAllText() {
12590         return canSelectText() && !hasPasswordTransformationMethod()
12591                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
12592     }
12593 
selectAllText()12594     boolean selectAllText() {
12595         if (mEditor != null) {
12596             // Hide the toolbar before changing the selection to avoid flickering.
12597             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
12598         }
12599         final int length = mText.length();
12600         Selection.setSelection(mSpannable, 0, length);
12601         return length > 0;
12602     }
12603 
replaceSelectionWithText(CharSequence text)12604     void replaceSelectionWithText(CharSequence text) {
12605         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
12606     }
12607 
12608     /**
12609      * Paste clipboard content between min and max positions.
12610      */
paste(int min, int max, boolean withFormatting)12611     private void paste(int min, int max, boolean withFormatting) {
12612         ClipboardManager clipboard = getClipboardManagerForUser();
12613         ClipData clip = clipboard.getPrimaryClip();
12614         if (clip != null) {
12615             boolean didFirst = false;
12616             for (int i = 0; i < clip.getItemCount(); i++) {
12617                 final CharSequence paste;
12618                 if (withFormatting) {
12619                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
12620                 } else {
12621                     // Get an item as text and remove all spans by toString().
12622                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
12623                     paste = (text instanceof Spanned) ? text.toString() : text;
12624                 }
12625                 if (paste != null) {
12626                     if (!didFirst) {
12627                         Selection.setSelection(mSpannable, max);
12628                         ((Editable) mText).replace(min, max, paste);
12629                         didFirst = true;
12630                     } else {
12631                         ((Editable) mText).insert(getSelectionEnd(), "\n");
12632                         ((Editable) mText).insert(getSelectionEnd(), paste);
12633                     }
12634                 }
12635             }
12636             sLastCutCopyOrTextChangedTime = 0;
12637         }
12638     }
12639 
shareSelectedText()12640     private void shareSelectedText() {
12641         String selectedText = getSelectedText();
12642         if (selectedText != null && !selectedText.isEmpty()) {
12643             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
12644             sharingIntent.setType("text/plain");
12645             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
12646             selectedText = TextUtils.trimToParcelableSize(selectedText);
12647             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
12648             getContext().startActivity(Intent.createChooser(sharingIntent, null));
12649             Selection.setSelection(mSpannable, getSelectionEnd());
12650         }
12651     }
12652 
12653     @CheckResult
setPrimaryClip(ClipData clip)12654     private boolean setPrimaryClip(ClipData clip) {
12655         ClipboardManager clipboard = getClipboardManagerForUser();
12656         try {
12657             clipboard.setPrimaryClip(clip);
12658         } catch (Throwable t) {
12659             return false;
12660         }
12661         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
12662         return true;
12663     }
12664 
12665     /**
12666      * Get the character offset closest to the specified absolute position. A typical use case is to
12667      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
12668      *
12669      * @param x The horizontal absolute position of a point on screen
12670      * @param y The vertical absolute position of a point on screen
12671      * @return the character offset for the character whose position is closest to the specified
12672      *  position. Returns -1 if there is no layout.
12673      */
getOffsetForPosition(float x, float y)12674     public int getOffsetForPosition(float x, float y) {
12675         if (getLayout() == null) return -1;
12676         final int line = getLineAtCoordinate(y);
12677         final int offset = getOffsetAtCoordinate(line, x);
12678         return offset;
12679     }
12680 
convertToLocalHorizontalCoordinate(float x)12681     float convertToLocalHorizontalCoordinate(float x) {
12682         x -= getTotalPaddingLeft();
12683         // Clamp the position to inside of the view.
12684         x = Math.max(0.0f, x);
12685         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
12686         x += getScrollX();
12687         return x;
12688     }
12689 
12690     @UnsupportedAppUsage
getLineAtCoordinate(float y)12691     int getLineAtCoordinate(float y) {
12692         y -= getTotalPaddingTop();
12693         // Clamp the position to inside of the view.
12694         y = Math.max(0.0f, y);
12695         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
12696         y += getScrollY();
12697         return getLayout().getLineForVertical((int) y);
12698     }
12699 
getLineAtCoordinateUnclamped(float y)12700     int getLineAtCoordinateUnclamped(float y) {
12701         y -= getTotalPaddingTop();
12702         y += getScrollY();
12703         return getLayout().getLineForVertical((int) y);
12704     }
12705 
getOffsetAtCoordinate(int line, float x)12706     int getOffsetAtCoordinate(int line, float x) {
12707         x = convertToLocalHorizontalCoordinate(x);
12708         return getLayout().getOffsetForHorizontal(line, x);
12709     }
12710 
12711     @Override
onDragEvent(DragEvent event)12712     public boolean onDragEvent(DragEvent event) {
12713         switch (event.getAction()) {
12714             case DragEvent.ACTION_DRAG_STARTED:
12715                 return mEditor != null && mEditor.hasInsertionController();
12716 
12717             case DragEvent.ACTION_DRAG_ENTERED:
12718                 TextView.this.requestFocus();
12719                 return true;
12720 
12721             case DragEvent.ACTION_DRAG_LOCATION:
12722                 if (mText instanceof Spannable) {
12723                     final int offset = getOffsetForPosition(event.getX(), event.getY());
12724                     Selection.setSelection(mSpannable, offset);
12725                 }
12726                 return true;
12727 
12728             case DragEvent.ACTION_DROP:
12729                 if (mEditor != null) mEditor.onDrop(event);
12730                 return true;
12731 
12732             case DragEvent.ACTION_DRAG_ENDED:
12733             case DragEvent.ACTION_DRAG_EXITED:
12734             default:
12735                 return true;
12736         }
12737     }
12738 
isInBatchEditMode()12739     boolean isInBatchEditMode() {
12740         if (mEditor == null) return false;
12741         final Editor.InputMethodState ims = mEditor.mInputMethodState;
12742         if (ims != null) {
12743             return ims.mBatchEditNesting > 0;
12744         }
12745         return mEditor.mInBatchEditControllers;
12746     }
12747 
12748     @Override
onRtlPropertiesChanged(int layoutDirection)12749     public void onRtlPropertiesChanged(int layoutDirection) {
12750         super.onRtlPropertiesChanged(layoutDirection);
12751 
12752         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
12753         if (mTextDir != newTextDir) {
12754             mTextDir = newTextDir;
12755             if (mLayout != null) {
12756                 checkForRelayout();
12757             }
12758         }
12759     }
12760 
12761     /**
12762      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
12763      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
12764      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
12765      * return value may not be the same as the one TextView uses if the View's layout direction is
12766      * not resolved or detached from parent root view.
12767      */
getTextDirectionHeuristic()12768     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
12769         if (hasPasswordTransformationMethod()) {
12770             // passwords fields should be LTR
12771             return TextDirectionHeuristics.LTR;
12772         }
12773 
12774         if (mEditor != null
12775                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
12776                     == EditorInfo.TYPE_CLASS_PHONE) {
12777             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
12778             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
12779             // RTL digits.
12780             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
12781             final String zero = symbols.getDigitStrings()[0];
12782             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
12783             // direction.
12784             final int firstCodepoint = zero.codePointAt(0);
12785             final byte digitDirection = Character.getDirectionality(firstCodepoint);
12786             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
12787                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
12788                 return TextDirectionHeuristics.RTL;
12789             } else {
12790                 return TextDirectionHeuristics.LTR;
12791             }
12792         }
12793 
12794         // Always need to resolve layout direction first
12795         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
12796 
12797         // Now, we can select the heuristic
12798         switch (getTextDirection()) {
12799             default:
12800             case TEXT_DIRECTION_FIRST_STRONG:
12801                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
12802                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
12803             case TEXT_DIRECTION_ANY_RTL:
12804                 return TextDirectionHeuristics.ANYRTL_LTR;
12805             case TEXT_DIRECTION_LTR:
12806                 return TextDirectionHeuristics.LTR;
12807             case TEXT_DIRECTION_RTL:
12808                 return TextDirectionHeuristics.RTL;
12809             case TEXT_DIRECTION_LOCALE:
12810                 return TextDirectionHeuristics.LOCALE;
12811             case TEXT_DIRECTION_FIRST_STRONG_LTR:
12812                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
12813             case TEXT_DIRECTION_FIRST_STRONG_RTL:
12814                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
12815         }
12816     }
12817 
12818     /**
12819      * @hide
12820      */
12821     @Override
onResolveDrawables(int layoutDirection)12822     public void onResolveDrawables(int layoutDirection) {
12823         // No need to resolve twice
12824         if (mLastLayoutDirection == layoutDirection) {
12825             return;
12826         }
12827         mLastLayoutDirection = layoutDirection;
12828 
12829         // Resolve drawables
12830         if (mDrawables != null) {
12831             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
12832                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
12833                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
12834                 applyCompoundDrawableTint();
12835             }
12836         }
12837     }
12838 
12839     /**
12840      * Prepares a drawable for display by propagating layout direction and
12841      * drawable state.
12842      *
12843      * @param dr the drawable to prepare
12844      */
prepareDrawableForDisplay(@ullable Drawable dr)12845     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
12846         if (dr == null) {
12847             return;
12848         }
12849 
12850         dr.setLayoutDirection(getLayoutDirection());
12851 
12852         if (dr.isStateful()) {
12853             dr.setState(getDrawableState());
12854             dr.jumpToCurrentState();
12855         }
12856     }
12857 
12858     /**
12859      * @hide
12860      */
resetResolvedDrawables()12861     protected void resetResolvedDrawables() {
12862         super.resetResolvedDrawables();
12863         mLastLayoutDirection = -1;
12864     }
12865 
12866     /**
12867      * @hide
12868      */
viewClicked(InputMethodManager imm)12869     protected void viewClicked(InputMethodManager imm) {
12870         if (imm != null) {
12871             imm.viewClicked(this);
12872         }
12873     }
12874 
12875     /**
12876      * Deletes the range of text [start, end[.
12877      * @hide
12878      */
12879     @UnsupportedAppUsage
deleteText_internal(int start, int end)12880     protected void deleteText_internal(int start, int end) {
12881         ((Editable) mText).delete(start, end);
12882     }
12883 
12884     /**
12885      * Replaces the range of text [start, end[ by replacement text
12886      * @hide
12887      */
replaceText_internal(int start, int end, CharSequence text)12888     protected void replaceText_internal(int start, int end, CharSequence text) {
12889         ((Editable) mText).replace(start, end, text);
12890     }
12891 
12892     /**
12893      * Sets a span on the specified range of text
12894      * @hide
12895      */
setSpan_internal(Object span, int start, int end, int flags)12896     protected void setSpan_internal(Object span, int start, int end, int flags) {
12897         ((Editable) mText).setSpan(span, start, end, flags);
12898     }
12899 
12900     /**
12901      * Moves the cursor to the specified offset position in text
12902      * @hide
12903      */
setCursorPosition_internal(int start, int end)12904     protected void setCursorPosition_internal(int start, int end) {
12905         Selection.setSelection(((Editable) mText), start, end);
12906     }
12907 
12908     /**
12909      * An Editor should be created as soon as any of the editable-specific fields (grouped
12910      * inside the Editor object) is assigned to a non-default value.
12911      * This method will create the Editor if needed.
12912      *
12913      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
12914      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
12915      * Editor for backward compatibility, as soon as one of these fields is assigned.
12916      *
12917      * Also note that for performance reasons, the mEditor is created when needed, but not
12918      * reset when no more edit-specific fields are needed.
12919      */
12920     @UnsupportedAppUsage
createEditorIfNeeded()12921     private void createEditorIfNeeded() {
12922         if (mEditor == null) {
12923             mEditor = new Editor(this);
12924         }
12925     }
12926 
12927     /**
12928      * @hide
12929      */
12930     @Override
12931     @UnsupportedAppUsage
getIterableTextForAccessibility()12932     public CharSequence getIterableTextForAccessibility() {
12933         return mText;
12934     }
12935 
ensureIterableTextForAccessibilitySelectable()12936     private void ensureIterableTextForAccessibilitySelectable() {
12937         if (!(mText instanceof Spannable)) {
12938             setText(mText, BufferType.SPANNABLE);
12939         }
12940     }
12941 
12942     /**
12943      * @hide
12944      */
12945     @Override
getIteratorForGranularity(int granularity)12946     public TextSegmentIterator getIteratorForGranularity(int granularity) {
12947         switch (granularity) {
12948             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
12949                 Spannable text = (Spannable) getIterableTextForAccessibility();
12950                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
12951                     AccessibilityIterators.LineTextSegmentIterator iterator =
12952                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
12953                     iterator.initialize(text, getLayout());
12954                     return iterator;
12955                 }
12956             } break;
12957             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
12958                 Spannable text = (Spannable) getIterableTextForAccessibility();
12959                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
12960                     AccessibilityIterators.PageTextSegmentIterator iterator =
12961                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
12962                     iterator.initialize(this);
12963                     return iterator;
12964                 }
12965             } break;
12966         }
12967         return super.getIteratorForGranularity(granularity);
12968     }
12969 
12970     /**
12971      * @hide
12972      */
12973     @Override
getAccessibilitySelectionStart()12974     public int getAccessibilitySelectionStart() {
12975         return getSelectionStart();
12976     }
12977 
12978     /**
12979      * @hide
12980      */
isAccessibilitySelectionExtendable()12981     public boolean isAccessibilitySelectionExtendable() {
12982         return true;
12983     }
12984 
12985     /**
12986      * @hide
12987      */
12988     @Override
getAccessibilitySelectionEnd()12989     public int getAccessibilitySelectionEnd() {
12990         return getSelectionEnd();
12991     }
12992 
12993     /**
12994      * @hide
12995      */
12996     @Override
setAccessibilitySelection(int start, int end)12997     public void setAccessibilitySelection(int start, int end) {
12998         if (getAccessibilitySelectionStart() == start
12999                 && getAccessibilitySelectionEnd() == end) {
13000             return;
13001         }
13002         CharSequence text = getIterableTextForAccessibility();
13003         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
13004             Selection.setSelection((Spannable) text, start, end);
13005         } else {
13006             Selection.removeSelection((Spannable) text);
13007         }
13008         // Hide all selection controllers used for adjusting selection
13009         // since we are doing so explicitlty by other means and these
13010         // controllers interact with how selection behaves.
13011         if (mEditor != null) {
13012             mEditor.hideCursorAndSpanControllers();
13013             mEditor.stopTextActionMode();
13014         }
13015     }
13016 
13017     /** @hide */
13018     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)13019     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
13020         super.encodeProperties(stream);
13021 
13022         TruncateAt ellipsize = getEllipsize();
13023         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
13024         stream.addProperty("text:textSize", getTextSize());
13025         stream.addProperty("text:scaledTextSize", getScaledTextSize());
13026         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
13027         stream.addProperty("text:selectionStart", getSelectionStart());
13028         stream.addProperty("text:selectionEnd", getSelectionEnd());
13029         stream.addProperty("text:curTextColor", mCurTextColor);
13030         stream.addProperty("text:text", mText == null ? null : mText.toString());
13031         stream.addProperty("text:gravity", mGravity);
13032     }
13033 
13034     /**
13035      * User interface state that is stored by TextView for implementing
13036      * {@link View#onSaveInstanceState}.
13037      */
13038     public static class SavedState extends BaseSavedState {
13039         int selStart = -1;
13040         int selEnd = -1;
13041         @UnsupportedAppUsage
13042         CharSequence text;
13043         boolean frozenWithFocus;
13044         CharSequence error;
13045         ParcelableParcel editorState;  // Optional state from Editor.
13046 
SavedState(Parcelable superState)13047         SavedState(Parcelable superState) {
13048             super(superState);
13049         }
13050 
13051         @Override
writeToParcel(Parcel out, int flags)13052         public void writeToParcel(Parcel out, int flags) {
13053             super.writeToParcel(out, flags);
13054             out.writeInt(selStart);
13055             out.writeInt(selEnd);
13056             out.writeInt(frozenWithFocus ? 1 : 0);
13057             TextUtils.writeToParcel(text, out, flags);
13058 
13059             if (error == null) {
13060                 out.writeInt(0);
13061             } else {
13062                 out.writeInt(1);
13063                 TextUtils.writeToParcel(error, out, flags);
13064             }
13065 
13066             if (editorState == null) {
13067                 out.writeInt(0);
13068             } else {
13069                 out.writeInt(1);
13070                 editorState.writeToParcel(out, flags);
13071             }
13072         }
13073 
13074         @Override
toString()13075         public String toString() {
13076             String str = "TextView.SavedState{"
13077                     + Integer.toHexString(System.identityHashCode(this))
13078                     + " start=" + selStart + " end=" + selEnd;
13079             if (text != null) {
13080                 str += " text=" + text;
13081             }
13082             return str + "}";
13083         }
13084 
13085         @SuppressWarnings("hiding")
13086         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
13087                 new Parcelable.Creator<SavedState>() {
13088                     public SavedState createFromParcel(Parcel in) {
13089                         return new SavedState(in);
13090                     }
13091 
13092                     public SavedState[] newArray(int size) {
13093                         return new SavedState[size];
13094                     }
13095                 };
13096 
SavedState(Parcel in)13097         private SavedState(Parcel in) {
13098             super(in);
13099             selStart = in.readInt();
13100             selEnd = in.readInt();
13101             frozenWithFocus = (in.readInt() != 0);
13102             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13103 
13104             if (in.readInt() != 0) {
13105                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13106             }
13107 
13108             if (in.readInt() != 0) {
13109                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
13110             }
13111         }
13112     }
13113 
13114     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
13115         private char[] mChars;
13116         private int mStart, mLength;
13117 
CharWrapper(char[] chars, int start, int len)13118         public CharWrapper(char[] chars, int start, int len) {
13119             mChars = chars;
13120             mStart = start;
13121             mLength = len;
13122         }
13123 
set(char[] chars, int start, int len)13124         /* package */ void set(char[] chars, int start, int len) {
13125             mChars = chars;
13126             mStart = start;
13127             mLength = len;
13128         }
13129 
length()13130         public int length() {
13131             return mLength;
13132         }
13133 
charAt(int off)13134         public char charAt(int off) {
13135             return mChars[off + mStart];
13136         }
13137 
13138         @Override
toString()13139         public String toString() {
13140             return new String(mChars, mStart, mLength);
13141         }
13142 
subSequence(int start, int end)13143         public CharSequence subSequence(int start, int end) {
13144             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13145                 throw new IndexOutOfBoundsException(start + ", " + end);
13146             }
13147 
13148             return new String(mChars, start + mStart, end - start);
13149         }
13150 
getChars(int start, int end, char[] buf, int off)13151         public void getChars(int start, int end, char[] buf, int off) {
13152             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13153                 throw new IndexOutOfBoundsException(start + ", " + end);
13154             }
13155 
13156             System.arraycopy(mChars, start + mStart, buf, off, end - start);
13157         }
13158 
13159         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13160         public void drawText(BaseCanvas c, int start, int end,
13161                              float x, float y, Paint p) {
13162             c.drawText(mChars, start + mStart, end - start, x, y, p);
13163         }
13164 
13165         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13166         public void drawTextRun(BaseCanvas c, int start, int end,
13167                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
13168             int count = end - start;
13169             int contextCount = contextEnd - contextStart;
13170             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
13171                     contextCount, x, y, isRtl, p);
13172         }
13173 
measureText(int start, int end, Paint p)13174         public float measureText(int start, int end, Paint p) {
13175             return p.measureText(mChars, start + mStart, end - start);
13176         }
13177 
getTextWidths(int start, int end, float[] widths, Paint p)13178         public int getTextWidths(int start, int end, float[] widths, Paint p) {
13179             return p.getTextWidths(mChars, start + mStart, end - start, widths);
13180         }
13181 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13182         public float getTextRunAdvances(int start, int end, int contextStart,
13183                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
13184                 Paint p) {
13185             int count = end - start;
13186             int contextCount = contextEnd - contextStart;
13187             return p.getTextRunAdvances(mChars, start + mStart, count,
13188                     contextStart + mStart, contextCount, isRtl, advances,
13189                     advancesIndex);
13190         }
13191 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13192         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
13193                 int offset, int cursorOpt, Paint p) {
13194             int contextCount = contextEnd - contextStart;
13195             return p.getTextRunCursor(mChars, contextStart + mStart,
13196                     contextCount, isRtl, offset + mStart, cursorOpt);
13197         }
13198     }
13199 
13200     private static final class Marquee {
13201         // TODO: Add an option to configure this
13202         private static final float MARQUEE_DELTA_MAX = 0.07f;
13203         private static final int MARQUEE_DELAY = 1200;
13204         private static final int MARQUEE_DP_PER_SECOND = 30;
13205 
13206         private static final byte MARQUEE_STOPPED = 0x0;
13207         private static final byte MARQUEE_STARTING = 0x1;
13208         private static final byte MARQUEE_RUNNING = 0x2;
13209 
13210         private final WeakReference<TextView> mView;
13211         private final Choreographer mChoreographer;
13212 
13213         private byte mStatus = MARQUEE_STOPPED;
13214         private final float mPixelsPerMs;
13215         private float mMaxScroll;
13216         private float mMaxFadeScroll;
13217         private float mGhostStart;
13218         private float mGhostOffset;
13219         private float mFadeStop;
13220         private int mRepeatLimit;
13221 
13222         private float mScroll;
13223         private long mLastAnimationMs;
13224 
Marquee(TextView v)13225         Marquee(TextView v) {
13226             final float density = v.getContext().getResources().getDisplayMetrics().density;
13227             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
13228             mView = new WeakReference<TextView>(v);
13229             mChoreographer = Choreographer.getInstance();
13230         }
13231 
13232         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
13233             @Override
13234             public void doFrame(long frameTimeNanos) {
13235                 tick();
13236             }
13237         };
13238 
13239         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
13240             @Override
13241             public void doFrame(long frameTimeNanos) {
13242                 mStatus = MARQUEE_RUNNING;
13243                 mLastAnimationMs = mChoreographer.getFrameTime();
13244                 tick();
13245             }
13246         };
13247 
13248         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
13249             @Override
13250             public void doFrame(long frameTimeNanos) {
13251                 if (mStatus == MARQUEE_RUNNING) {
13252                     if (mRepeatLimit >= 0) {
13253                         mRepeatLimit--;
13254                     }
13255                     start(mRepeatLimit);
13256                 }
13257             }
13258         };
13259 
tick()13260         void tick() {
13261             if (mStatus != MARQUEE_RUNNING) {
13262                 return;
13263             }
13264 
13265             mChoreographer.removeFrameCallback(mTickCallback);
13266 
13267             final TextView textView = mView.get();
13268             if (textView != null && (textView.isFocused() || textView.isSelected())) {
13269                 long currentMs = mChoreographer.getFrameTime();
13270                 long deltaMs = currentMs - mLastAnimationMs;
13271                 mLastAnimationMs = currentMs;
13272                 float deltaPx = deltaMs * mPixelsPerMs;
13273                 mScroll += deltaPx;
13274                 if (mScroll > mMaxScroll) {
13275                     mScroll = mMaxScroll;
13276                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
13277                 } else {
13278                     mChoreographer.postFrameCallback(mTickCallback);
13279                 }
13280                 textView.invalidate();
13281             }
13282         }
13283 
stop()13284         void stop() {
13285             mStatus = MARQUEE_STOPPED;
13286             mChoreographer.removeFrameCallback(mStartCallback);
13287             mChoreographer.removeFrameCallback(mRestartCallback);
13288             mChoreographer.removeFrameCallback(mTickCallback);
13289             resetScroll();
13290         }
13291 
resetScroll()13292         private void resetScroll() {
13293             mScroll = 0.0f;
13294             final TextView textView = mView.get();
13295             if (textView != null) textView.invalidate();
13296         }
13297 
start(int repeatLimit)13298         void start(int repeatLimit) {
13299             if (repeatLimit == 0) {
13300                 stop();
13301                 return;
13302             }
13303             mRepeatLimit = repeatLimit;
13304             final TextView textView = mView.get();
13305             if (textView != null && textView.mLayout != null) {
13306                 mStatus = MARQUEE_STARTING;
13307                 mScroll = 0.0f;
13308                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
13309                         - textView.getCompoundPaddingRight();
13310                 final float lineWidth = textView.mLayout.getLineWidth(0);
13311                 final float gap = textWidth / 3.0f;
13312                 mGhostStart = lineWidth - textWidth + gap;
13313                 mMaxScroll = mGhostStart + textWidth;
13314                 mGhostOffset = lineWidth + gap;
13315                 mFadeStop = lineWidth + textWidth / 6.0f;
13316                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
13317 
13318                 textView.invalidate();
13319                 mChoreographer.postFrameCallback(mStartCallback);
13320             }
13321         }
13322 
getGhostOffset()13323         float getGhostOffset() {
13324             return mGhostOffset;
13325         }
13326 
getScroll()13327         float getScroll() {
13328             return mScroll;
13329         }
13330 
getMaxFadeScroll()13331         float getMaxFadeScroll() {
13332             return mMaxFadeScroll;
13333         }
13334 
shouldDrawLeftFade()13335         boolean shouldDrawLeftFade() {
13336             return mScroll <= mFadeStop;
13337         }
13338 
shouldDrawGhost()13339         boolean shouldDrawGhost() {
13340             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
13341         }
13342 
isRunning()13343         boolean isRunning() {
13344             return mStatus == MARQUEE_RUNNING;
13345         }
13346 
isStopped()13347         boolean isStopped() {
13348             return mStatus == MARQUEE_STOPPED;
13349         }
13350     }
13351 
13352     private class ChangeWatcher implements TextWatcher, SpanWatcher {
13353 
13354         private CharSequence mBeforeText;
13355 
beforeTextChanged(CharSequence buffer, int start, int before, int after)13356         public void beforeTextChanged(CharSequence buffer, int start,
13357                                       int before, int after) {
13358             if (DEBUG_EXTRACT) {
13359                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
13360                         + " before=" + before + " after=" + after + ": " + buffer);
13361             }
13362 
13363             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
13364                 mBeforeText = mTransformed.toString();
13365             }
13366 
13367             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
13368         }
13369 
onTextChanged(CharSequence buffer, int start, int before, int after)13370         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
13371             if (DEBUG_EXTRACT) {
13372                 Log.v(LOG_TAG, "onTextChanged start=" + start
13373                         + " before=" + before + " after=" + after + ": " + buffer);
13374             }
13375             TextView.this.handleTextChanged(buffer, start, before, after);
13376 
13377             if (AccessibilityManager.getInstance(mContext).isEnabled()
13378                     && (isFocused() || isSelected() && isShown())) {
13379                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
13380                 mBeforeText = null;
13381             }
13382         }
13383 
afterTextChanged(Editable buffer)13384         public void afterTextChanged(Editable buffer) {
13385             if (DEBUG_EXTRACT) {
13386                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
13387             }
13388             TextView.this.sendAfterTextChanged(buffer);
13389 
13390             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
13391                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
13392             }
13393         }
13394 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13395         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
13396             if (DEBUG_EXTRACT) {
13397                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
13398                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
13399             }
13400             TextView.this.spanChange(buf, what, s, st, e, en);
13401         }
13402 
onSpanAdded(Spannable buf, Object what, int s, int e)13403         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
13404             if (DEBUG_EXTRACT) {
13405                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
13406             }
13407             TextView.this.spanChange(buf, what, -1, s, -1, e);
13408         }
13409 
onSpanRemoved(Spannable buf, Object what, int s, int e)13410         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
13411             if (DEBUG_EXTRACT) {
13412                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
13413             }
13414             TextView.this.spanChange(buf, what, s, -1, e, -1);
13415         }
13416     }
13417 }
13418