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_RENDERING_INFO_KEY;
21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
23 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
24 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
25 
26 import android.R;
27 import android.annotation.CallSuper;
28 import android.annotation.CheckResult;
29 import android.annotation.ColorInt;
30 import android.annotation.DrawableRes;
31 import android.annotation.FloatRange;
32 import android.annotation.IntDef;
33 import android.annotation.IntRange;
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.annotation.Px;
37 import android.annotation.RequiresPermission;
38 import android.annotation.Size;
39 import android.annotation.StringRes;
40 import android.annotation.StyleRes;
41 import android.annotation.XmlRes;
42 import android.app.Activity;
43 import android.app.PendingIntent;
44 import android.app.assist.AssistStructure;
45 import android.compat.annotation.UnsupportedAppUsage;
46 import android.content.ClipData;
47 import android.content.ClipDescription;
48 import android.content.ClipboardManager;
49 import android.content.Context;
50 import android.content.Intent;
51 import android.content.UndoManager;
52 import android.content.pm.PackageManager;
53 import android.content.res.ColorStateList;
54 import android.content.res.CompatibilityInfo;
55 import android.content.res.Configuration;
56 import android.content.res.Resources;
57 import android.content.res.TypedArray;
58 import android.content.res.XmlResourceParser;
59 import android.graphics.BaseCanvas;
60 import android.graphics.BlendMode;
61 import android.graphics.Canvas;
62 import android.graphics.Insets;
63 import android.graphics.Paint;
64 import android.graphics.Paint.FontMetricsInt;
65 import android.graphics.Path;
66 import android.graphics.PorterDuff;
67 import android.graphics.Rect;
68 import android.graphics.RectF;
69 import android.graphics.Typeface;
70 import android.graphics.drawable.Drawable;
71 import android.graphics.fonts.FontStyle;
72 import android.graphics.fonts.FontVariationAxis;
73 import android.icu.text.DecimalFormatSymbols;
74 import android.os.AsyncTask;
75 import android.os.Build;
76 import android.os.Build.VERSION_CODES;
77 import android.os.Bundle;
78 import android.os.LocaleList;
79 import android.os.Parcel;
80 import android.os.Parcelable;
81 import android.os.ParcelableParcel;
82 import android.os.Process;
83 import android.os.SystemClock;
84 import android.os.UserHandle;
85 import android.provider.Settings;
86 import android.text.BoringLayout;
87 import android.text.DynamicLayout;
88 import android.text.Editable;
89 import android.text.GetChars;
90 import android.text.GraphicsOperations;
91 import android.text.InputFilter;
92 import android.text.InputType;
93 import android.text.Layout;
94 import android.text.ParcelableSpan;
95 import android.text.PrecomputedText;
96 import android.text.Selection;
97 import android.text.SpanWatcher;
98 import android.text.Spannable;
99 import android.text.SpannableStringBuilder;
100 import android.text.Spanned;
101 import android.text.SpannedString;
102 import android.text.StaticLayout;
103 import android.text.TextDirectionHeuristic;
104 import android.text.TextDirectionHeuristics;
105 import android.text.TextPaint;
106 import android.text.TextUtils;
107 import android.text.TextUtils.TruncateAt;
108 import android.text.TextWatcher;
109 import android.text.method.AllCapsTransformationMethod;
110 import android.text.method.ArrowKeyMovementMethod;
111 import android.text.method.DateKeyListener;
112 import android.text.method.DateTimeKeyListener;
113 import android.text.method.DialerKeyListener;
114 import android.text.method.DigitsKeyListener;
115 import android.text.method.KeyListener;
116 import android.text.method.LinkMovementMethod;
117 import android.text.method.MetaKeyKeyListener;
118 import android.text.method.MovementMethod;
119 import android.text.method.PasswordTransformationMethod;
120 import android.text.method.SingleLineTransformationMethod;
121 import android.text.method.TextKeyListener;
122 import android.text.method.TimeKeyListener;
123 import android.text.method.TransformationMethod;
124 import android.text.method.TransformationMethod2;
125 import android.text.method.WordIterator;
126 import android.text.style.CharacterStyle;
127 import android.text.style.ClickableSpan;
128 import android.text.style.ParagraphStyle;
129 import android.text.style.SpellCheckSpan;
130 import android.text.style.SuggestionSpan;
131 import android.text.style.URLSpan;
132 import android.text.style.UpdateAppearance;
133 import android.text.util.Linkify;
134 import android.util.AttributeSet;
135 import android.util.DisplayMetrics;
136 import android.util.IntArray;
137 import android.util.Log;
138 import android.util.SparseIntArray;
139 import android.util.TypedValue;
140 import android.view.AccessibilityIterators.TextSegmentIterator;
141 import android.view.ActionMode;
142 import android.view.Choreographer;
143 import android.view.ContextMenu;
144 import android.view.DragEvent;
145 import android.view.Gravity;
146 import android.view.HapticFeedbackConstants;
147 import android.view.InputDevice;
148 import android.view.KeyCharacterMap;
149 import android.view.KeyEvent;
150 import android.view.MotionEvent;
151 import android.view.PointerIcon;
152 import android.view.View;
153 import android.view.ViewConfiguration;
154 import android.view.ViewDebug;
155 import android.view.ViewGroup.LayoutParams;
156 import android.view.ViewHierarchyEncoder;
157 import android.view.ViewParent;
158 import android.view.ViewRootImpl;
159 import android.view.ViewStructure;
160 import android.view.ViewTreeObserver;
161 import android.view.accessibility.AccessibilityEvent;
162 import android.view.accessibility.AccessibilityManager;
163 import android.view.accessibility.AccessibilityNodeInfo;
164 import android.view.animation.AnimationUtils;
165 import android.view.autofill.AutofillManager;
166 import android.view.autofill.AutofillValue;
167 import android.view.contentcapture.ContentCaptureManager;
168 import android.view.contentcapture.ContentCaptureSession;
169 import android.view.inputmethod.BaseInputConnection;
170 import android.view.inputmethod.CompletionInfo;
171 import android.view.inputmethod.CorrectionInfo;
172 import android.view.inputmethod.CursorAnchorInfo;
173 import android.view.inputmethod.EditorInfo;
174 import android.view.inputmethod.ExtractedText;
175 import android.view.inputmethod.ExtractedTextRequest;
176 import android.view.inputmethod.InputConnection;
177 import android.view.inputmethod.InputMethodManager;
178 import android.view.inspector.InspectableProperty;
179 import android.view.inspector.InspectableProperty.EnumEntry;
180 import android.view.inspector.InspectableProperty.FlagEntry;
181 import android.view.textclassifier.TextClassification;
182 import android.view.textclassifier.TextClassificationContext;
183 import android.view.textclassifier.TextClassificationManager;
184 import android.view.textclassifier.TextClassifier;
185 import android.view.textclassifier.TextLinks;
186 import android.view.textservice.SpellCheckerSubtype;
187 import android.view.textservice.TextServicesManager;
188 import android.widget.RemoteViews.RemoteView;
189 
190 import com.android.internal.annotations.VisibleForTesting;
191 import com.android.internal.logging.MetricsLogger;
192 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
193 import com.android.internal.util.FastMath;
194 import com.android.internal.util.Preconditions;
195 import com.android.internal.widget.EditableInputConnection;
196 
197 import libcore.util.EmptyArray;
198 
199 import org.xmlpull.v1.XmlPullParserException;
200 
201 import java.io.IOException;
202 import java.lang.annotation.Retention;
203 import java.lang.annotation.RetentionPolicy;
204 import java.lang.ref.WeakReference;
205 import java.util.ArrayList;
206 import java.util.Arrays;
207 import java.util.Locale;
208 import java.util.Objects;
209 import java.util.concurrent.CompletableFuture;
210 import java.util.concurrent.TimeUnit;
211 import java.util.function.Consumer;
212 import java.util.function.Supplier;
213 
214 /**
215  * A user interface element that displays text to the user.
216  * To provide user-editable text, see {@link EditText}.
217  * <p>
218  * The following code sample shows a typical use, with an XML layout
219  * and code to modify the contents of the text view:
220  * </p>
221 
222  * <pre>
223  * &lt;LinearLayout
224        xmlns:android="http://schemas.android.com/apk/res/android"
225        android:layout_width="match_parent"
226        android:layout_height="match_parent"&gt;
227  *    &lt;TextView
228  *        android:id="@+id/text_view_id"
229  *        android:layout_height="wrap_content"
230  *        android:layout_width="wrap_content"
231  *        android:text="@string/hello" /&gt;
232  * &lt;/LinearLayout&gt;
233  * </pre>
234  * <p>
235  * This code sample demonstrates how to modify the contents of the text view
236  * defined in the previous XML layout:
237  * </p>
238  * <pre>
239  * public class MainActivity extends Activity {
240  *
241  *    protected void onCreate(Bundle savedInstanceState) {
242  *         super.onCreate(savedInstanceState);
243  *         setContentView(R.layout.activity_main);
244  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
245  *         helloTextView.setText(R.string.user_greeting);
246  *     }
247  * }
248  * </pre>
249  * <p>
250  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
251  * </p>
252  * <p>
253  * <b>XML attributes</b>
254  * <p>
255  * See {@link android.R.styleable#TextView TextView Attributes},
256  * {@link android.R.styleable#View View Attributes}
257  *
258  * @attr ref android.R.styleable#TextView_text
259  * @attr ref android.R.styleable#TextView_bufferType
260  * @attr ref android.R.styleable#TextView_hint
261  * @attr ref android.R.styleable#TextView_textColor
262  * @attr ref android.R.styleable#TextView_textColorHighlight
263  * @attr ref android.R.styleable#TextView_textColorHint
264  * @attr ref android.R.styleable#TextView_textAppearance
265  * @attr ref android.R.styleable#TextView_textColorLink
266  * @attr ref android.R.styleable#TextView_textFontWeight
267  * @attr ref android.R.styleable#TextView_textSize
268  * @attr ref android.R.styleable#TextView_textScaleX
269  * @attr ref android.R.styleable#TextView_fontFamily
270  * @attr ref android.R.styleable#TextView_typeface
271  * @attr ref android.R.styleable#TextView_textStyle
272  * @attr ref android.R.styleable#TextView_cursorVisible
273  * @attr ref android.R.styleable#TextView_maxLines
274  * @attr ref android.R.styleable#TextView_maxHeight
275  * @attr ref android.R.styleable#TextView_lines
276  * @attr ref android.R.styleable#TextView_height
277  * @attr ref android.R.styleable#TextView_minLines
278  * @attr ref android.R.styleable#TextView_minHeight
279  * @attr ref android.R.styleable#TextView_maxEms
280  * @attr ref android.R.styleable#TextView_maxWidth
281  * @attr ref android.R.styleable#TextView_ems
282  * @attr ref android.R.styleable#TextView_width
283  * @attr ref android.R.styleable#TextView_minEms
284  * @attr ref android.R.styleable#TextView_minWidth
285  * @attr ref android.R.styleable#TextView_gravity
286  * @attr ref android.R.styleable#TextView_scrollHorizontally
287  * @attr ref android.R.styleable#TextView_password
288  * @attr ref android.R.styleable#TextView_singleLine
289  * @attr ref android.R.styleable#TextView_selectAllOnFocus
290  * @attr ref android.R.styleable#TextView_includeFontPadding
291  * @attr ref android.R.styleable#TextView_maxLength
292  * @attr ref android.R.styleable#TextView_shadowColor
293  * @attr ref android.R.styleable#TextView_shadowDx
294  * @attr ref android.R.styleable#TextView_shadowDy
295  * @attr ref android.R.styleable#TextView_shadowRadius
296  * @attr ref android.R.styleable#TextView_autoLink
297  * @attr ref android.R.styleable#TextView_linksClickable
298  * @attr ref android.R.styleable#TextView_numeric
299  * @attr ref android.R.styleable#TextView_digits
300  * @attr ref android.R.styleable#TextView_phoneNumber
301  * @attr ref android.R.styleable#TextView_inputMethod
302  * @attr ref android.R.styleable#TextView_capitalize
303  * @attr ref android.R.styleable#TextView_autoText
304  * @attr ref android.R.styleable#TextView_editable
305  * @attr ref android.R.styleable#TextView_freezesText
306  * @attr ref android.R.styleable#TextView_ellipsize
307  * @attr ref android.R.styleable#TextView_drawableTop
308  * @attr ref android.R.styleable#TextView_drawableBottom
309  * @attr ref android.R.styleable#TextView_drawableRight
310  * @attr ref android.R.styleable#TextView_drawableLeft
311  * @attr ref android.R.styleable#TextView_drawableStart
312  * @attr ref android.R.styleable#TextView_drawableEnd
313  * @attr ref android.R.styleable#TextView_drawablePadding
314  * @attr ref android.R.styleable#TextView_drawableTint
315  * @attr ref android.R.styleable#TextView_drawableTintMode
316  * @attr ref android.R.styleable#TextView_lineSpacingExtra
317  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
318  * @attr ref android.R.styleable#TextView_justificationMode
319  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
320  * @attr ref android.R.styleable#TextView_inputType
321  * @attr ref android.R.styleable#TextView_imeOptions
322  * @attr ref android.R.styleable#TextView_privateImeOptions
323  * @attr ref android.R.styleable#TextView_imeActionLabel
324  * @attr ref android.R.styleable#TextView_imeActionId
325  * @attr ref android.R.styleable#TextView_editorExtras
326  * @attr ref android.R.styleable#TextView_elegantTextHeight
327  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
328  * @attr ref android.R.styleable#TextView_letterSpacing
329  * @attr ref android.R.styleable#TextView_fontFeatureSettings
330  * @attr ref android.R.styleable#TextView_fontVariationSettings
331  * @attr ref android.R.styleable#TextView_breakStrategy
332  * @attr ref android.R.styleable#TextView_hyphenationFrequency
333  * @attr ref android.R.styleable#TextView_autoSizeTextType
334  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
335  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
336  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
337  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
338  * @attr ref android.R.styleable#TextView_textCursorDrawable
339  * @attr ref android.R.styleable#TextView_textSelectHandle
340  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
341  * @attr ref android.R.styleable#TextView_textSelectHandleRight
342  * @attr ref android.R.styleable#TextView_allowUndo
343  * @attr ref android.R.styleable#TextView_enabled
344  */
345 @RemoteView
346 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
347     static final String LOG_TAG = "TextView";
348     static final boolean DEBUG_EXTRACT = false;
349     static final boolean DEBUG_CURSOR = false;
350 
351     private static final float[] TEMP_POSITION = new float[2];
352 
353     // Enum for the "typeface" XML parameter.
354     // TODO: How can we get this from the XML instead of hardcoding it here?
355     /** @hide */
356     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
357     @Retention(RetentionPolicy.SOURCE)
358     public @interface XMLTypefaceAttr{}
359     private static final int DEFAULT_TYPEFACE = -1;
360     private static final int SANS = 1;
361     private static final int SERIF = 2;
362     private static final int MONOSPACE = 3;
363 
364     // Enum for the "ellipsize" XML parameter.
365     private static final int ELLIPSIZE_NOT_SET = -1;
366     private static final int ELLIPSIZE_NONE = 0;
367     private static final int ELLIPSIZE_START = 1;
368     private static final int ELLIPSIZE_MIDDLE = 2;
369     private static final int ELLIPSIZE_END = 3;
370     private static final int ELLIPSIZE_MARQUEE = 4;
371 
372     // Bitfield for the "numeric" XML parameter.
373     // TODO: How can we get this from the XML instead of hardcoding it here?
374     private static final int SIGNED = 2;
375     private static final int DECIMAL = 4;
376 
377     /**
378      * Draw marquee text with fading edges as usual
379      */
380     private static final int MARQUEE_FADE_NORMAL = 0;
381 
382     /**
383      * Draw marquee text as ellipsize end while inactive instead of with the fade.
384      * (Useful for devices where the fade can be expensive if overdone)
385      */
386     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
387 
388     /**
389      * Draw marquee text with fading edges because it is currently active/animating.
390      */
391     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
392 
393     @UnsupportedAppUsage
394     private static final int LINES = 1;
395     private static final int EMS = LINES;
396     private static final int PIXELS = 2;
397 
398     private static final RectF TEMP_RECTF = new RectF();
399 
400     /** @hide */
401     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
402     private static final int ANIMATED_SCROLL_GAP = 250;
403 
404     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
405     private static final Spanned EMPTY_SPANNED = new SpannedString("");
406 
407     private static final int CHANGE_WATCHER_PRIORITY = 100;
408 
409     // New state used to change background based on whether this TextView is multiline.
410     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
411 
412     // Accessibility action to share selected text.
413     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
414 
415     /**
416      * @hide
417      */
418     // Accessibility action start id for "process text" actions.
419     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
420 
421     /**
422      * @hide
423      */
424     static final int PROCESS_TEXT_REQUEST_CODE = 100;
425 
426     /**
427      *  Return code of {@link #doKeyDown}.
428      */
429     private static final int KEY_EVENT_NOT_HANDLED = 0;
430     private static final int KEY_EVENT_HANDLED = -1;
431     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
432     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
433 
434     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
435 
436     // System wide time for last cut, copy or text changed action.
437     static long sLastCutCopyOrTextChangedTime;
438 
439     private ColorStateList mTextColor;
440     private ColorStateList mHintTextColor;
441     private ColorStateList mLinkTextColor;
442     @ViewDebug.ExportedProperty(category = "text")
443 
444     /**
445      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
446      */
447     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
448     private int mCurTextColor;
449 
450     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
451     private int mCurHintTextColor;
452     private boolean mFreezesText;
453 
454     @UnsupportedAppUsage
455     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
456     @UnsupportedAppUsage
457     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
458 
459     @UnsupportedAppUsage
460     private float mShadowRadius;
461     @UnsupportedAppUsage
462     private float mShadowDx;
463     @UnsupportedAppUsage
464     private float mShadowDy;
465     private int mShadowColor;
466 
467     private boolean mPreDrawRegistered;
468     private boolean mPreDrawListenerDetached;
469 
470     private TextClassifier mTextClassifier;
471     private TextClassifier mTextClassificationSession;
472     private TextClassificationContext mTextClassificationContext;
473 
474     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
475     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
476     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
477     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
478     // into by the user holding the movement key down) then we shouldn't prevent the focus from
479     // changing.
480     private boolean mPreventDefaultMovement;
481 
482     private TextUtils.TruncateAt mEllipsize;
483 
484     static class Drawables {
485         static final int LEFT = 0;
486         static final int TOP = 1;
487         static final int RIGHT = 2;
488         static final int BOTTOM = 3;
489 
490         static final int DRAWABLE_NONE = -1;
491         static final int DRAWABLE_RIGHT = 0;
492         static final int DRAWABLE_LEFT = 1;
493 
494         final Rect mCompoundRect = new Rect();
495 
496         final Drawable[] mShowing = new Drawable[4];
497 
498         ColorStateList mTintList;
499         BlendMode mBlendMode;
500         boolean mHasTint;
501         boolean mHasTintMode;
502 
503         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
504         Drawable mDrawableLeftInitial, mDrawableRightInitial;
505 
506         boolean mIsRtlCompatibilityMode;
507         boolean mOverride;
508 
509         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
510                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
511 
512         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
513                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
514 
515         int mDrawablePadding;
516 
517         int mDrawableSaved = DRAWABLE_NONE;
518 
Drawables(Context context)519         public Drawables(Context context) {
520             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
521             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
522                     || !context.getApplicationInfo().hasRtlSupport();
523             mOverride = false;
524         }
525 
526         /**
527          * @return {@code true} if this object contains metadata that needs to
528          *         be retained, {@code false} otherwise
529          */
530         public boolean hasMetadata() {
531             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
532         }
533 
534         /**
535          * Updates the list of displayed drawables to account for the current
536          * layout direction.
537          *
538          * @param layoutDirection the current layout direction
539          * @return {@code true} if the displayed drawables changed
540          */
541         public boolean resolveWithLayoutDirection(int layoutDirection) {
542             final Drawable previousLeft = mShowing[Drawables.LEFT];
543             final Drawable previousRight = mShowing[Drawables.RIGHT];
544 
545             // First reset "left" and "right" drawables to their initial values
546             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
547             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
548 
549             if (mIsRtlCompatibilityMode) {
550                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
551                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
552                     mShowing[Drawables.LEFT] = mDrawableStart;
553                     mDrawableSizeLeft = mDrawableSizeStart;
554                     mDrawableHeightLeft = mDrawableHeightStart;
555                 }
556                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
557                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
558                     mShowing[Drawables.RIGHT] = mDrawableEnd;
559                     mDrawableSizeRight = mDrawableSizeEnd;
560                     mDrawableHeightRight = mDrawableHeightEnd;
561                 }
562             } else {
563                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
564                 // drawable if and only if they have been defined
565                 switch(layoutDirection) {
566                     case LAYOUT_DIRECTION_RTL:
567                         if (mOverride) {
568                             mShowing[Drawables.RIGHT] = mDrawableStart;
569                             mDrawableSizeRight = mDrawableSizeStart;
570                             mDrawableHeightRight = mDrawableHeightStart;
571 
572                             mShowing[Drawables.LEFT] = mDrawableEnd;
573                             mDrawableSizeLeft = mDrawableSizeEnd;
574                             mDrawableHeightLeft = mDrawableHeightEnd;
575                         }
576                         break;
577 
578                     case LAYOUT_DIRECTION_LTR:
579                     default:
580                         if (mOverride) {
581                             mShowing[Drawables.LEFT] = mDrawableStart;
582                             mDrawableSizeLeft = mDrawableSizeStart;
583                             mDrawableHeightLeft = mDrawableHeightStart;
584 
585                             mShowing[Drawables.RIGHT] = mDrawableEnd;
586                             mDrawableSizeRight = mDrawableSizeEnd;
587                             mDrawableHeightRight = mDrawableHeightEnd;
588                         }
589                         break;
590                 }
591             }
592 
593             applyErrorDrawableIfNeeded(layoutDirection);
594 
595             return mShowing[Drawables.LEFT] != previousLeft
596                     || mShowing[Drawables.RIGHT] != previousRight;
597         }
598 
599         public void setErrorDrawable(Drawable dr, TextView tv) {
600             if (mDrawableError != dr && mDrawableError != null) {
601                 mDrawableError.setCallback(null);
602             }
603             mDrawableError = dr;
604 
605             if (mDrawableError != null) {
606                 final Rect compoundRect = mCompoundRect;
607                 final int[] state = tv.getDrawableState();
608 
609                 mDrawableError.setState(state);
610                 mDrawableError.copyBounds(compoundRect);
611                 mDrawableError.setCallback(tv);
612                 mDrawableSizeError = compoundRect.width();
613                 mDrawableHeightError = compoundRect.height();
614             } else {
615                 mDrawableSizeError = mDrawableHeightError = 0;
616             }
617         }
618 
619         private void applyErrorDrawableIfNeeded(int layoutDirection) {
620             // first restore the initial state if needed
621             switch (mDrawableSaved) {
622                 case DRAWABLE_LEFT:
623                     mShowing[Drawables.LEFT] = mDrawableTemp;
624                     mDrawableSizeLeft = mDrawableSizeTemp;
625                     mDrawableHeightLeft = mDrawableHeightTemp;
626                     break;
627                 case DRAWABLE_RIGHT:
628                     mShowing[Drawables.RIGHT] = mDrawableTemp;
629                     mDrawableSizeRight = mDrawableSizeTemp;
630                     mDrawableHeightRight = mDrawableHeightTemp;
631                     break;
632                 case DRAWABLE_NONE:
633                 default:
634             }
635             // then, if needed, assign the Error drawable to the correct location
636             if (mDrawableError != null) {
637                 switch(layoutDirection) {
638                     case LAYOUT_DIRECTION_RTL:
639                         mDrawableSaved = DRAWABLE_LEFT;
640 
641                         mDrawableTemp = mShowing[Drawables.LEFT];
642                         mDrawableSizeTemp = mDrawableSizeLeft;
643                         mDrawableHeightTemp = mDrawableHeightLeft;
644 
645                         mShowing[Drawables.LEFT] = mDrawableError;
646                         mDrawableSizeLeft = mDrawableSizeError;
647                         mDrawableHeightLeft = mDrawableHeightError;
648                         break;
649                     case LAYOUT_DIRECTION_LTR:
650                     default:
651                         mDrawableSaved = DRAWABLE_RIGHT;
652 
653                         mDrawableTemp = mShowing[Drawables.RIGHT];
654                         mDrawableSizeTemp = mDrawableSizeRight;
655                         mDrawableHeightTemp = mDrawableHeightRight;
656 
657                         mShowing[Drawables.RIGHT] = mDrawableError;
658                         mDrawableSizeRight = mDrawableSizeError;
659                         mDrawableHeightRight = mDrawableHeightError;
660                         break;
661                 }
662             }
663         }
664     }
665 
666     @UnsupportedAppUsage
667     Drawables mDrawables;
668 
669     @UnsupportedAppUsage
670     private CharWrapper mCharWrapper;
671 
672     @UnsupportedAppUsage(trackingBug = 124050217)
673     private Marquee mMarquee;
674     @UnsupportedAppUsage
675     private boolean mRestartMarquee;
676 
677     private int mMarqueeRepeatLimit = 3;
678 
679     private int mLastLayoutDirection = -1;
680 
681     /**
682      * On some devices the fading edges add a performance penalty if used
683      * extensively in the same layout. This mode indicates how the marquee
684      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
685      */
686     @UnsupportedAppUsage
687     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
688 
689     /**
690      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
691      * the layout that should be used when the mode switches.
692      */
693     @UnsupportedAppUsage
694     private Layout mSavedMarqueeModeLayout;
695 
696     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
697     @ViewDebug.ExportedProperty(category = "text")
698     @UnsupportedAppUsage
699     private @Nullable CharSequence mText;
700     private @Nullable Spannable mSpannable;
701     private @Nullable PrecomputedText mPrecomputed;
702 
703     @UnsupportedAppUsage
704     private CharSequence mTransformed;
705     @UnsupportedAppUsage
706     private BufferType mBufferType = BufferType.NORMAL;
707 
708     private CharSequence mHint;
709     @UnsupportedAppUsage
710     private Layout mHintLayout;
711 
712     private MovementMethod mMovement;
713 
714     private TransformationMethod mTransformation;
715     @UnsupportedAppUsage
716     private boolean mAllowTransformationLengthChange;
717     @UnsupportedAppUsage
718     private ChangeWatcher mChangeWatcher;
719 
720     @UnsupportedAppUsage(trackingBug = 123769451)
721     private ArrayList<TextWatcher> mListeners;
722 
723     // display attributes
724     @UnsupportedAppUsage
725     private final TextPaint mTextPaint;
726     @UnsupportedAppUsage
727     private boolean mUserSetTextScaleX;
728     @UnsupportedAppUsage
729     private Layout mLayout;
730     private boolean mLocalesChanged = false;
731     private int mTextSizeUnit = -1;
732 
733     // True if setKeyListener() has been explicitly called
734     private boolean mListenerChanged = false;
735     // True if internationalized input should be used for numbers and date and time.
736     private final boolean mUseInternationalizedInput;
737     // True if fallback fonts that end up getting used should be allowed to affect line spacing.
738     /* package */ boolean mUseFallbackLineSpacing;
739 
740     @ViewDebug.ExportedProperty(category = "text")
741     @UnsupportedAppUsage
742     private int mGravity = Gravity.TOP | Gravity.START;
743     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
744     private boolean mHorizontallyScrolling;
745 
746     private int mAutoLinkMask;
747     private boolean mLinksClickable = true;
748 
749     @UnsupportedAppUsage
750     private float mSpacingMult = 1.0f;
751     @UnsupportedAppUsage
752     private float mSpacingAdd = 0.0f;
753 
754     private int mBreakStrategy;
755     private int mHyphenationFrequency;
756     private int mJustificationMode;
757 
758     @UnsupportedAppUsage
759     private int mMaximum = Integer.MAX_VALUE;
760     @UnsupportedAppUsage
761     private int mMaxMode = LINES;
762     @UnsupportedAppUsage
763     private int mMinimum = 0;
764     @UnsupportedAppUsage
765     private int mMinMode = LINES;
766 
767     @UnsupportedAppUsage
768     private int mOldMaximum = mMaximum;
769     @UnsupportedAppUsage
770     private int mOldMaxMode = mMaxMode;
771 
772     @UnsupportedAppUsage
773     private int mMaxWidth = Integer.MAX_VALUE;
774     @UnsupportedAppUsage
775     private int mMaxWidthMode = PIXELS;
776     @UnsupportedAppUsage
777     private int mMinWidth = 0;
778     @UnsupportedAppUsage
779     private int mMinWidthMode = PIXELS;
780 
781     @UnsupportedAppUsage
782     private boolean mSingleLine;
783     @UnsupportedAppUsage
784     private int mDesiredHeightAtMeasure = -1;
785     @UnsupportedAppUsage
786     private boolean mIncludePad = true;
787     private int mDeferScroll = -1;
788 
789     // tmp primitives, so we don't alloc them on each draw
790     private Rect mTempRect;
791     private long mLastScroll;
792     private Scroller mScroller;
793     private TextPaint mTempTextPaint;
794 
795     @UnsupportedAppUsage
796     private BoringLayout.Metrics mBoring;
797     @UnsupportedAppUsage
798     private BoringLayout.Metrics mHintBoring;
799     @UnsupportedAppUsage
800     private BoringLayout mSavedLayout;
801     @UnsupportedAppUsage
802     private BoringLayout mSavedHintLayout;
803 
804     @UnsupportedAppUsage
805     private TextDirectionHeuristic mTextDir;
806 
807     private InputFilter[] mFilters = NO_FILTERS;
808 
809     /**
810      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
811      * the same as {@link Process#myUserHandle()}.
812      *
813      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
814      * other apps may need to set this so that the system can use right user's resources and
815      * services such as input methods and spell checkers.</p>
816      *
817      * @see #setTextOperationUser(UserHandle)
818      */
819     @Nullable
820     private UserHandle mTextOperationUser;
821 
822     private volatile Locale mCurrentSpellCheckerLocaleCache;
823 
824     // It is possible to have a selection even when mEditor is null (programmatically set, like when
825     // a link is pressed). These highlight-related fields do not go in mEditor.
826     @UnsupportedAppUsage
827     int mHighlightColor = 0x6633B5E5;
828     private Path mHighlightPath;
829     @UnsupportedAppUsage
830     private final Paint mHighlightPaint;
831     @UnsupportedAppUsage
832     private boolean mHighlightPathBogus = true;
833 
834     // Although these fields are specific to editable text, they are not added to Editor because
835     // they are defined by the TextView's style and are theme-dependent.
836     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
837     int mCursorDrawableRes;
838     private Drawable mCursorDrawable;
839     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
840     // by removing it, but we would break apps targeting <= P that use it by reflection.
841     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
842     int mTextSelectHandleLeftRes;
843     private Drawable mTextSelectHandleLeft;
844     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
845     // by removing it, but we would break apps targeting <= P that use it by reflection.
846     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
847     int mTextSelectHandleRightRes;
848     private Drawable mTextSelectHandleRight;
849     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
850     // by removing it, but we would break apps targeting <= P that use it by reflection.
851     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
852     int mTextSelectHandleRes;
853     private Drawable mTextSelectHandle;
854     int mTextEditSuggestionItemLayout;
855     int mTextEditSuggestionContainerLayout;
856     int mTextEditSuggestionHighlightStyle;
857 
858     private static final int NO_POINTER_ID = -1;
859     /**
860      * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among
861      * TextView and the handle views which are rendered on popup windows.
862      */
863     private int mPrimePointerId = NO_POINTER_ID;
864 
865     /**
866      * Whether the prime pointer is from the event delivered to selection handle or insertion
867      * handle.
868      */
869     private boolean mIsPrimePointerFromHandleView;
870 
871     /**
872      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
873      * See {@link #createEditorIfNeeded()}.
874      */
875     @UnsupportedAppUsage
876     private Editor mEditor;
877 
878     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
879     private static final int DEVICE_PROVISIONED_NO = 1;
880     private static final int DEVICE_PROVISIONED_YES = 2;
881 
882     /**
883      * Some special options such as sharing selected text should only be shown if the device
884      * is provisioned. Only check the provisioned state once for a given view instance.
885      */
886     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
887 
888     /**
889      * The TextView does not auto-size text (default).
890      */
891     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
892 
893     /**
894      * The TextView scales text size both horizontally and vertically to fit within the
895      * container.
896      */
897     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
898 
899     /** @hide */
900     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
901             AUTO_SIZE_TEXT_TYPE_NONE,
902             AUTO_SIZE_TEXT_TYPE_UNIFORM
903     })
904     @Retention(RetentionPolicy.SOURCE)
905     public @interface AutoSizeTextType {}
906     // Default minimum size for auto-sizing text in scaled pixels.
907     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
908     // Default maximum size for auto-sizing text in scaled pixels.
909     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
910     // Default value for the step size in pixels.
911     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
912     // Use this to specify that any of the auto-size configuration int values have not been set.
913     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
914     // Auto-size text type.
915     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
916     // Specify if auto-size text is needed.
917     private boolean mNeedsAutoSizeText = false;
918     // Step size for auto-sizing in pixels.
919     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
920     // Minimum text size for auto-sizing in pixels.
921     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
922     // Maximum text size for auto-sizing in pixels.
923     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
924     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
925     // when auto-sizing text.
926     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
927     // Specifies whether auto-size should use the provided auto size steps set or if it should
928     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
929     // mAutoSizeStepGranularityInPx.
930     private boolean mHasPresetAutoSizeValues = false;
931 
932     // Autofill-related attributes
933     //
934     // Indicates whether the text was set statically or dynamically, so it can be used to
935     // sanitize autofill requests.
936     private boolean mTextSetFromXmlOrResourceId = false;
937     // Resource id used to set the text.
938     private @StringRes int mTextId = Resources.ID_NULL;
939     // Resource id used to set the hint.
940     private @StringRes int mHintId = Resources.ID_NULL;
941     //
942     // End of autofill-related attributes
943 
944     /**
945      * Kick-start the font cache for the zygote process (to pay the cost of
946      * initializing freetype for our default font only once).
947      * @hide
948      */
949     public static void preloadFontCache() {
950         Paint p = new Paint();
951         p.setAntiAlias(true);
952         // Ensure that the Typeface is loaded here.
953         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
954         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
955         // since Paint.measureText can not be called without Typeface static initializer.
956         p.setTypeface(Typeface.DEFAULT);
957         // We don't care about the result, just the side-effect of measuring.
958         p.measureText("H");
959     }
960 
961     /**
962      * Interface definition for a callback to be invoked when an action is
963      * performed on the editor.
964      */
965     public interface OnEditorActionListener {
966         /**
967          * Called when an action is being performed.
968          *
969          * @param v The view that was clicked.
970          * @param actionId Identifier of the action.  This will be either the
971          * identifier you supplied, or {@link EditorInfo#IME_NULL
972          * EditorInfo.IME_NULL} if being called due to the enter key
973          * being pressed.
974          * @param event If triggered by an enter key, this is the event;
975          * otherwise, this is null.
976          * @return Return true if you have consumed the action, else false.
977          */
978         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
979     }
980 
981     public TextView(Context context) {
982         this(context, null);
983     }
984 
985     public TextView(Context context, @Nullable AttributeSet attrs) {
986         this(context, attrs, com.android.internal.R.attr.textViewStyle);
987     }
988 
989     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
990         this(context, attrs, defStyleAttr, 0);
991     }
992 
993     @SuppressWarnings("deprecation")
994     public TextView(
995             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
996         super(context, attrs, defStyleAttr, defStyleRes);
997 
998         // TextView is important by default, unless app developer overrode attribute.
999         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
1000             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
1001         }
1002         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
1003             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
1004         }
1005 
1006         setTextInternal("");
1007 
1008         final Resources res = getResources();
1009         final CompatibilityInfo compat = res.getCompatibilityInfo();
1010 
1011         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
1012         mTextPaint.density = res.getDisplayMetrics().density;
1013         mTextPaint.setCompatibilityScaling(compat.applicationScale);
1014 
1015         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1016         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
1017 
1018         mMovement = getDefaultMovementMethod();
1019 
1020         mTransformation = null;
1021 
1022         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
1023         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
1024         attributes.mTextSize = 15;
1025         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1026         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1027         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1028 
1029         final Resources.Theme theme = context.getTheme();
1030 
1031         /*
1032          * Look the appearance up without checking first if it exists because
1033          * almost every TextView has one and it greatly simplifies the logic
1034          * to be able to parse the appearance first and then let specific tags
1035          * for this View override it.
1036          */
1037         TypedArray a = theme.obtainStyledAttributes(attrs,
1038                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1039         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1040                 attrs, a, defStyleAttr, defStyleRes);
1041         TypedArray appearance = null;
1042         int ap = a.getResourceId(
1043                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1044         a.recycle();
1045         if (ap != -1) {
1046             appearance = theme.obtainStyledAttributes(
1047                     ap, com.android.internal.R.styleable.TextAppearance);
1048             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1049                     null, appearance, 0, ap);
1050         }
1051         if (appearance != null) {
1052             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1053             attributes.mFontFamilyExplicit = false;
1054             appearance.recycle();
1055         }
1056 
1057         boolean editable = getDefaultEditable();
1058         CharSequence inputMethod = null;
1059         int numeric = 0;
1060         CharSequence digits = null;
1061         boolean phone = false;
1062         boolean autotext = false;
1063         int autocap = -1;
1064         int buffertype = 0;
1065         boolean selectallonfocus = false;
1066         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1067                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1068         ColorStateList drawableTint = null;
1069         BlendMode drawableTintMode = null;
1070         int drawablePadding = 0;
1071         int ellipsize = ELLIPSIZE_NOT_SET;
1072         boolean singleLine = false;
1073         int maxlength = -1;
1074         CharSequence text = "";
1075         CharSequence hint = null;
1076         boolean password = false;
1077         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1078         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1079         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1080         int inputType = EditorInfo.TYPE_NULL;
1081         a = theme.obtainStyledAttributes(
1082                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1083         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1084                 defStyleAttr, defStyleRes);
1085         int firstBaselineToTopHeight = -1;
1086         int lastBaselineToBottomHeight = -1;
1087         int lineHeight = -1;
1088 
1089         readTextAppearance(context, a, attributes, true /* styleArray */);
1090 
1091         int n = a.getIndexCount();
1092 
1093         // Must set id in a temporary variable because it will be reset by setText()
1094         boolean textIsSetFromXml = false;
1095         for (int i = 0; i < n; i++) {
1096             int attr = a.getIndex(i);
1097 
1098             switch (attr) {
1099                 case com.android.internal.R.styleable.TextView_editable:
1100                     editable = a.getBoolean(attr, editable);
1101                     break;
1102 
1103                 case com.android.internal.R.styleable.TextView_inputMethod:
1104                     inputMethod = a.getText(attr);
1105                     break;
1106 
1107                 case com.android.internal.R.styleable.TextView_numeric:
1108                     numeric = a.getInt(attr, numeric);
1109                     break;
1110 
1111                 case com.android.internal.R.styleable.TextView_digits:
1112                     digits = a.getText(attr);
1113                     break;
1114 
1115                 case com.android.internal.R.styleable.TextView_phoneNumber:
1116                     phone = a.getBoolean(attr, phone);
1117                     break;
1118 
1119                 case com.android.internal.R.styleable.TextView_autoText:
1120                     autotext = a.getBoolean(attr, autotext);
1121                     break;
1122 
1123                 case com.android.internal.R.styleable.TextView_capitalize:
1124                     autocap = a.getInt(attr, autocap);
1125                     break;
1126 
1127                 case com.android.internal.R.styleable.TextView_bufferType:
1128                     buffertype = a.getInt(attr, buffertype);
1129                     break;
1130 
1131                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1132                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1133                     break;
1134 
1135                 case com.android.internal.R.styleable.TextView_autoLink:
1136                     mAutoLinkMask = a.getInt(attr, 0);
1137                     break;
1138 
1139                 case com.android.internal.R.styleable.TextView_linksClickable:
1140                     mLinksClickable = a.getBoolean(attr, true);
1141                     break;
1142 
1143                 case com.android.internal.R.styleable.TextView_drawableLeft:
1144                     drawableLeft = a.getDrawable(attr);
1145                     break;
1146 
1147                 case com.android.internal.R.styleable.TextView_drawableTop:
1148                     drawableTop = a.getDrawable(attr);
1149                     break;
1150 
1151                 case com.android.internal.R.styleable.TextView_drawableRight:
1152                     drawableRight = a.getDrawable(attr);
1153                     break;
1154 
1155                 case com.android.internal.R.styleable.TextView_drawableBottom:
1156                     drawableBottom = a.getDrawable(attr);
1157                     break;
1158 
1159                 case com.android.internal.R.styleable.TextView_drawableStart:
1160                     drawableStart = a.getDrawable(attr);
1161                     break;
1162 
1163                 case com.android.internal.R.styleable.TextView_drawableEnd:
1164                     drawableEnd = a.getDrawable(attr);
1165                     break;
1166 
1167                 case com.android.internal.R.styleable.TextView_drawableTint:
1168                     drawableTint = a.getColorStateList(attr);
1169                     break;
1170 
1171                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1172                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1173                             drawableTintMode);
1174                     break;
1175 
1176                 case com.android.internal.R.styleable.TextView_drawablePadding:
1177                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1178                     break;
1179 
1180                 case com.android.internal.R.styleable.TextView_maxLines:
1181                     setMaxLines(a.getInt(attr, -1));
1182                     break;
1183 
1184                 case com.android.internal.R.styleable.TextView_maxHeight:
1185                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1186                     break;
1187 
1188                 case com.android.internal.R.styleable.TextView_lines:
1189                     setLines(a.getInt(attr, -1));
1190                     break;
1191 
1192                 case com.android.internal.R.styleable.TextView_height:
1193                     setHeight(a.getDimensionPixelSize(attr, -1));
1194                     break;
1195 
1196                 case com.android.internal.R.styleable.TextView_minLines:
1197                     setMinLines(a.getInt(attr, -1));
1198                     break;
1199 
1200                 case com.android.internal.R.styleable.TextView_minHeight:
1201                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1202                     break;
1203 
1204                 case com.android.internal.R.styleable.TextView_maxEms:
1205                     setMaxEms(a.getInt(attr, -1));
1206                     break;
1207 
1208                 case com.android.internal.R.styleable.TextView_maxWidth:
1209                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1210                     break;
1211 
1212                 case com.android.internal.R.styleable.TextView_ems:
1213                     setEms(a.getInt(attr, -1));
1214                     break;
1215 
1216                 case com.android.internal.R.styleable.TextView_width:
1217                     setWidth(a.getDimensionPixelSize(attr, -1));
1218                     break;
1219 
1220                 case com.android.internal.R.styleable.TextView_minEms:
1221                     setMinEms(a.getInt(attr, -1));
1222                     break;
1223 
1224                 case com.android.internal.R.styleable.TextView_minWidth:
1225                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1226                     break;
1227 
1228                 case com.android.internal.R.styleable.TextView_gravity:
1229                     setGravity(a.getInt(attr, -1));
1230                     break;
1231 
1232                 case com.android.internal.R.styleable.TextView_hint:
1233                     mHintId = a.getResourceId(attr, Resources.ID_NULL);
1234                     hint = a.getText(attr);
1235                     break;
1236 
1237                 case com.android.internal.R.styleable.TextView_text:
1238                     textIsSetFromXml = true;
1239                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1240                     text = a.getText(attr);
1241                     break;
1242 
1243                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1244                     if (a.getBoolean(attr, false)) {
1245                         setHorizontallyScrolling(true);
1246                     }
1247                     break;
1248 
1249                 case com.android.internal.R.styleable.TextView_singleLine:
1250                     singleLine = a.getBoolean(attr, singleLine);
1251                     break;
1252 
1253                 case com.android.internal.R.styleable.TextView_ellipsize:
1254                     ellipsize = a.getInt(attr, ellipsize);
1255                     break;
1256 
1257                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1258                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1259                     break;
1260 
1261                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1262                     if (!a.getBoolean(attr, true)) {
1263                         setIncludeFontPadding(false);
1264                     }
1265                     break;
1266 
1267                 case com.android.internal.R.styleable.TextView_cursorVisible:
1268                     if (!a.getBoolean(attr, true)) {
1269                         setCursorVisible(false);
1270                     }
1271                     break;
1272 
1273                 case com.android.internal.R.styleable.TextView_maxLength:
1274                     maxlength = a.getInt(attr, -1);
1275                     break;
1276 
1277                 case com.android.internal.R.styleable.TextView_textScaleX:
1278                     setTextScaleX(a.getFloat(attr, 1.0f));
1279                     break;
1280 
1281                 case com.android.internal.R.styleable.TextView_freezesText:
1282                     mFreezesText = a.getBoolean(attr, false);
1283                     break;
1284 
1285                 case com.android.internal.R.styleable.TextView_enabled:
1286                     setEnabled(a.getBoolean(attr, isEnabled()));
1287                     break;
1288 
1289                 case com.android.internal.R.styleable.TextView_password:
1290                     password = a.getBoolean(attr, password);
1291                     break;
1292 
1293                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1294                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1295                     break;
1296 
1297                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1298                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1299                     break;
1300 
1301                 case com.android.internal.R.styleable.TextView_inputType:
1302                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1303                     break;
1304 
1305                 case com.android.internal.R.styleable.TextView_allowUndo:
1306                     createEditorIfNeeded();
1307                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1308                     break;
1309 
1310                 case com.android.internal.R.styleable.TextView_imeOptions:
1311                     createEditorIfNeeded();
1312                     mEditor.createInputContentTypeIfNeeded();
1313                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1314                             mEditor.mInputContentType.imeOptions);
1315                     break;
1316 
1317                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1318                     createEditorIfNeeded();
1319                     mEditor.createInputContentTypeIfNeeded();
1320                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1321                     break;
1322 
1323                 case com.android.internal.R.styleable.TextView_imeActionId:
1324                     createEditorIfNeeded();
1325                     mEditor.createInputContentTypeIfNeeded();
1326                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1327                             mEditor.mInputContentType.imeActionId);
1328                     break;
1329 
1330                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1331                     setPrivateImeOptions(a.getString(attr));
1332                     break;
1333 
1334                 case com.android.internal.R.styleable.TextView_editorExtras:
1335                     try {
1336                         setInputExtras(a.getResourceId(attr, 0));
1337                     } catch (XmlPullParserException e) {
1338                         Log.w(LOG_TAG, "Failure reading input extras", e);
1339                     } catch (IOException e) {
1340                         Log.w(LOG_TAG, "Failure reading input extras", e);
1341                     }
1342                     break;
1343 
1344                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1345                     mCursorDrawableRes = a.getResourceId(attr, 0);
1346                     break;
1347 
1348                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1349                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1350                     break;
1351 
1352                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1353                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1354                     break;
1355 
1356                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1357                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1358                     break;
1359 
1360                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1361                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1362                     break;
1363 
1364                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1365                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1366                     break;
1367 
1368                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1369                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1370                     break;
1371 
1372                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1373                     setTextIsSelectable(a.getBoolean(attr, false));
1374                     break;
1375 
1376                 case com.android.internal.R.styleable.TextView_breakStrategy:
1377                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1378                     break;
1379 
1380                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1381                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1382                     break;
1383 
1384                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1385                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1386                     break;
1387 
1388                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1389                     autoSizeStepGranularityInPx = a.getDimension(attr,
1390                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1391                     break;
1392 
1393                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1394                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1395                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1396                     break;
1397 
1398                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1399                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1400                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1401                     break;
1402 
1403                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1404                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1405                     if (autoSizeStepSizeArrayResId > 0) {
1406                         final TypedArray autoSizePresetTextSizes = a.getResources()
1407                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1408                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1409                         autoSizePresetTextSizes.recycle();
1410                     }
1411                     break;
1412                 case com.android.internal.R.styleable.TextView_justificationMode:
1413                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1414                     break;
1415 
1416                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1417                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1418                     break;
1419 
1420                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1421                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1422                     break;
1423 
1424                 case com.android.internal.R.styleable.TextView_lineHeight:
1425                     lineHeight = a.getDimensionPixelSize(attr, -1);
1426                     break;
1427             }
1428         }
1429 
1430         a.recycle();
1431 
1432         BufferType bufferType = BufferType.EDITABLE;
1433 
1434         final int variation =
1435                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1436         final boolean passwordInputType = variation
1437                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1438         final boolean webPasswordInputType = variation
1439                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1440         final boolean numberPasswordInputType = variation
1441                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1442 
1443         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1444         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1445         mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
1446 
1447         if (inputMethod != null) {
1448             Class<?> c;
1449 
1450             try {
1451                 c = Class.forName(inputMethod.toString());
1452             } catch (ClassNotFoundException ex) {
1453                 throw new RuntimeException(ex);
1454             }
1455 
1456             try {
1457                 createEditorIfNeeded();
1458                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1459             } catch (InstantiationException ex) {
1460                 throw new RuntimeException(ex);
1461             } catch (IllegalAccessException ex) {
1462                 throw new RuntimeException(ex);
1463             }
1464             try {
1465                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1466                         ? inputType
1467                         : mEditor.mKeyListener.getInputType();
1468             } catch (IncompatibleClassChangeError e) {
1469                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1470             }
1471         } else if (digits != null) {
1472             createEditorIfNeeded();
1473             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1474             // If no input type was specified, we will default to generic
1475             // text, since we can't tell the IME about the set of digits
1476             // that was selected.
1477             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1478                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1479         } else if (inputType != EditorInfo.TYPE_NULL) {
1480             setInputType(inputType, true);
1481             // If set, the input type overrides what was set using the deprecated singleLine flag.
1482             singleLine = !isMultilineInputType(inputType);
1483         } else if (phone) {
1484             createEditorIfNeeded();
1485             mEditor.mKeyListener = DialerKeyListener.getInstance();
1486             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1487         } else if (numeric != 0) {
1488             createEditorIfNeeded();
1489             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1490                     null,  // locale
1491                     (numeric & SIGNED) != 0,
1492                     (numeric & DECIMAL) != 0);
1493             inputType = mEditor.mKeyListener.getInputType();
1494             mEditor.mInputType = inputType;
1495         } else if (autotext || autocap != -1) {
1496             TextKeyListener.Capitalize cap;
1497 
1498             inputType = EditorInfo.TYPE_CLASS_TEXT;
1499 
1500             switch (autocap) {
1501                 case 1:
1502                     cap = TextKeyListener.Capitalize.SENTENCES;
1503                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1504                     break;
1505 
1506                 case 2:
1507                     cap = TextKeyListener.Capitalize.WORDS;
1508                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1509                     break;
1510 
1511                 case 3:
1512                     cap = TextKeyListener.Capitalize.CHARACTERS;
1513                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1514                     break;
1515 
1516                 default:
1517                     cap = TextKeyListener.Capitalize.NONE;
1518                     break;
1519             }
1520 
1521             createEditorIfNeeded();
1522             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1523             mEditor.mInputType = inputType;
1524         } else if (editable) {
1525             createEditorIfNeeded();
1526             mEditor.mKeyListener = TextKeyListener.getInstance();
1527             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1528         } else if (isTextSelectable()) {
1529             // Prevent text changes from keyboard.
1530             if (mEditor != null) {
1531                 mEditor.mKeyListener = null;
1532                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1533             }
1534             bufferType = BufferType.SPANNABLE;
1535             // So that selection can be changed using arrow keys and touch is handled.
1536             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1537         } else {
1538             if (mEditor != null) mEditor.mKeyListener = null;
1539 
1540             switch (buffertype) {
1541                 case 0:
1542                     bufferType = BufferType.NORMAL;
1543                     break;
1544                 case 1:
1545                     bufferType = BufferType.SPANNABLE;
1546                     break;
1547                 case 2:
1548                     bufferType = BufferType.EDITABLE;
1549                     break;
1550             }
1551         }
1552 
1553         if (mEditor != null) {
1554             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1555                     numberPasswordInputType);
1556         }
1557 
1558         if (selectallonfocus) {
1559             createEditorIfNeeded();
1560             mEditor.mSelectAllOnFocus = true;
1561 
1562             if (bufferType == BufferType.NORMAL) {
1563                 bufferType = BufferType.SPANNABLE;
1564             }
1565         }
1566 
1567         // Set up the tint (if needed) before setting the drawables so that it
1568         // gets applied correctly.
1569         if (drawableTint != null || drawableTintMode != null) {
1570             if (mDrawables == null) {
1571                 mDrawables = new Drawables(context);
1572             }
1573             if (drawableTint != null) {
1574                 mDrawables.mTintList = drawableTint;
1575                 mDrawables.mHasTint = true;
1576             }
1577             if (drawableTintMode != null) {
1578                 mDrawables.mBlendMode = drawableTintMode;
1579                 mDrawables.mHasTintMode = true;
1580             }
1581         }
1582 
1583         // This call will save the initial left/right drawables
1584         setCompoundDrawablesWithIntrinsicBounds(
1585                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1586         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1587         setCompoundDrawablePadding(drawablePadding);
1588 
1589         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1590         // of lines of height are unchanged for multi-line TextViews.
1591         setInputTypeSingleLine(singleLine);
1592         applySingleLine(singleLine, singleLine, singleLine);
1593 
1594         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1595             ellipsize = ELLIPSIZE_END;
1596         }
1597 
1598         switch (ellipsize) {
1599             case ELLIPSIZE_START:
1600                 setEllipsize(TextUtils.TruncateAt.START);
1601                 break;
1602             case ELLIPSIZE_MIDDLE:
1603                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1604                 break;
1605             case ELLIPSIZE_END:
1606                 setEllipsize(TextUtils.TruncateAt.END);
1607                 break;
1608             case ELLIPSIZE_MARQUEE:
1609                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1610                     setHorizontalFadingEdgeEnabled(true);
1611                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1612                 } else {
1613                     setHorizontalFadingEdgeEnabled(false);
1614                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1615                 }
1616                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1617                 break;
1618         }
1619 
1620         final boolean isPassword = password || passwordInputType || webPasswordInputType
1621                 || numberPasswordInputType;
1622         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1623                 && (mEditor.mInputType
1624                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1625                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1626         if (isMonospaceEnforced) {
1627             attributes.mTypefaceIndex = MONOSPACE;
1628         }
1629 
1630         applyTextAppearance(attributes);
1631 
1632         if (isPassword) {
1633             setTransformationMethod(PasswordTransformationMethod.getInstance());
1634         }
1635 
1636         if (maxlength >= 0) {
1637             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1638         } else {
1639             setFilters(NO_FILTERS);
1640         }
1641 
1642         setText(text, bufferType);
1643         if (mText == null) {
1644             mText = "";
1645         }
1646         if (mTransformed == null) {
1647             mTransformed = "";
1648         }
1649 
1650         if (textIsSetFromXml) {
1651             mTextSetFromXmlOrResourceId = true;
1652         }
1653 
1654         if (hint != null) setHint(hint);
1655 
1656         /*
1657          * Views are not normally clickable unless specified to be.
1658          * However, TextViews that have input or movement methods *are*
1659          * clickable by default. By setting clickable here, we implicitly set focusable as well
1660          * if not overridden by the developer.
1661          */
1662         a = context.obtainStyledAttributes(
1663                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1664         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1665         boolean clickable = canInputOrMove || isClickable();
1666         boolean longClickable = canInputOrMove || isLongClickable();
1667         int focusable = getFocusable();
1668 
1669         n = a.getIndexCount();
1670         for (int i = 0; i < n; i++) {
1671             int attr = a.getIndex(i);
1672 
1673             switch (attr) {
1674                 case com.android.internal.R.styleable.View_focusable:
1675                     TypedValue val = new TypedValue();
1676                     if (a.getValue(attr, val)) {
1677                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1678                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1679                                 : val.data;
1680                     }
1681                     break;
1682 
1683                 case com.android.internal.R.styleable.View_clickable:
1684                     clickable = a.getBoolean(attr, clickable);
1685                     break;
1686 
1687                 case com.android.internal.R.styleable.View_longClickable:
1688                     longClickable = a.getBoolean(attr, longClickable);
1689                     break;
1690             }
1691         }
1692         a.recycle();
1693 
1694         // Some apps were relying on the undefined behavior of focusable winning over
1695         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1696         // when starting with EditText and setting only focusable=false). To keep those apps from
1697         // breaking, re-apply the focusable attribute here.
1698         if (focusable != getFocusable()) {
1699             setFocusable(focusable);
1700         }
1701         setClickable(clickable);
1702         setLongClickable(longClickable);
1703 
1704         if (mEditor != null) mEditor.prepareCursorControllers();
1705 
1706         // If not explicitly specified this view is important for accessibility.
1707         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1708             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1709         }
1710 
1711         if (supportsAutoSizeText()) {
1712             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1713                 // If uniform auto-size has been specified but preset values have not been set then
1714                 // replace the auto-size configuration values that have not been specified with the
1715                 // defaults.
1716                 if (!mHasPresetAutoSizeValues) {
1717                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1718 
1719                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1720                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1721                                 TypedValue.COMPLEX_UNIT_SP,
1722                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1723                                 displayMetrics);
1724                     }
1725 
1726                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1727                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1728                                 TypedValue.COMPLEX_UNIT_SP,
1729                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1730                                 displayMetrics);
1731                     }
1732 
1733                     if (autoSizeStepGranularityInPx
1734                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1735                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1736                     }
1737 
1738                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1739                             autoSizeMaxTextSizeInPx,
1740                             autoSizeStepGranularityInPx);
1741                 }
1742 
1743                 setupAutoSizeText();
1744             }
1745         } else {
1746             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1747         }
1748 
1749         if (firstBaselineToTopHeight >= 0) {
1750             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1751         }
1752         if (lastBaselineToBottomHeight >= 0) {
1753             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1754         }
1755         if (lineHeight >= 0) {
1756             setLineHeight(lineHeight);
1757         }
1758     }
1759 
1760     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)1761     private void setTextInternal(@Nullable CharSequence text) {
1762         mText = text;
1763         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
1764         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
1765     }
1766 
1767     /**
1768      * Specify whether this widget should automatically scale the text to try to perfectly fit
1769      * within the layout bounds by using the default auto-size configuration.
1770      *
1771      * @param autoSizeTextType the type of auto-size. Must be one of
1772      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1773      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1774      *
1775      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1776      *
1777      * @attr ref android.R.styleable#TextView_autoSizeTextType
1778      *
1779      * @see #getAutoSizeTextType()
1780      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1781     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1782         if (supportsAutoSizeText()) {
1783             switch (autoSizeTextType) {
1784                 case AUTO_SIZE_TEXT_TYPE_NONE:
1785                     clearAutoSizeConfiguration();
1786                     break;
1787                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1788                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1789                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1790                             TypedValue.COMPLEX_UNIT_SP,
1791                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1792                             displayMetrics);
1793                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1794                             TypedValue.COMPLEX_UNIT_SP,
1795                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1796                             displayMetrics);
1797 
1798                     validateAndSetAutoSizeTextTypeUniformConfiguration(
1799                             autoSizeMinTextSizeInPx,
1800                             autoSizeMaxTextSizeInPx,
1801                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1802                     if (setupAutoSizeText()) {
1803                         autoSizeText();
1804                         invalidate();
1805                     }
1806                     break;
1807                 default:
1808                     throw new IllegalArgumentException(
1809                             "Unknown auto-size text type: " + autoSizeTextType);
1810             }
1811         }
1812     }
1813 
1814     /**
1815      * Specify whether this widget should automatically scale the text to try to perfectly fit
1816      * within the layout bounds. If all the configuration params are valid the type of auto-size is
1817      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1818      *
1819      * @param autoSizeMinTextSize the minimum text size available for auto-size
1820      * @param autoSizeMaxTextSize the maximum text size available for auto-size
1821      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1822      *                                the minimum and maximum text size in order to build the set of
1823      *                                text sizes the system uses to choose from when auto-sizing
1824      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1825      *             possible dimension units
1826      *
1827      * @throws IllegalArgumentException if any of the configuration params are invalid.
1828      *
1829      * @attr ref android.R.styleable#TextView_autoSizeTextType
1830      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1831      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1832      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1833      *
1834      * @see #setAutoSizeTextTypeWithDefaults(int)
1835      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1836      * @see #getAutoSizeMinTextSize()
1837      * @see #getAutoSizeMaxTextSize()
1838      * @see #getAutoSizeStepGranularity()
1839      * @see #getAutoSizeTextAvailableSizes()
1840      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1841     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1842             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1843         if (supportsAutoSizeText()) {
1844             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1845             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1846                     unit, autoSizeMinTextSize, displayMetrics);
1847             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1848                     unit, autoSizeMaxTextSize, displayMetrics);
1849             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1850                     unit, autoSizeStepGranularity, displayMetrics);
1851 
1852             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1853                     autoSizeMaxTextSizeInPx,
1854                     autoSizeStepGranularityInPx);
1855 
1856             if (setupAutoSizeText()) {
1857                 autoSizeText();
1858                 invalidate();
1859             }
1860         }
1861     }
1862 
1863     /**
1864      * Specify whether this widget should automatically scale the text to try to perfectly fit
1865      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1866      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1867      *
1868      * @param presetSizes an {@code int} array of sizes in pixels
1869      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1870      *             the possible dimension units
1871      *
1872      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1873      *
1874      * @attr ref android.R.styleable#TextView_autoSizeTextType
1875      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1876      *
1877      * @see #setAutoSizeTextTypeWithDefaults(int)
1878      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1879      * @see #getAutoSizeMinTextSize()
1880      * @see #getAutoSizeMaxTextSize()
1881      * @see #getAutoSizeTextAvailableSizes()
1882      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1883     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1884         if (supportsAutoSizeText()) {
1885             final int presetSizesLength = presetSizes.length;
1886             if (presetSizesLength > 0) {
1887                 int[] presetSizesInPx = new int[presetSizesLength];
1888 
1889                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1890                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1891                 } else {
1892                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1893                     // Convert all to sizes to pixels.
1894                     for (int i = 0; i < presetSizesLength; i++) {
1895                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
1896                             presetSizes[i], displayMetrics));
1897                     }
1898                 }
1899 
1900                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1901                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
1902                     throw new IllegalArgumentException("None of the preset sizes is valid: "
1903                             + Arrays.toString(presetSizes));
1904                 }
1905             } else {
1906                 mHasPresetAutoSizeValues = false;
1907             }
1908 
1909             if (setupAutoSizeText()) {
1910                 autoSizeText();
1911                 invalidate();
1912             }
1913         }
1914     }
1915 
1916     /**
1917      * Returns the type of auto-size set for this widget.
1918      *
1919      * @return an {@code int} corresponding to one of the auto-size types:
1920      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1921      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1922      *
1923      * @attr ref android.R.styleable#TextView_autoSizeTextType
1924      *
1925      * @see #setAutoSizeTextTypeWithDefaults(int)
1926      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1927      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1928      */
1929     @InspectableProperty(enumMapping = {
1930             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
1931             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
1932     })
1933     @AutoSizeTextType
getAutoSizeTextType()1934     public int getAutoSizeTextType() {
1935         return mAutoSizeTextType;
1936     }
1937 
1938     /**
1939      * @return the current auto-size step granularity in pixels.
1940      *
1941      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1942      *
1943      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1944      */
1945     @InspectableProperty
getAutoSizeStepGranularity()1946     public int getAutoSizeStepGranularity() {
1947         return Math.round(mAutoSizeStepGranularityInPx);
1948     }
1949 
1950     /**
1951      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
1952      *         if auto-size has not been configured this function returns {@code -1}.
1953      *
1954      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1955      *
1956      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1957      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1958      */
1959     @InspectableProperty
getAutoSizeMinTextSize()1960     public int getAutoSizeMinTextSize() {
1961         return Math.round(mAutoSizeMinTextSizeInPx);
1962     }
1963 
1964     /**
1965      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
1966      *         if auto-size has not been configured this function returns {@code -1}.
1967      *
1968      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1969      *
1970      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1971      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1972      */
1973     @InspectableProperty
getAutoSizeMaxTextSize()1974     public int getAutoSizeMaxTextSize() {
1975         return Math.round(mAutoSizeMaxTextSizeInPx);
1976     }
1977 
1978     /**
1979      * @return the current auto-size {@code int} sizes array (in pixels).
1980      *
1981      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1982      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1983      */
getAutoSizeTextAvailableSizes()1984     public int[] getAutoSizeTextAvailableSizes() {
1985         return mAutoSizeTextSizesInPx;
1986     }
1987 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)1988     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
1989         final int textSizesLength = textSizes.length();
1990         final int[] parsedSizes = new int[textSizesLength];
1991 
1992         if (textSizesLength > 0) {
1993             for (int i = 0; i < textSizesLength; i++) {
1994                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
1995             }
1996             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
1997             setupAutoSizeUniformPresetSizesConfiguration();
1998         }
1999     }
2000 
setupAutoSizeUniformPresetSizesConfiguration()2001     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
2002         final int sizesLength = mAutoSizeTextSizesInPx.length;
2003         mHasPresetAutoSizeValues = sizesLength > 0;
2004         if (mHasPresetAutoSizeValues) {
2005             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2006             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
2007             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
2008             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2009         }
2010         return mHasPresetAutoSizeValues;
2011     }
2012 
2013     /**
2014      * If all params are valid then save the auto-size configuration.
2015      *
2016      * @throws IllegalArgumentException if any of the params are invalid
2017      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2018     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
2019             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
2020         // First validate.
2021         if (autoSizeMinTextSizeInPx <= 0) {
2022             throw new IllegalArgumentException("Minimum auto-size text size ("
2023                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
2024         }
2025 
2026         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2027             throw new IllegalArgumentException("Maximum auto-size text size ("
2028                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2029                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2030         }
2031 
2032         if (autoSizeStepGranularityInPx <= 0) {
2033             throw new IllegalArgumentException("The auto-size step granularity ("
2034                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2035         }
2036 
2037         // All good, persist the configuration.
2038         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2039         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2040         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2041         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2042         mHasPresetAutoSizeValues = false;
2043     }
2044 
clearAutoSizeConfiguration()2045     private void clearAutoSizeConfiguration() {
2046         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2047         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2048         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2049         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2050         mAutoSizeTextSizesInPx = EmptyArray.INT;
2051         mNeedsAutoSizeText = false;
2052     }
2053 
2054     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)2055     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2056         final int presetValuesLength = presetValues.length;
2057         if (presetValuesLength == 0) {
2058             return presetValues;
2059         }
2060         Arrays.sort(presetValues);
2061 
2062         final IntArray uniqueValidSizes = new IntArray();
2063         for (int i = 0; i < presetValuesLength; i++) {
2064             final int currentPresetValue = presetValues[i];
2065 
2066             if (currentPresetValue > 0
2067                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2068                 uniqueValidSizes.add(currentPresetValue);
2069             }
2070         }
2071 
2072         return presetValuesLength == uniqueValidSizes.size()
2073             ? presetValues
2074             : uniqueValidSizes.toArray();
2075     }
2076 
setupAutoSizeText()2077     private boolean setupAutoSizeText() {
2078         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2079             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2080             // not have a predefined set of sizes or if the current sizes array is empty.
2081             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2082                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2083                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2084                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2085                 for (int i = 0; i < autoSizeValuesLength; i++) {
2086                     autoSizeTextSizesInPx[i] = Math.round(
2087                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2088                 }
2089                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2090             }
2091 
2092             mNeedsAutoSizeText = true;
2093         } else {
2094             mNeedsAutoSizeText = false;
2095         }
2096 
2097         return mNeedsAutoSizeText;
2098     }
2099 
parseDimensionArray(TypedArray dimens)2100     private int[] parseDimensionArray(TypedArray dimens) {
2101         if (dimens == null) {
2102             return null;
2103         }
2104         int[] result = new int[dimens.length()];
2105         for (int i = 0; i < result.length; i++) {
2106             result[i] = dimens.getDimensionPixelSize(i, 0);
2107         }
2108         return result;
2109     }
2110 
2111     /**
2112      * @hide
2113      */
2114     @Override
onActivityResult(int requestCode, int resultCode, Intent data)2115     public void onActivityResult(int requestCode, int resultCode, Intent data) {
2116         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2117             if (resultCode == Activity.RESULT_OK && data != null) {
2118                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2119                 if (result != null) {
2120                     if (isTextEditable()) {
2121                         replaceSelectionWithText(result);
2122                         if (mEditor != null) {
2123                             mEditor.refreshTextActionMode();
2124                         }
2125                     } else {
2126                         if (result.length() > 0) {
2127                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2128                                 .show();
2129                         }
2130                     }
2131                 }
2132             } else if (mSpannable != null) {
2133                 // Reset the selection.
2134                 Selection.setSelection(mSpannable, getSelectionEnd());
2135             }
2136         }
2137     }
2138 
2139     /**
2140      * Sets the Typeface taking into account the given attributes.
2141      *
2142      * @param typeface a typeface
2143      * @param familyName family name string, e.g. "serif"
2144      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2145      * @param style a typeface style
2146      * @param weight a weight value for the Typeface or -1 if not specified.
2147      */
setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2148     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2149             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2150             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2151         if (typeface == null && familyName != null) {
2152             // Lookup normal Typeface from system font map.
2153             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2154             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2155         } else if (typeface != null) {
2156             resolveStyleAndSetTypeface(typeface, style, weight);
2157         } else {  // both typeface and familyName is null.
2158             switch (typefaceIndex) {
2159                 case SANS:
2160                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2161                     break;
2162                 case SERIF:
2163                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2164                     break;
2165                 case MONOSPACE:
2166                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2167                     break;
2168                 case DEFAULT_TYPEFACE:
2169                 default:
2170                     resolveStyleAndSetTypeface(null, style, weight);
2171                     break;
2172             }
2173         }
2174     }
2175 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2176     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2177             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2178         if (weight >= 0) {
2179             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2180             final boolean italic = (style & Typeface.ITALIC) != 0;
2181             setTypeface(Typeface.create(typeface, weight, italic));
2182         } else {
2183             setTypeface(typeface, style);
2184         }
2185     }
2186 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2187     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2188         boolean hasRelativeDrawables = (start != null) || (end != null);
2189         if (hasRelativeDrawables) {
2190             Drawables dr = mDrawables;
2191             if (dr == null) {
2192                 mDrawables = dr = new Drawables(getContext());
2193             }
2194             mDrawables.mOverride = true;
2195             final Rect compoundRect = dr.mCompoundRect;
2196             int[] state = getDrawableState();
2197             if (start != null) {
2198                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2199                 start.setState(state);
2200                 start.copyBounds(compoundRect);
2201                 start.setCallback(this);
2202 
2203                 dr.mDrawableStart = start;
2204                 dr.mDrawableSizeStart = compoundRect.width();
2205                 dr.mDrawableHeightStart = compoundRect.height();
2206             } else {
2207                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2208             }
2209             if (end != null) {
2210                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2211                 end.setState(state);
2212                 end.copyBounds(compoundRect);
2213                 end.setCallback(this);
2214 
2215                 dr.mDrawableEnd = end;
2216                 dr.mDrawableSizeEnd = compoundRect.width();
2217                 dr.mDrawableHeightEnd = compoundRect.height();
2218             } else {
2219                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2220             }
2221             resetResolvedDrawables();
2222             resolveDrawables();
2223             applyCompoundDrawableTint();
2224         }
2225     }
2226 
2227     @android.view.RemotableViewMethod
2228     @Override
setEnabled(boolean enabled)2229     public void setEnabled(boolean enabled) {
2230         if (enabled == isEnabled()) {
2231             return;
2232         }
2233 
2234         if (!enabled) {
2235             // Hide the soft input if the currently active TextView is disabled
2236             InputMethodManager imm = getInputMethodManager();
2237             if (imm != null && imm.isActive(this)) {
2238                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2239             }
2240         }
2241 
2242         super.setEnabled(enabled);
2243 
2244         if (enabled) {
2245             // Make sure IME is updated with current editor info.
2246             InputMethodManager imm = getInputMethodManager();
2247             if (imm != null) imm.restartInput(this);
2248         }
2249 
2250         // Will change text color
2251         if (mEditor != null) {
2252             mEditor.invalidateTextDisplayList();
2253             mEditor.prepareCursorControllers();
2254 
2255             // start or stop the cursor blinking as appropriate
2256             mEditor.makeBlink();
2257         }
2258     }
2259 
2260     /**
2261      * Sets the typeface and style in which the text should be displayed,
2262      * and turns on the fake bold and italic bits in the Paint if the
2263      * Typeface that you provided does not have all the bits in the
2264      * style that you specified.
2265      *
2266      * @attr ref android.R.styleable#TextView_typeface
2267      * @attr ref android.R.styleable#TextView_textStyle
2268      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2269     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2270         if (style > 0) {
2271             if (tf == null) {
2272                 tf = Typeface.defaultFromStyle(style);
2273             } else {
2274                 tf = Typeface.create(tf, style);
2275             }
2276 
2277             setTypeface(tf);
2278             // now compute what (if any) algorithmic styling is needed
2279             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2280             int need = style & ~typefaceStyle;
2281             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2282             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2283         } else {
2284             mTextPaint.setFakeBoldText(false);
2285             mTextPaint.setTextSkewX(0);
2286             setTypeface(tf);
2287         }
2288     }
2289 
2290     /**
2291      * Subclasses override this to specify that they have a KeyListener
2292      * by default even if not specifically called for in the XML options.
2293      */
getDefaultEditable()2294     protected boolean getDefaultEditable() {
2295         return false;
2296     }
2297 
2298     /**
2299      * Subclasses override this to specify a default movement method.
2300      */
getDefaultMovementMethod()2301     protected MovementMethod getDefaultMovementMethod() {
2302         return null;
2303     }
2304 
2305     /**
2306      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2307      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2308      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2309      * the return value from this method to Spannable or Editable, respectively.
2310      *
2311      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2312      * should make your own copy first.</p>
2313      *
2314      * @return The text displayed by the text view.
2315      * @attr ref android.R.styleable#TextView_text
2316      */
2317     @ViewDebug.CapturedViewProperty
2318     @InspectableProperty
getText()2319     public CharSequence getText() {
2320         return mText;
2321     }
2322 
2323     /**
2324      * Returns the length, in characters, of the text managed by this TextView
2325      * @return The length of the text managed by the TextView in characters.
2326      */
length()2327     public int length() {
2328         return mText.length();
2329     }
2330 
2331     /**
2332      * Return the text that TextView is displaying as an Editable object. If the text is not
2333      * editable, null is returned.
2334      *
2335      * @see #getText
2336      */
getEditableText()2337     public Editable getEditableText() {
2338         return (mText instanceof Editable) ? (Editable) mText : null;
2339     }
2340 
2341     /**
2342      * @hide
2343      */
2344     @VisibleForTesting
getTransformed()2345     public CharSequence getTransformed() {
2346         return mTransformed;
2347     }
2348 
2349     /**
2350      * Gets the vertical distance between lines of text, in pixels.
2351      * Note that markup within the text can cause individual lines
2352      * to be taller or shorter than this height, and the layout may
2353      * contain additional first-or last-line padding.
2354      * @return The height of one standard line in pixels.
2355      */
2356     @InspectableProperty
getLineHeight()2357     public int getLineHeight() {
2358         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2359     }
2360 
2361     /**
2362      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2363      * This value can be null if the text or width has recently changed.
2364      * @return The Layout that is currently being used to display the text.
2365      */
getLayout()2366     public final Layout getLayout() {
2367         return mLayout;
2368     }
2369 
2370     /**
2371      * @return the {@link android.text.Layout} that is currently being used to
2372      * display the hint text. This can be null.
2373      */
2374     @UnsupportedAppUsage
getHintLayout()2375     final Layout getHintLayout() {
2376         return mHintLayout;
2377     }
2378 
2379     /**
2380      * Retrieve the {@link android.content.UndoManager} that is currently associated
2381      * with this TextView.  By default there is no associated UndoManager, so null
2382      * is returned.  One can be associated with the TextView through
2383      * {@link #setUndoManager(android.content.UndoManager, String)}
2384      *
2385      * @hide
2386      */
getUndoManager()2387     public final UndoManager getUndoManager() {
2388         // TODO: Consider supporting a global undo manager.
2389         throw new UnsupportedOperationException("not implemented");
2390     }
2391 
2392 
2393     /**
2394      * @hide
2395      */
2396     @VisibleForTesting
getEditorForTesting()2397     public final Editor getEditorForTesting() {
2398         return mEditor;
2399     }
2400 
2401     /**
2402      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2403      * done, all edit operations on the TextView will result in appropriate
2404      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2405      * stack.
2406      *
2407      * @param undoManager The {@link android.content.UndoManager} to associate with
2408      * this TextView, or null to clear any existing association.
2409      * @param tag String tag identifying this particular TextView owner in the
2410      * UndoManager.  This is used to keep the correct association with the
2411      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2412      *
2413      * @hide
2414      */
setUndoManager(UndoManager undoManager, String tag)2415     public final void setUndoManager(UndoManager undoManager, String tag) {
2416         // TODO: Consider supporting a global undo manager. An implementation will need to:
2417         // * createEditorIfNeeded()
2418         // * Promote to BufferType.EDITABLE if needed.
2419         // * Update the UndoManager and UndoOwner.
2420         // Likewise it will need to be able to restore the default UndoManager.
2421         throw new UnsupportedOperationException("not implemented");
2422     }
2423 
2424     /**
2425      * Gets the current {@link KeyListener} for the TextView.
2426      * This will frequently be null for non-EditText TextViews.
2427      * @return the current key listener for this TextView.
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      */
getKeyListener()2436     public final KeyListener getKeyListener() {
2437         return mEditor == null ? null : mEditor.mKeyListener;
2438     }
2439 
2440     /**
2441      * Sets the key listener to be used with this TextView.  This can be null
2442      * to disallow user input.  Note that this method has significant and
2443      * subtle interactions with soft keyboards and other input method:
2444      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2445      * for important details.  Calling this method will replace the current
2446      * content type of the text view with the content type returned by the
2447      * key listener.
2448      * <p>
2449      * Be warned that if you want a TextView with a key listener or movement
2450      * method not to be focusable, or if you want a TextView without a
2451      * key listener or movement method to be focusable, you must call
2452      * {@link #setFocusable} again after calling this to get the focusability
2453      * back the way you want it.
2454      *
2455      * @attr ref android.R.styleable#TextView_numeric
2456      * @attr ref android.R.styleable#TextView_digits
2457      * @attr ref android.R.styleable#TextView_phoneNumber
2458      * @attr ref android.R.styleable#TextView_inputMethod
2459      * @attr ref android.R.styleable#TextView_capitalize
2460      * @attr ref android.R.styleable#TextView_autoText
2461      */
setKeyListener(KeyListener input)2462     public void setKeyListener(KeyListener input) {
2463         mListenerChanged = true;
2464         setKeyListenerOnly(input);
2465         fixFocusableAndClickableSettings();
2466 
2467         if (input != null) {
2468             createEditorIfNeeded();
2469             setInputTypeFromEditor();
2470         } else {
2471             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2472         }
2473 
2474         InputMethodManager imm = getInputMethodManager();
2475         if (imm != null) imm.restartInput(this);
2476     }
2477 
setInputTypeFromEditor()2478     private void setInputTypeFromEditor() {
2479         try {
2480             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2481         } catch (IncompatibleClassChangeError e) {
2482             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2483         }
2484         // Change inputType, without affecting transformation.
2485         // No need to applySingleLine since mSingleLine is unchanged.
2486         setInputTypeSingleLine(mSingleLine);
2487     }
2488 
setKeyListenerOnly(KeyListener input)2489     private void setKeyListenerOnly(KeyListener input) {
2490         if (mEditor == null && input == null) return; // null is the default value
2491 
2492         createEditorIfNeeded();
2493         if (mEditor.mKeyListener != input) {
2494             mEditor.mKeyListener = input;
2495             if (input != null && !(mText instanceof Editable)) {
2496                 setText(mText);
2497             }
2498 
2499             setFilters((Editable) mText, mFilters);
2500         }
2501     }
2502 
2503     /**
2504      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2505      * which provides positioning, scrolling, and text selection functionality.
2506      * This will frequently be null for non-EditText TextViews.
2507      * @return the movement method being used for this TextView.
2508      * @see android.text.method.MovementMethod
2509      */
getMovementMethod()2510     public final MovementMethod getMovementMethod() {
2511         return mMovement;
2512     }
2513 
2514     /**
2515      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2516      * for this TextView. This can be null to disallow using the arrow keys to move the
2517      * cursor or scroll the view.
2518      * <p>
2519      * Be warned that if you want a TextView with a key listener or movement
2520      * method not to be focusable, or if you want a TextView without a
2521      * key listener or movement method to be focusable, you must call
2522      * {@link #setFocusable} again after calling this to get the focusability
2523      * back the way you want it.
2524      */
setMovementMethod(MovementMethod movement)2525     public final void setMovementMethod(MovementMethod movement) {
2526         if (mMovement != movement) {
2527             mMovement = movement;
2528 
2529             if (movement != null && mSpannable == null) {
2530                 setText(mText);
2531             }
2532 
2533             fixFocusableAndClickableSettings();
2534 
2535             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2536             // mMovement
2537             if (mEditor != null) mEditor.prepareCursorControllers();
2538         }
2539     }
2540 
fixFocusableAndClickableSettings()2541     private void fixFocusableAndClickableSettings() {
2542         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2543             setFocusable(FOCUSABLE);
2544             setClickable(true);
2545             setLongClickable(true);
2546         } else {
2547             setFocusable(FOCUSABLE_AUTO);
2548             setClickable(false);
2549             setLongClickable(false);
2550         }
2551     }
2552 
2553     /**
2554      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2555      * This is frequently null, except for single-line and password fields.
2556      * @return the current transformation method for this TextView.
2557      *
2558      * @attr ref android.R.styleable#TextView_password
2559      * @attr ref android.R.styleable#TextView_singleLine
2560      */
getTransformationMethod()2561     public final TransformationMethod getTransformationMethod() {
2562         return mTransformation;
2563     }
2564 
2565     /**
2566      * Sets the transformation that is applied to the text that this
2567      * TextView is displaying.
2568      *
2569      * @attr ref android.R.styleable#TextView_password
2570      * @attr ref android.R.styleable#TextView_singleLine
2571      */
setTransformationMethod(TransformationMethod method)2572     public final void setTransformationMethod(TransformationMethod method) {
2573         if (method == mTransformation) {
2574             // Avoid the setText() below if the transformation is
2575             // the same.
2576             return;
2577         }
2578         if (mTransformation != null) {
2579             if (mSpannable != null) {
2580                 mSpannable.removeSpan(mTransformation);
2581             }
2582         }
2583 
2584         mTransformation = method;
2585 
2586         if (method instanceof TransformationMethod2) {
2587             TransformationMethod2 method2 = (TransformationMethod2) method;
2588             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2589             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2590         } else {
2591             mAllowTransformationLengthChange = false;
2592         }
2593 
2594         setText(mText);
2595 
2596         if (hasPasswordTransformationMethod()) {
2597             notifyViewAccessibilityStateChangedIfNeeded(
2598                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2599         }
2600 
2601         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2602         // getTextDirectionHeuristic, needs reset
2603         mTextDir = getTextDirectionHeuristic();
2604     }
2605 
2606     /**
2607      * Returns the top padding of the view, plus space for the top
2608      * Drawable if any.
2609      */
getCompoundPaddingTop()2610     public int getCompoundPaddingTop() {
2611         final Drawables dr = mDrawables;
2612         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2613             return mPaddingTop;
2614         } else {
2615             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2616         }
2617     }
2618 
2619     /**
2620      * Returns the bottom padding of the view, plus space for the bottom
2621      * Drawable if any.
2622      */
getCompoundPaddingBottom()2623     public int getCompoundPaddingBottom() {
2624         final Drawables dr = mDrawables;
2625         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2626             return mPaddingBottom;
2627         } else {
2628             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2629         }
2630     }
2631 
2632     /**
2633      * Returns the left padding of the view, plus space for the left
2634      * Drawable if any.
2635      */
getCompoundPaddingLeft()2636     public int getCompoundPaddingLeft() {
2637         final Drawables dr = mDrawables;
2638         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2639             return mPaddingLeft;
2640         } else {
2641             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2642         }
2643     }
2644 
2645     /**
2646      * Returns the right padding of the view, plus space for the right
2647      * Drawable if any.
2648      */
getCompoundPaddingRight()2649     public int getCompoundPaddingRight() {
2650         final Drawables dr = mDrawables;
2651         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2652             return mPaddingRight;
2653         } else {
2654             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2655         }
2656     }
2657 
2658     /**
2659      * Returns the start padding of the view, plus space for the start
2660      * Drawable if any.
2661      */
getCompoundPaddingStart()2662     public int getCompoundPaddingStart() {
2663         resolveDrawables();
2664         switch(getLayoutDirection()) {
2665             default:
2666             case LAYOUT_DIRECTION_LTR:
2667                 return getCompoundPaddingLeft();
2668             case LAYOUT_DIRECTION_RTL:
2669                 return getCompoundPaddingRight();
2670         }
2671     }
2672 
2673     /**
2674      * Returns the end padding of the view, plus space for the end
2675      * Drawable if any.
2676      */
getCompoundPaddingEnd()2677     public int getCompoundPaddingEnd() {
2678         resolveDrawables();
2679         switch(getLayoutDirection()) {
2680             default:
2681             case LAYOUT_DIRECTION_LTR:
2682                 return getCompoundPaddingRight();
2683             case LAYOUT_DIRECTION_RTL:
2684                 return getCompoundPaddingLeft();
2685         }
2686     }
2687 
2688     /**
2689      * Returns the extended top padding of the view, including both the
2690      * top Drawable if any and any extra space to keep more than maxLines
2691      * of text from showing.  It is only valid to call this after measuring.
2692      */
getExtendedPaddingTop()2693     public int getExtendedPaddingTop() {
2694         if (mMaxMode != LINES) {
2695             return getCompoundPaddingTop();
2696         }
2697 
2698         if (mLayout == null) {
2699             assumeLayout();
2700         }
2701 
2702         if (mLayout.getLineCount() <= mMaximum) {
2703             return getCompoundPaddingTop();
2704         }
2705 
2706         int top = getCompoundPaddingTop();
2707         int bottom = getCompoundPaddingBottom();
2708         int viewht = getHeight() - top - bottom;
2709         int layoutht = mLayout.getLineTop(mMaximum);
2710 
2711         if (layoutht >= viewht) {
2712             return top;
2713         }
2714 
2715         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2716         if (gravity == Gravity.TOP) {
2717             return top;
2718         } else if (gravity == Gravity.BOTTOM) {
2719             return top + viewht - layoutht;
2720         } else { // (gravity == Gravity.CENTER_VERTICAL)
2721             return top + (viewht - layoutht) / 2;
2722         }
2723     }
2724 
2725     /**
2726      * Returns the extended bottom padding of the view, including both the
2727      * bottom Drawable if any and any extra space to keep more than maxLines
2728      * of text from showing.  It is only valid to call this after measuring.
2729      */
getExtendedPaddingBottom()2730     public int getExtendedPaddingBottom() {
2731         if (mMaxMode != LINES) {
2732             return getCompoundPaddingBottom();
2733         }
2734 
2735         if (mLayout == null) {
2736             assumeLayout();
2737         }
2738 
2739         if (mLayout.getLineCount() <= mMaximum) {
2740             return getCompoundPaddingBottom();
2741         }
2742 
2743         int top = getCompoundPaddingTop();
2744         int bottom = getCompoundPaddingBottom();
2745         int viewht = getHeight() - top - bottom;
2746         int layoutht = mLayout.getLineTop(mMaximum);
2747 
2748         if (layoutht >= viewht) {
2749             return bottom;
2750         }
2751 
2752         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2753         if (gravity == Gravity.TOP) {
2754             return bottom + viewht - layoutht;
2755         } else if (gravity == Gravity.BOTTOM) {
2756             return bottom;
2757         } else { // (gravity == Gravity.CENTER_VERTICAL)
2758             return bottom + (viewht - layoutht) / 2;
2759         }
2760     }
2761 
2762     /**
2763      * Returns the total left padding of the view, including the left
2764      * Drawable if any.
2765      */
getTotalPaddingLeft()2766     public int getTotalPaddingLeft() {
2767         return getCompoundPaddingLeft();
2768     }
2769 
2770     /**
2771      * Returns the total right padding of the view, including the right
2772      * Drawable if any.
2773      */
getTotalPaddingRight()2774     public int getTotalPaddingRight() {
2775         return getCompoundPaddingRight();
2776     }
2777 
2778     /**
2779      * Returns the total start padding of the view, including the start
2780      * Drawable if any.
2781      */
getTotalPaddingStart()2782     public int getTotalPaddingStart() {
2783         return getCompoundPaddingStart();
2784     }
2785 
2786     /**
2787      * Returns the total end padding of the view, including the end
2788      * Drawable if any.
2789      */
getTotalPaddingEnd()2790     public int getTotalPaddingEnd() {
2791         return getCompoundPaddingEnd();
2792     }
2793 
2794     /**
2795      * Returns the total top padding of the view, including the top
2796      * Drawable if any, the extra space to keep more than maxLines
2797      * from showing, and the vertical offset for gravity, if any.
2798      */
getTotalPaddingTop()2799     public int getTotalPaddingTop() {
2800         return getExtendedPaddingTop() + getVerticalOffset(true);
2801     }
2802 
2803     /**
2804      * Returns the total bottom padding of the view, including the bottom
2805      * Drawable if any, the extra space to keep more than maxLines
2806      * from showing, and the vertical offset for gravity, if any.
2807      */
getTotalPaddingBottom()2808     public int getTotalPaddingBottom() {
2809         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2810     }
2811 
2812     /**
2813      * Sets the Drawables (if any) to appear to the left of, above, to the
2814      * right of, and below the text. Use {@code null} if you do not want a
2815      * Drawable there. The Drawables must already have had
2816      * {@link Drawable#setBounds} called.
2817      * <p>
2818      * Calling this method will overwrite any Drawables previously set using
2819      * {@link #setCompoundDrawablesRelative} or related methods.
2820      *
2821      * @attr ref android.R.styleable#TextView_drawableLeft
2822      * @attr ref android.R.styleable#TextView_drawableTop
2823      * @attr ref android.R.styleable#TextView_drawableRight
2824      * @attr ref android.R.styleable#TextView_drawableBottom
2825      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2826     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2827             @Nullable Drawable right, @Nullable Drawable bottom) {
2828         Drawables dr = mDrawables;
2829 
2830         // We're switching to absolute, discard relative.
2831         if (dr != null) {
2832             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2833             dr.mDrawableStart = null;
2834             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2835             dr.mDrawableEnd = null;
2836             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2837             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2838         }
2839 
2840         final boolean drawables = left != null || top != null || right != null || bottom != null;
2841         if (!drawables) {
2842             // Clearing drawables...  can we free the data structure?
2843             if (dr != null) {
2844                 if (!dr.hasMetadata()) {
2845                     mDrawables = null;
2846                 } else {
2847                     // We need to retain the last set padding, so just clear
2848                     // out all of the fields in the existing structure.
2849                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2850                         if (dr.mShowing[i] != null) {
2851                             dr.mShowing[i].setCallback(null);
2852                         }
2853                         dr.mShowing[i] = null;
2854                     }
2855                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2856                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2857                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2858                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2859                 }
2860             }
2861         } else {
2862             if (dr == null) {
2863                 mDrawables = dr = new Drawables(getContext());
2864             }
2865 
2866             mDrawables.mOverride = false;
2867 
2868             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2869                 dr.mShowing[Drawables.LEFT].setCallback(null);
2870             }
2871             dr.mShowing[Drawables.LEFT] = left;
2872 
2873             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2874                 dr.mShowing[Drawables.TOP].setCallback(null);
2875             }
2876             dr.mShowing[Drawables.TOP] = top;
2877 
2878             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2879                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2880             }
2881             dr.mShowing[Drawables.RIGHT] = right;
2882 
2883             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2884                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2885             }
2886             dr.mShowing[Drawables.BOTTOM] = bottom;
2887 
2888             final Rect compoundRect = dr.mCompoundRect;
2889             int[] state;
2890 
2891             state = getDrawableState();
2892 
2893             if (left != null) {
2894                 left.setState(state);
2895                 left.copyBounds(compoundRect);
2896                 left.setCallback(this);
2897                 dr.mDrawableSizeLeft = compoundRect.width();
2898                 dr.mDrawableHeightLeft = compoundRect.height();
2899             } else {
2900                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2901             }
2902 
2903             if (right != null) {
2904                 right.setState(state);
2905                 right.copyBounds(compoundRect);
2906                 right.setCallback(this);
2907                 dr.mDrawableSizeRight = compoundRect.width();
2908                 dr.mDrawableHeightRight = compoundRect.height();
2909             } else {
2910                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2911             }
2912 
2913             if (top != null) {
2914                 top.setState(state);
2915                 top.copyBounds(compoundRect);
2916                 top.setCallback(this);
2917                 dr.mDrawableSizeTop = compoundRect.height();
2918                 dr.mDrawableWidthTop = compoundRect.width();
2919             } else {
2920                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2921             }
2922 
2923             if (bottom != null) {
2924                 bottom.setState(state);
2925                 bottom.copyBounds(compoundRect);
2926                 bottom.setCallback(this);
2927                 dr.mDrawableSizeBottom = compoundRect.height();
2928                 dr.mDrawableWidthBottom = compoundRect.width();
2929             } else {
2930                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2931             }
2932         }
2933 
2934         // Save initial left/right drawables
2935         if (dr != null) {
2936             dr.mDrawableLeftInitial = left;
2937             dr.mDrawableRightInitial = right;
2938         }
2939 
2940         resetResolvedDrawables();
2941         resolveDrawables();
2942         applyCompoundDrawableTint();
2943         invalidate();
2944         requestLayout();
2945     }
2946 
2947     /**
2948      * Sets the Drawables (if any) to appear to the left of, above, to the
2949      * right of, and below the text. Use 0 if you do not want a Drawable there.
2950      * The Drawables' bounds will be set to their intrinsic bounds.
2951      * <p>
2952      * Calling this method will overwrite any Drawables previously set using
2953      * {@link #setCompoundDrawablesRelative} or related methods.
2954      *
2955      * @param left Resource identifier of the left Drawable.
2956      * @param top Resource identifier of the top Drawable.
2957      * @param right Resource identifier of the right Drawable.
2958      * @param bottom Resource identifier of the bottom Drawable.
2959      *
2960      * @attr ref android.R.styleable#TextView_drawableLeft
2961      * @attr ref android.R.styleable#TextView_drawableTop
2962      * @attr ref android.R.styleable#TextView_drawableRight
2963      * @attr ref android.R.styleable#TextView_drawableBottom
2964      */
2965     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)2966     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2967             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2968         final Context context = getContext();
2969         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2970                 top != 0 ? context.getDrawable(top) : null,
2971                 right != 0 ? context.getDrawable(right) : null,
2972                 bottom != 0 ? context.getDrawable(bottom) : null);
2973     }
2974 
2975     /**
2976      * Sets the Drawables (if any) to appear to the left of, above, to the
2977      * right of, and below the text. Use {@code null} if you do not want a
2978      * Drawable there. The Drawables' bounds will be set to their intrinsic
2979      * bounds.
2980      * <p>
2981      * Calling this method will overwrite any Drawables previously set using
2982      * {@link #setCompoundDrawablesRelative} or related methods.
2983      *
2984      * @attr ref android.R.styleable#TextView_drawableLeft
2985      * @attr ref android.R.styleable#TextView_drawableTop
2986      * @attr ref android.R.styleable#TextView_drawableRight
2987      * @attr ref android.R.styleable#TextView_drawableBottom
2988      */
2989     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2990     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2991             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2992 
2993         if (left != null) {
2994             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2995         }
2996         if (right != null) {
2997             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2998         }
2999         if (top != null) {
3000             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3001         }
3002         if (bottom != null) {
3003             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3004         }
3005         setCompoundDrawables(left, top, right, bottom);
3006     }
3007 
3008     /**
3009      * Sets the Drawables (if any) to appear to the start of, above, to the end
3010      * of, and below the text. Use {@code null} if you do not want a Drawable
3011      * there. The Drawables must already have had {@link Drawable#setBounds}
3012      * called.
3013      * <p>
3014      * Calling this method will overwrite any Drawables previously set using
3015      * {@link #setCompoundDrawables} or related methods.
3016      *
3017      * @attr ref android.R.styleable#TextView_drawableStart
3018      * @attr ref android.R.styleable#TextView_drawableTop
3019      * @attr ref android.R.styleable#TextView_drawableEnd
3020      * @attr ref android.R.styleable#TextView_drawableBottom
3021      */
3022     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3023     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
3024             @Nullable Drawable end, @Nullable Drawable bottom) {
3025         Drawables dr = mDrawables;
3026 
3027         // We're switching to relative, discard absolute.
3028         if (dr != null) {
3029             if (dr.mShowing[Drawables.LEFT] != null) {
3030                 dr.mShowing[Drawables.LEFT].setCallback(null);
3031             }
3032             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3033             if (dr.mShowing[Drawables.RIGHT] != null) {
3034                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3035             }
3036             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3037             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3038             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3039         }
3040 
3041         final boolean drawables = start != null || top != null
3042                 || end != null || bottom != null;
3043 
3044         if (!drawables) {
3045             // Clearing drawables...  can we free the data structure?
3046             if (dr != null) {
3047                 if (!dr.hasMetadata()) {
3048                     mDrawables = null;
3049                 } else {
3050                     // We need to retain the last set padding, so just clear
3051                     // out all of the fields in the existing structure.
3052                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3053                     dr.mDrawableStart = null;
3054                     if (dr.mShowing[Drawables.TOP] != null) {
3055                         dr.mShowing[Drawables.TOP].setCallback(null);
3056                     }
3057                     dr.mShowing[Drawables.TOP] = null;
3058                     if (dr.mDrawableEnd != null) {
3059                         dr.mDrawableEnd.setCallback(null);
3060                     }
3061                     dr.mDrawableEnd = null;
3062                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3063                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3064                     }
3065                     dr.mShowing[Drawables.BOTTOM] = null;
3066                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3067                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3068                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3069                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3070                 }
3071             }
3072         } else {
3073             if (dr == null) {
3074                 mDrawables = dr = new Drawables(getContext());
3075             }
3076 
3077             mDrawables.mOverride = true;
3078 
3079             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3080                 dr.mDrawableStart.setCallback(null);
3081             }
3082             dr.mDrawableStart = start;
3083 
3084             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3085                 dr.mShowing[Drawables.TOP].setCallback(null);
3086             }
3087             dr.mShowing[Drawables.TOP] = top;
3088 
3089             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3090                 dr.mDrawableEnd.setCallback(null);
3091             }
3092             dr.mDrawableEnd = end;
3093 
3094             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3095                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3096             }
3097             dr.mShowing[Drawables.BOTTOM] = bottom;
3098 
3099             final Rect compoundRect = dr.mCompoundRect;
3100             int[] state;
3101 
3102             state = getDrawableState();
3103 
3104             if (start != null) {
3105                 start.setState(state);
3106                 start.copyBounds(compoundRect);
3107                 start.setCallback(this);
3108                 dr.mDrawableSizeStart = compoundRect.width();
3109                 dr.mDrawableHeightStart = compoundRect.height();
3110             } else {
3111                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3112             }
3113 
3114             if (end != null) {
3115                 end.setState(state);
3116                 end.copyBounds(compoundRect);
3117                 end.setCallback(this);
3118                 dr.mDrawableSizeEnd = compoundRect.width();
3119                 dr.mDrawableHeightEnd = compoundRect.height();
3120             } else {
3121                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3122             }
3123 
3124             if (top != null) {
3125                 top.setState(state);
3126                 top.copyBounds(compoundRect);
3127                 top.setCallback(this);
3128                 dr.mDrawableSizeTop = compoundRect.height();
3129                 dr.mDrawableWidthTop = compoundRect.width();
3130             } else {
3131                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3132             }
3133 
3134             if (bottom != null) {
3135                 bottom.setState(state);
3136                 bottom.copyBounds(compoundRect);
3137                 bottom.setCallback(this);
3138                 dr.mDrawableSizeBottom = compoundRect.height();
3139                 dr.mDrawableWidthBottom = compoundRect.width();
3140             } else {
3141                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3142             }
3143         }
3144 
3145         resetResolvedDrawables();
3146         resolveDrawables();
3147         invalidate();
3148         requestLayout();
3149     }
3150 
3151     /**
3152      * Sets the Drawables (if any) to appear to the start of, above, to the end
3153      * of, and below the text. Use 0 if you do not want a Drawable there. The
3154      * Drawables' bounds will be set to their intrinsic bounds.
3155      * <p>
3156      * Calling this method will overwrite any Drawables previously set using
3157      * {@link #setCompoundDrawables} or related methods.
3158      *
3159      * @param start Resource identifier of the start Drawable.
3160      * @param top Resource identifier of the top Drawable.
3161      * @param end Resource identifier of the end Drawable.
3162      * @param bottom Resource identifier of the bottom Drawable.
3163      *
3164      * @attr ref android.R.styleable#TextView_drawableStart
3165      * @attr ref android.R.styleable#TextView_drawableTop
3166      * @attr ref android.R.styleable#TextView_drawableEnd
3167      * @attr ref android.R.styleable#TextView_drawableBottom
3168      */
3169     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3170     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3171             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3172         final Context context = getContext();
3173         setCompoundDrawablesRelativeWithIntrinsicBounds(
3174                 start != 0 ? context.getDrawable(start) : null,
3175                 top != 0 ? context.getDrawable(top) : null,
3176                 end != 0 ? context.getDrawable(end) : null,
3177                 bottom != 0 ? context.getDrawable(bottom) : null);
3178     }
3179 
3180     /**
3181      * Sets the Drawables (if any) to appear to the start of, above, to the end
3182      * of, and below the text. Use {@code null} if you do not want a Drawable
3183      * there. The Drawables' bounds will be set to their intrinsic bounds.
3184      * <p>
3185      * Calling this method will overwrite any Drawables previously set using
3186      * {@link #setCompoundDrawables} or related methods.
3187      *
3188      * @attr ref android.R.styleable#TextView_drawableStart
3189      * @attr ref android.R.styleable#TextView_drawableTop
3190      * @attr ref android.R.styleable#TextView_drawableEnd
3191      * @attr ref android.R.styleable#TextView_drawableBottom
3192      */
3193     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3194     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3195             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3196 
3197         if (start != null) {
3198             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3199         }
3200         if (end != null) {
3201             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3202         }
3203         if (top != null) {
3204             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3205         }
3206         if (bottom != null) {
3207             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3208         }
3209         setCompoundDrawablesRelative(start, top, end, bottom);
3210     }
3211 
3212     /**
3213      * Returns drawables for the left, top, right, and bottom borders.
3214      *
3215      * @attr ref android.R.styleable#TextView_drawableLeft
3216      * @attr ref android.R.styleable#TextView_drawableTop
3217      * @attr ref android.R.styleable#TextView_drawableRight
3218      * @attr ref android.R.styleable#TextView_drawableBottom
3219      */
3220     @NonNull
getCompoundDrawables()3221     public Drawable[] getCompoundDrawables() {
3222         final Drawables dr = mDrawables;
3223         if (dr != null) {
3224             return dr.mShowing.clone();
3225         } else {
3226             return new Drawable[] { null, null, null, null };
3227         }
3228     }
3229 
3230     /**
3231      * Returns drawables for the start, top, end, and bottom borders.
3232      *
3233      * @attr ref android.R.styleable#TextView_drawableStart
3234      * @attr ref android.R.styleable#TextView_drawableTop
3235      * @attr ref android.R.styleable#TextView_drawableEnd
3236      * @attr ref android.R.styleable#TextView_drawableBottom
3237      */
3238     @NonNull
getCompoundDrawablesRelative()3239     public Drawable[] getCompoundDrawablesRelative() {
3240         final Drawables dr = mDrawables;
3241         if (dr != null) {
3242             return new Drawable[] {
3243                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3244                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3245             };
3246         } else {
3247             return new Drawable[] { null, null, null, null };
3248         }
3249     }
3250 
3251     /**
3252      * Sets the size of the padding between the compound drawables and
3253      * the text.
3254      *
3255      * @attr ref android.R.styleable#TextView_drawablePadding
3256      */
3257     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3258     public void setCompoundDrawablePadding(int pad) {
3259         Drawables dr = mDrawables;
3260         if (pad == 0) {
3261             if (dr != null) {
3262                 dr.mDrawablePadding = pad;
3263             }
3264         } else {
3265             if (dr == null) {
3266                 mDrawables = dr = new Drawables(getContext());
3267             }
3268             dr.mDrawablePadding = pad;
3269         }
3270 
3271         invalidate();
3272         requestLayout();
3273     }
3274 
3275     /**
3276      * Returns the padding between the compound drawables and the text.
3277      *
3278      * @attr ref android.R.styleable#TextView_drawablePadding
3279      */
3280     @InspectableProperty(name = "drawablePadding")
getCompoundDrawablePadding()3281     public int getCompoundDrawablePadding() {
3282         final Drawables dr = mDrawables;
3283         return dr != null ? dr.mDrawablePadding : 0;
3284     }
3285 
3286     /**
3287      * Applies a tint to the compound drawables. Does not modify the
3288      * current tint mode, which is {@link BlendMode#SRC_IN} by default.
3289      * <p>
3290      * Subsequent calls to
3291      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3292      * and related methods will automatically mutate the drawables and apply
3293      * the specified tint and tint mode using
3294      * {@link Drawable#setTintList(ColorStateList)}.
3295      *
3296      * @param tint the tint to apply, may be {@code null} to clear tint
3297      *
3298      * @attr ref android.R.styleable#TextView_drawableTint
3299      * @see #getCompoundDrawableTintList()
3300      * @see Drawable#setTintList(ColorStateList)
3301      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3302     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3303         if (mDrawables == null) {
3304             mDrawables = new Drawables(getContext());
3305         }
3306         mDrawables.mTintList = tint;
3307         mDrawables.mHasTint = true;
3308 
3309         applyCompoundDrawableTint();
3310     }
3311 
3312     /**
3313      * @return the tint applied to the compound drawables
3314      * @attr ref android.R.styleable#TextView_drawableTint
3315      * @see #setCompoundDrawableTintList(ColorStateList)
3316      */
3317     @InspectableProperty(name = "drawableTint")
getCompoundDrawableTintList()3318     public ColorStateList getCompoundDrawableTintList() {
3319         return mDrawables != null ? mDrawables.mTintList : null;
3320     }
3321 
3322     /**
3323      * Specifies the blending mode used to apply the tint specified by
3324      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3325      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3326      *
3327      * @param tintMode the blending mode used to apply the tint, may be
3328      *                 {@code null} to clear tint
3329      * @attr ref android.R.styleable#TextView_drawableTintMode
3330      * @see #setCompoundDrawableTintList(ColorStateList)
3331      * @see Drawable#setTintMode(PorterDuff.Mode)
3332      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3333     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3334         setCompoundDrawableTintBlendMode(tintMode != null
3335                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3336     }
3337 
3338     /**
3339      * Specifies the blending mode used to apply the tint specified by
3340      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3341      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3342      *
3343      * @param blendMode the blending mode used to apply the tint, may be
3344      *                 {@code null} to clear tint
3345      * @attr ref android.R.styleable#TextView_drawableTintMode
3346      * @see #setCompoundDrawableTintList(ColorStateList)
3347      * @see Drawable#setTintBlendMode(BlendMode)
3348      */
setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3349     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3350         if (mDrawables == null) {
3351             mDrawables = new Drawables(getContext());
3352         }
3353         mDrawables.mBlendMode = blendMode;
3354         mDrawables.mHasTintMode = true;
3355 
3356         applyCompoundDrawableTint();
3357     }
3358 
3359     /**
3360      * Returns the blending mode used to apply the tint to the compound
3361      * drawables, if specified.
3362      *
3363      * @return the blending mode used to apply the tint to the compound
3364      *         drawables
3365      * @attr ref android.R.styleable#TextView_drawableTintMode
3366      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3367      *
3368      */
3369     @InspectableProperty(name = "drawableTintMode")
getCompoundDrawableTintMode()3370     public PorterDuff.Mode getCompoundDrawableTintMode() {
3371         BlendMode mode = getCompoundDrawableTintBlendMode();
3372         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3373     }
3374 
3375     /**
3376      * Returns the blending mode used to apply the tint to the compound
3377      * drawables, if specified.
3378      *
3379      * @return the blending mode used to apply the tint to the compound
3380      *         drawables
3381      * @attr ref android.R.styleable#TextView_drawableTintMode
3382      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3383      */
3384     @InspectableProperty(name = "drawableBlendMode",
3385             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
getCompoundDrawableTintBlendMode()3386     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3387         return mDrawables != null ? mDrawables.mBlendMode : null;
3388     }
3389 
applyCompoundDrawableTint()3390     private void applyCompoundDrawableTint() {
3391         if (mDrawables == null) {
3392             return;
3393         }
3394 
3395         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3396             final ColorStateList tintList = mDrawables.mTintList;
3397             final BlendMode blendMode = mDrawables.mBlendMode;
3398             final boolean hasTint = mDrawables.mHasTint;
3399             final boolean hasTintMode = mDrawables.mHasTintMode;
3400             final int[] state = getDrawableState();
3401 
3402             for (Drawable dr : mDrawables.mShowing) {
3403                 if (dr == null) {
3404                     continue;
3405                 }
3406 
3407                 if (dr == mDrawables.mDrawableError) {
3408                     // From a developer's perspective, the error drawable isn't
3409                     // a compound drawable. Don't apply the generic compound
3410                     // drawable tint to it.
3411                     continue;
3412                 }
3413 
3414                 dr.mutate();
3415 
3416                 if (hasTint) {
3417                     dr.setTintList(tintList);
3418                 }
3419 
3420                 if (hasTintMode) {
3421                     dr.setTintBlendMode(blendMode);
3422                 }
3423 
3424                 // The drawable (or one of its children) may not have been
3425                 // stateful before applying the tint, so let's try again.
3426                 if (dr.isStateful()) {
3427                     dr.setState(state);
3428                 }
3429             }
3430         }
3431     }
3432 
3433     /**
3434      * @inheritDoc
3435      *
3436      * @see #setFirstBaselineToTopHeight(int)
3437      * @see #setLastBaselineToBottomHeight(int)
3438      */
3439     @Override
setPadding(int left, int top, int right, int bottom)3440     public void setPadding(int left, int top, int right, int bottom) {
3441         if (left != mPaddingLeft
3442                 || right != mPaddingRight
3443                 || top != mPaddingTop
3444                 ||  bottom != mPaddingBottom) {
3445             nullLayouts();
3446         }
3447 
3448         // the super call will requestLayout()
3449         super.setPadding(left, top, right, bottom);
3450         invalidate();
3451     }
3452 
3453     /**
3454      * @inheritDoc
3455      *
3456      * @see #setFirstBaselineToTopHeight(int)
3457      * @see #setLastBaselineToBottomHeight(int)
3458      */
3459     @Override
setPaddingRelative(int start, int top, int end, int bottom)3460     public void setPaddingRelative(int start, int top, int end, int bottom) {
3461         if (start != getPaddingStart()
3462                 || end != getPaddingEnd()
3463                 || top != mPaddingTop
3464                 || bottom != mPaddingBottom) {
3465             nullLayouts();
3466         }
3467 
3468         // the super call will requestLayout()
3469         super.setPaddingRelative(start, top, end, bottom);
3470         invalidate();
3471     }
3472 
3473     /**
3474      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3475      * the distance between the top of the TextView and first line's baseline.
3476      * <p>
3477      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3478      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3479      *
3480      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3481      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3482      * Moreover since this function sets the top padding, if the height of the TextView is less than
3483      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3484      * down and bottom will be clipped.
3485      *
3486      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3487      *      in pixels
3488      *
3489      * @see #getFirstBaselineToTopHeight()
3490      * @see #setLastBaselineToBottomHeight(int)
3491      * @see #setPadding(int, int, int, int)
3492      * @see #setPaddingRelative(int, int, int, int)
3493      *
3494      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3495      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3496     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3497         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3498 
3499         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3500         final int fontMetricsTop;
3501         if (getIncludeFontPadding()) {
3502             fontMetricsTop = fontMetrics.top;
3503         } else {
3504             fontMetricsTop = fontMetrics.ascent;
3505         }
3506 
3507         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3508         // in settings). At the moment, we don't.
3509 
3510         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3511             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3512             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3513         }
3514     }
3515 
3516     /**
3517      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3518      * the distance between the bottom of the TextView and the last line's baseline.
3519      * <p>
3520      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3521      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3522      *
3523      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3524      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3525      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3526      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3527      * clipped.
3528      *
3529      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3530      *      in pixels
3531      *
3532      * @see #getLastBaselineToBottomHeight()
3533      * @see #setFirstBaselineToTopHeight(int)
3534      * @see #setPadding(int, int, int, int)
3535      * @see #setPaddingRelative(int, int, int, int)
3536      *
3537      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3538      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3539     public void setLastBaselineToBottomHeight(
3540             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3541         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3542 
3543         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3544         final int fontMetricsBottom;
3545         if (getIncludeFontPadding()) {
3546             fontMetricsBottom = fontMetrics.bottom;
3547         } else {
3548             fontMetricsBottom = fontMetrics.descent;
3549         }
3550 
3551         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3552         // in settings). At the moment, we don't.
3553 
3554         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3555             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3556             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3557         }
3558     }
3559 
3560     /**
3561      * Returns the distance between the first text baseline and the top of this TextView.
3562      *
3563      * @see #setFirstBaselineToTopHeight(int)
3564      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3565      */
3566     @InspectableProperty
getFirstBaselineToTopHeight()3567     public int getFirstBaselineToTopHeight() {
3568         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3569     }
3570 
3571     /**
3572      * Returns the distance between the last text baseline and the bottom of this TextView.
3573      *
3574      * @see #setLastBaselineToBottomHeight(int)
3575      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3576      */
3577     @InspectableProperty
getLastBaselineToBottomHeight()3578     public int getLastBaselineToBottomHeight() {
3579         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3580     }
3581 
3582     /**
3583      * Gets the autolink mask of the text.
3584      *
3585      * See {@link Linkify#ALL} and peers for possible values.
3586      *
3587      * @attr ref android.R.styleable#TextView_autoLink
3588      */
3589     @InspectableProperty(name = "autoLink", flagMapping = {
3590             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3591             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3592             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3593             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3594     })
getAutoLinkMask()3595     public final int getAutoLinkMask() {
3596         return mAutoLinkMask;
3597     }
3598 
3599     /**
3600      * Sets the Drawable corresponding to the selection handle used for
3601      * positioning the cursor within text. The Drawable defaults to the value
3602      * of the textSelectHandle attribute.
3603      * Note that any change applied to the handle Drawable will not be visible
3604      * until the handle is hidden and then drawn again.
3605      *
3606      * @see #setTextSelectHandle(int)
3607      * @attr ref android.R.styleable#TextView_textSelectHandle
3608      */
3609     @android.view.RemotableViewMethod
setTextSelectHandle(@onNull Drawable textSelectHandle)3610     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3611         Preconditions.checkNotNull(textSelectHandle,
3612                 "The text select handle should not be null.");
3613         mTextSelectHandle = textSelectHandle;
3614         mTextSelectHandleRes = 0;
3615         if (mEditor != null) {
3616             mEditor.loadHandleDrawables(true /* overwrite */);
3617         }
3618     }
3619 
3620     /**
3621      * Sets the Drawable corresponding to the selection handle used for
3622      * positioning the cursor within text. The Drawable defaults to the value
3623      * of the textSelectHandle attribute.
3624      * Note that any change applied to the handle Drawable will not be visible
3625      * until the handle is hidden and then drawn again.
3626      *
3627      * @see #setTextSelectHandle(Drawable)
3628      * @attr ref android.R.styleable#TextView_textSelectHandle
3629      */
3630     @android.view.RemotableViewMethod
setTextSelectHandle(@rawableRes int textSelectHandle)3631     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3632         Preconditions.checkArgument(textSelectHandle != 0,
3633                 "The text select handle should be a valid drawable resource id.");
3634         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3635     }
3636 
3637     /**
3638      * Returns the Drawable corresponding to the selection handle used
3639      * for positioning the cursor within text.
3640      * Note that any change applied to the handle Drawable will not be visible
3641      * until the handle is hidden and then drawn again.
3642      *
3643      * @return the text select handle drawable
3644      *
3645      * @see #setTextSelectHandle(Drawable)
3646      * @see #setTextSelectHandle(int)
3647      * @attr ref android.R.styleable#TextView_textSelectHandle
3648      */
getTextSelectHandle()3649     @Nullable public Drawable getTextSelectHandle() {
3650         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3651             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3652         }
3653         return mTextSelectHandle;
3654     }
3655 
3656     /**
3657      * Sets the Drawable corresponding to the left handle used
3658      * for selecting text. The Drawable defaults to the value of the
3659      * textSelectHandleLeft attribute.
3660      * Note that any change applied to the handle Drawable will not be visible
3661      * until the handle is hidden and then drawn again.
3662      *
3663      * @see #setTextSelectHandleLeft(int)
3664      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3665      */
3666     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3667     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3668         Preconditions.checkNotNull(textSelectHandleLeft,
3669                 "The left text select handle should not be null.");
3670         mTextSelectHandleLeft = textSelectHandleLeft;
3671         mTextSelectHandleLeftRes = 0;
3672         if (mEditor != null) {
3673             mEditor.loadHandleDrawables(true /* overwrite */);
3674         }
3675     }
3676 
3677     /**
3678      * Sets the Drawable corresponding to the left handle used
3679      * for selecting text. The Drawable defaults to the value of the
3680      * textSelectHandleLeft attribute.
3681      * Note that any change applied to the handle Drawable will not be visible
3682      * until the handle is hidden and then drawn again.
3683      *
3684      * @see #setTextSelectHandleLeft(Drawable)
3685      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3686      */
3687     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3688     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
3689         Preconditions.checkArgument(textSelectHandleLeft != 0,
3690                 "The text select left handle should be a valid drawable resource id.");
3691         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
3692     }
3693 
3694     /**
3695      * Returns the Drawable corresponding to the left handle used
3696      * for selecting text.
3697      * Note that any change applied to the handle Drawable will not be visible
3698      * until the handle is hidden and then drawn again.
3699      *
3700      * @return the left text selection handle drawable
3701      *
3702      * @see #setTextSelectHandleLeft(Drawable)
3703      * @see #setTextSelectHandleLeft(int)
3704      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3705      */
getTextSelectHandleLeft()3706     @Nullable public Drawable getTextSelectHandleLeft() {
3707         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
3708             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
3709         }
3710         return mTextSelectHandleLeft;
3711     }
3712 
3713     /**
3714      * Sets the Drawable corresponding to the right handle used
3715      * for selecting text. The Drawable defaults to the value of the
3716      * textSelectHandleRight attribute.
3717      * Note that any change applied to the handle Drawable will not be visible
3718      * until the handle is hidden and then drawn again.
3719      *
3720      * @see #setTextSelectHandleRight(int)
3721      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3722      */
3723     @android.view.RemotableViewMethod
setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3724     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
3725         Preconditions.checkNotNull(textSelectHandleRight,
3726                 "The right text select handle should not be null.");
3727         mTextSelectHandleRight = textSelectHandleRight;
3728         mTextSelectHandleRightRes = 0;
3729         if (mEditor != null) {
3730             mEditor.loadHandleDrawables(true /* overwrite */);
3731         }
3732     }
3733 
3734     /**
3735      * Sets the Drawable corresponding to the right handle used
3736      * for selecting text. The Drawable defaults to the value of the
3737      * textSelectHandleRight attribute.
3738      * Note that any change applied to the handle Drawable will not be visible
3739      * until the handle is hidden and then drawn again.
3740      *
3741      * @see #setTextSelectHandleRight(Drawable)
3742      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3743      */
3744     @android.view.RemotableViewMethod
setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3745     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
3746         Preconditions.checkArgument(textSelectHandleRight != 0,
3747                 "The text select right handle should be a valid drawable resource id.");
3748         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
3749     }
3750 
3751     /**
3752      * Returns the Drawable corresponding to the right handle used
3753      * for selecting text.
3754      * Note that any change applied to the handle Drawable will not be visible
3755      * until the handle is hidden and then drawn again.
3756      *
3757      * @return the right text selection handle drawable
3758      *
3759      * @see #setTextSelectHandleRight(Drawable)
3760      * @see #setTextSelectHandleRight(int)
3761      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3762      */
getTextSelectHandleRight()3763     @Nullable public Drawable getTextSelectHandleRight() {
3764         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
3765             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
3766         }
3767         return mTextSelectHandleRight;
3768     }
3769 
3770     /**
3771      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3772      * value of the textCursorDrawable attribute.
3773      * Note that any change applied to the cursor Drawable will not be visible
3774      * until the cursor is hidden and then drawn again.
3775      *
3776      * @see #setTextCursorDrawable(int)
3777      * @attr ref android.R.styleable#TextView_textCursorDrawable
3778      */
setTextCursorDrawable(@ullable Drawable textCursorDrawable)3779     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
3780         mCursorDrawable = textCursorDrawable;
3781         mCursorDrawableRes = 0;
3782         if (mEditor != null) {
3783             mEditor.loadCursorDrawable();
3784         }
3785     }
3786 
3787     /**
3788      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3789      * value of the textCursorDrawable attribute.
3790      * Note that any change applied to the cursor Drawable will not be visible
3791      * until the cursor is hidden and then drawn again.
3792      *
3793      * @see #setTextCursorDrawable(Drawable)
3794      * @attr ref android.R.styleable#TextView_textCursorDrawable
3795      */
setTextCursorDrawable(@rawableRes int textCursorDrawable)3796     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
3797         setTextCursorDrawable(
3798                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
3799     }
3800 
3801     /**
3802      * Returns the Drawable corresponding to the text cursor.
3803      * Note that any change applied to the cursor Drawable will not be visible
3804      * until the cursor is hidden and then drawn again.
3805      *
3806      * @return the text cursor drawable
3807      *
3808      * @see #setTextCursorDrawable(Drawable)
3809      * @see #setTextCursorDrawable(int)
3810      * @attr ref android.R.styleable#TextView_textCursorDrawable
3811      */
getTextCursorDrawable()3812     @Nullable public Drawable getTextCursorDrawable() {
3813         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
3814             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
3815         }
3816         return mCursorDrawable;
3817     }
3818 
3819     /**
3820      * Sets the text appearance from the specified style resource.
3821      * <p>
3822      * Use a framework-defined {@code TextAppearance} style like
3823      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3824      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3825      * set of attributes that can be used in a custom style.
3826      *
3827      * @param resId the resource identifier of the style to apply
3828      * @attr ref android.R.styleable#TextView_textAppearance
3829      */
3830     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)3831     public void setTextAppearance(@StyleRes int resId) {
3832         setTextAppearance(mContext, resId);
3833     }
3834 
3835     /**
3836      * Sets the text color, size, style, hint color, and highlight color
3837      * from the specified TextAppearance resource.
3838      *
3839      * @deprecated Use {@link #setTextAppearance(int)} instead.
3840      */
3841     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)3842     public void setTextAppearance(Context context, @StyleRes int resId) {
3843         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3844         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
3845         readTextAppearance(context, ta, attributes, false /* styleArray */);
3846         ta.recycle();
3847         applyTextAppearance(attributes);
3848     }
3849 
3850     /**
3851      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
3852      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
3853      */
3854     private static class TextAppearanceAttributes {
3855         int mTextColorHighlight = 0;
3856         ColorStateList mTextColor = null;
3857         ColorStateList mTextColorHint = null;
3858         ColorStateList mTextColorLink = null;
3859         int mTextSize = -1;
3860         int mTextSizeUnit = -1;
3861         LocaleList mTextLocales = null;
3862         String mFontFamily = null;
3863         Typeface mFontTypeface = null;
3864         boolean mFontFamilyExplicit = false;
3865         int mTypefaceIndex = -1;
3866         int mTextStyle = 0;
3867         int mFontWeight = -1;
3868         boolean mAllCaps = false;
3869         int mShadowColor = 0;
3870         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
3871         boolean mHasElegant = false;
3872         boolean mElegant = false;
3873         boolean mHasFallbackLineSpacing = false;
3874         boolean mFallbackLineSpacing = false;
3875         boolean mHasLetterSpacing = false;
3876         float mLetterSpacing = 0;
3877         String mFontFeatureSettings = null;
3878         String mFontVariationSettings = null;
3879 
3880         @Override
toString()3881         public String toString() {
3882             return "TextAppearanceAttributes {\n"
3883                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
3884                     + "    mTextColor:" + mTextColor + "\n"
3885                     + "    mTextColorHint:" + mTextColorHint + "\n"
3886                     + "    mTextColorLink:" + mTextColorLink + "\n"
3887                     + "    mTextSize:" + mTextSize + "\n"
3888                     + "    mTextSizeUnit:" + mTextSizeUnit + "\n"
3889                     + "    mTextLocales:" + mTextLocales + "\n"
3890                     + "    mFontFamily:" + mFontFamily + "\n"
3891                     + "    mFontTypeface:" + mFontTypeface + "\n"
3892                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
3893                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
3894                     + "    mTextStyle:" + mTextStyle + "\n"
3895                     + "    mFontWeight:" + mFontWeight + "\n"
3896                     + "    mAllCaps:" + mAllCaps + "\n"
3897                     + "    mShadowColor:" + mShadowColor + "\n"
3898                     + "    mShadowDx:" + mShadowDx + "\n"
3899                     + "    mShadowDy:" + mShadowDy + "\n"
3900                     + "    mShadowRadius:" + mShadowRadius + "\n"
3901                     + "    mHasElegant:" + mHasElegant + "\n"
3902                     + "    mElegant:" + mElegant + "\n"
3903                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
3904                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
3905                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
3906                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
3907                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
3908                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
3909                     + "}";
3910         }
3911     }
3912 
3913     // Maps styleable attributes that exist both in TextView style and TextAppearance.
3914     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
3915     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3916         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
3917                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3918         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
3919                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3920         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
3921                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3922         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
3923                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3924         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
3925                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3926         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
3927                 com.android.internal.R.styleable.TextAppearance_textLocale);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3928         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
3929                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3930         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
3931                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3932         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
3933                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)3934         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
3935                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)3936         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
3937                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)3938         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
3939                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)3940         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
3941                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)3942         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
3943                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)3944         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
3945                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)3946         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
3947                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)3948         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
3949                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)3950         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
3951                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)3952         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
3953                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)3954         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
3955                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
3956     }
3957 
3958     /**
3959      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
3960      * set. If the TypedArray contains a value that was already set in the given attributes, that
3961      * will be overridden.
3962      *
3963      * @param context The Context to be used
3964      * @param appearance The TypedArray to read properties from
3965      * @param attributes the TextAppearanceAttributes to fill in
3966      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
3967      *                   what attribute indexes will be used to read the properties.
3968      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)3969     private void readTextAppearance(Context context, TypedArray appearance,
3970             TextAppearanceAttributes attributes, boolean styleArray) {
3971         final int n = appearance.getIndexCount();
3972         for (int i = 0; i < n; i++) {
3973             final int attr = appearance.getIndex(i);
3974             int index = attr;
3975             // Translate style array index ids to TextAppearance ids.
3976             if (styleArray) {
3977                 index = sAppearanceValues.get(attr, -1);
3978                 if (index == -1) {
3979                     // This value is not part of a Text Appearance and should be ignored.
3980                     continue;
3981                 }
3982             }
3983             switch (index) {
3984                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
3985                     attributes.mTextColorHighlight =
3986                             appearance.getColor(attr, attributes.mTextColorHighlight);
3987                     break;
3988                 case com.android.internal.R.styleable.TextAppearance_textColor:
3989                     attributes.mTextColor = appearance.getColorStateList(attr);
3990                     break;
3991                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
3992                     attributes.mTextColorHint = appearance.getColorStateList(attr);
3993                     break;
3994                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
3995                     attributes.mTextColorLink = appearance.getColorStateList(attr);
3996                     break;
3997                 case com.android.internal.R.styleable.TextAppearance_textSize:
3998                     attributes.mTextSize =
3999                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
4000                     attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit();
4001                     break;
4002                 case com.android.internal.R.styleable.TextAppearance_textLocale:
4003                     final String localeString = appearance.getString(attr);
4004                     if (localeString != null) {
4005                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
4006                         if (!localeList.isEmpty()) {
4007                             attributes.mTextLocales = localeList;
4008                         }
4009                     }
4010                     break;
4011                 case com.android.internal.R.styleable.TextAppearance_typeface:
4012                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
4013                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4014                         attributes.mFontFamily = null;
4015                     }
4016                     break;
4017                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
4018                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
4019                         try {
4020                             attributes.mFontTypeface = appearance.getFont(attr);
4021                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
4022                             // Expected if it is not a font resource.
4023                         }
4024                     }
4025                     if (attributes.mFontTypeface == null) {
4026                         attributes.mFontFamily = appearance.getString(attr);
4027                     }
4028                     attributes.mFontFamilyExplicit = true;
4029                     break;
4030                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4031                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4032                     break;
4033                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4034                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4035                     break;
4036                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4037                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4038                     break;
4039                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4040                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4041                     break;
4042                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4043                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4044                     break;
4045                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4046                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4047                     break;
4048                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4049                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4050                     break;
4051                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4052                     attributes.mHasElegant = true;
4053                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4054                     break;
4055                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4056                     attributes.mHasFallbackLineSpacing = true;
4057                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4058                             attributes.mFallbackLineSpacing);
4059                     break;
4060                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4061                     attributes.mHasLetterSpacing = true;
4062                     attributes.mLetterSpacing =
4063                             appearance.getFloat(attr, attributes.mLetterSpacing);
4064                     break;
4065                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4066                     attributes.mFontFeatureSettings = appearance.getString(attr);
4067                     break;
4068                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4069                     attributes.mFontVariationSettings = appearance.getString(attr);
4070                     break;
4071                 default:
4072             }
4073         }
4074     }
4075 
applyTextAppearance(TextAppearanceAttributes attributes)4076     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4077         if (attributes.mTextColor != null) {
4078             setTextColor(attributes.mTextColor);
4079         }
4080 
4081         if (attributes.mTextColorHint != null) {
4082             setHintTextColor(attributes.mTextColorHint);
4083         }
4084 
4085         if (attributes.mTextColorLink != null) {
4086             setLinkTextColor(attributes.mTextColorLink);
4087         }
4088 
4089         if (attributes.mTextColorHighlight != 0) {
4090             setHighlightColor(attributes.mTextColorHighlight);
4091         }
4092 
4093         if (attributes.mTextSize != -1) {
4094             mTextSizeUnit = attributes.mTextSizeUnit;
4095             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4096         }
4097 
4098         if (attributes.mTextLocales != null) {
4099             setTextLocales(attributes.mTextLocales);
4100         }
4101 
4102         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4103             attributes.mFontFamily = null;
4104         }
4105         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4106                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4107 
4108         if (attributes.mShadowColor != 0) {
4109             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4110                     attributes.mShadowColor);
4111         }
4112 
4113         if (attributes.mAllCaps) {
4114             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4115         }
4116 
4117         if (attributes.mHasElegant) {
4118             setElegantTextHeight(attributes.mElegant);
4119         }
4120 
4121         if (attributes.mHasFallbackLineSpacing) {
4122             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4123         }
4124 
4125         if (attributes.mHasLetterSpacing) {
4126             setLetterSpacing(attributes.mLetterSpacing);
4127         }
4128 
4129         if (attributes.mFontFeatureSettings != null) {
4130             setFontFeatureSettings(attributes.mFontFeatureSettings);
4131         }
4132 
4133         if (attributes.mFontVariationSettings != null) {
4134             setFontVariationSettings(attributes.mFontVariationSettings);
4135         }
4136     }
4137 
4138     /**
4139      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4140      * the first member of {@link #getTextLocales()}.
4141      * @return the default primary {@link Locale} of the text in this TextView.
4142      */
4143     @NonNull
getTextLocale()4144     public Locale getTextLocale() {
4145         return mTextPaint.getTextLocale();
4146     }
4147 
4148     /**
4149      * Get the default {@link LocaleList} of the text in this TextView.
4150      * @return the default {@link LocaleList} of the text in this TextView.
4151      */
4152     @NonNull @Size(min = 1)
getTextLocales()4153     public LocaleList getTextLocales() {
4154         return mTextPaint.getTextLocales();
4155     }
4156 
changeListenerLocaleTo(@ullable Locale locale)4157     private void changeListenerLocaleTo(@Nullable Locale locale) {
4158         if (mListenerChanged) {
4159             // If a listener has been explicitly set, don't change it. We may break something.
4160             return;
4161         }
4162         // The following null check is not absolutely necessary since all calling points of
4163         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4164         // here in case others would want to call this method in the future.
4165         if (mEditor != null) {
4166             KeyListener listener = mEditor.mKeyListener;
4167             if (listener instanceof DigitsKeyListener) {
4168                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4169             } else if (listener instanceof DateKeyListener) {
4170                 listener = DateKeyListener.getInstance(locale);
4171             } else if (listener instanceof TimeKeyListener) {
4172                 listener = TimeKeyListener.getInstance(locale);
4173             } else if (listener instanceof DateTimeKeyListener) {
4174                 listener = DateTimeKeyListener.getInstance(locale);
4175             } else {
4176                 return;
4177             }
4178             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4179             setKeyListenerOnly(listener);
4180             setInputTypeFromEditor();
4181             if (wasPasswordType) {
4182                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4183                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4184                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4185                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4186                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4187                 }
4188             }
4189         }
4190     }
4191 
4192     /**
4193      * Set the default {@link Locale} of the text in this TextView to a one-member
4194      * {@link LocaleList} containing just the given Locale.
4195      *
4196      * @param locale the {@link Locale} for drawing text, must not be null.
4197      *
4198      * @see #setTextLocales
4199      */
setTextLocale(@onNull Locale locale)4200     public void setTextLocale(@NonNull Locale locale) {
4201         mLocalesChanged = true;
4202         mTextPaint.setTextLocale(locale);
4203         if (mLayout != null) {
4204             nullLayouts();
4205             requestLayout();
4206             invalidate();
4207         }
4208     }
4209 
4210     /**
4211      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4212      *
4213      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4214      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4215      * other aspects of text display, including line breaking.
4216      *
4217      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4218      *
4219      * @see Paint#setTextLocales
4220      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)4221     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4222         mLocalesChanged = true;
4223         mTextPaint.setTextLocales(locales);
4224         if (mLayout != null) {
4225             nullLayouts();
4226             requestLayout();
4227             invalidate();
4228         }
4229     }
4230 
4231     @Override
onConfigurationChanged(Configuration newConfig)4232     protected void onConfigurationChanged(Configuration newConfig) {
4233         super.onConfigurationChanged(newConfig);
4234         if (!mLocalesChanged) {
4235             mTextPaint.setTextLocales(LocaleList.getDefault());
4236             if (mLayout != null) {
4237                 nullLayouts();
4238                 requestLayout();
4239                 invalidate();
4240             }
4241         }
4242     }
4243 
4244     /**
4245      * @return the size (in pixels) of the default text size in this TextView.
4246      */
4247     @InspectableProperty
4248     @ViewDebug.ExportedProperty(category = "text")
getTextSize()4249     public float getTextSize() {
4250         return mTextPaint.getTextSize();
4251     }
4252 
4253     /**
4254      * @return the size (in scaled pixels) of the default text size in this TextView.
4255      * @hide
4256      */
4257     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()4258     public float getScaledTextSize() {
4259         return mTextPaint.getTextSize() / mTextPaint.density;
4260     }
4261 
4262     /** @hide */
4263     @ViewDebug.ExportedProperty(category = "text", mapping = {
4264             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4265             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4266             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4267             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4268     })
getTypefaceStyle()4269     public int getTypefaceStyle() {
4270         Typeface typeface = mTextPaint.getTypeface();
4271         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4272     }
4273 
4274     /**
4275      * Set the default text size to the given value, interpreted as "scaled
4276      * pixel" units.  This size is adjusted based on the current density and
4277      * user font size preference.
4278      *
4279      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4280      *
4281      * @param size The scaled pixel size.
4282      *
4283      * @attr ref android.R.styleable#TextView_textSize
4284      */
4285     @android.view.RemotableViewMethod
setTextSize(float size)4286     public void setTextSize(float size) {
4287         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4288     }
4289 
4290     /**
4291      * Set the default text size to a given unit and value. See {@link
4292      * TypedValue} for the possible dimension units.
4293      *
4294      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4295      *
4296      * @param unit The desired dimension unit.
4297      * @param size The desired size in the given units.
4298      *
4299      * @attr ref android.R.styleable#TextView_textSize
4300      */
setTextSize(int unit, float size)4301     public void setTextSize(int unit, float size) {
4302         if (!isAutoSizeEnabled()) {
4303             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4304         }
4305     }
4306 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4307     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4308         Context c = getContext();
4309         Resources r;
4310 
4311         if (c == null) {
4312             r = Resources.getSystem();
4313         } else {
4314             r = c.getResources();
4315         }
4316 
4317         mTextSizeUnit = unit;
4318         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
4319                 shouldRequestLayout);
4320     }
4321 
4322     @UnsupportedAppUsage
setRawTextSize(float size, boolean shouldRequestLayout)4323     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4324         if (size != mTextPaint.getTextSize()) {
4325             mTextPaint.setTextSize(size);
4326 
4327             if (shouldRequestLayout && mLayout != null) {
4328                 // Do not auto-size right after setting the text size.
4329                 mNeedsAutoSizeText = false;
4330                 nullLayouts();
4331                 requestLayout();
4332                 invalidate();
4333             }
4334         }
4335     }
4336 
4337     /**
4338      * Gets the text size unit defined by the developer. It may be specified in resources or be
4339      * passed as the unit argument of {@link #setTextSize(int, float)} at runtime.
4340      *
4341      * @return the dimension type of the text size unit originally defined.
4342      * @see TypedValue#TYPE_DIMENSION
4343      */
getTextSizeUnit()4344     public int getTextSizeUnit() {
4345         return mTextSizeUnit;
4346     }
4347 
4348     /**
4349      * Gets the extent by which text should be stretched horizontally.
4350      * This will usually be 1.0.
4351      * @return The horizontal scale factor.
4352      */
4353     @InspectableProperty
getTextScaleX()4354     public float getTextScaleX() {
4355         return mTextPaint.getTextScaleX();
4356     }
4357 
4358     /**
4359      * Sets the horizontal scale factor for text. The default value
4360      * is 1.0. Values greater than 1.0 stretch the text wider.
4361      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4362      * @param size The horizontal scale factor.
4363      * @attr ref android.R.styleable#TextView_textScaleX
4364      */
4365     @android.view.RemotableViewMethod
setTextScaleX(float size)4366     public void setTextScaleX(float size) {
4367         if (size != mTextPaint.getTextScaleX()) {
4368             mUserSetTextScaleX = true;
4369             mTextPaint.setTextScaleX(size);
4370 
4371             if (mLayout != null) {
4372                 nullLayouts();
4373                 requestLayout();
4374                 invalidate();
4375             }
4376         }
4377     }
4378 
4379     /**
4380      * Sets the typeface and style in which the text should be displayed.
4381      * Note that not all Typeface families actually have bold and italic
4382      * variants, so you may need to use
4383      * {@link #setTypeface(Typeface, int)} to get the appearance
4384      * that you actually want.
4385      *
4386      * @see #getTypeface()
4387      *
4388      * @attr ref android.R.styleable#TextView_fontFamily
4389      * @attr ref android.R.styleable#TextView_typeface
4390      * @attr ref android.R.styleable#TextView_textStyle
4391      */
setTypeface(@ullable Typeface tf)4392     public void setTypeface(@Nullable Typeface tf) {
4393         if (mTextPaint.getTypeface() != tf) {
4394             mTextPaint.setTypeface(tf);
4395 
4396             if (mLayout != null) {
4397                 nullLayouts();
4398                 requestLayout();
4399                 invalidate();
4400             }
4401         }
4402     }
4403 
4404     /**
4405      * Gets the current {@link Typeface} that is used to style the text.
4406      * @return The current Typeface.
4407      *
4408      * @see #setTypeface(Typeface)
4409      *
4410      * @attr ref android.R.styleable#TextView_fontFamily
4411      * @attr ref android.R.styleable#TextView_typeface
4412      * @attr ref android.R.styleable#TextView_textStyle
4413      */
4414     @InspectableProperty
getTypeface()4415     public Typeface getTypeface() {
4416         return mTextPaint.getTypeface();
4417     }
4418 
4419     /**
4420      * Set the TextView's elegant height metrics flag. This setting selects font
4421      * variants that have not been compacted to fit Latin-based vertical
4422      * metrics, and also increases top and bottom bounds to provide more space.
4423      *
4424      * @param elegant set the paint's elegant metrics flag.
4425      *
4426      * @see #isElegantTextHeight()
4427      * @see Paint#isElegantTextHeight()
4428      *
4429      * @attr ref android.R.styleable#TextView_elegantTextHeight
4430      */
setElegantTextHeight(boolean elegant)4431     public void setElegantTextHeight(boolean elegant) {
4432         if (elegant != mTextPaint.isElegantTextHeight()) {
4433             mTextPaint.setElegantTextHeight(elegant);
4434             if (mLayout != null) {
4435                 nullLayouts();
4436                 requestLayout();
4437                 invalidate();
4438             }
4439         }
4440     }
4441 
4442     /**
4443      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4444      * displaying the text (which is needed to avoid text from consecutive lines running into
4445      * each other). If set, fallback fonts that end up getting used can increase the ascent
4446      * and descent of the lines that they are used on.
4447      * <p/>
4448      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4449      * is typically much taller or deeper than Latin text.
4450      *
4451      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4452      *
4453      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4454      *
4455      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4456      */
setFallbackLineSpacing(boolean enabled)4457     public void setFallbackLineSpacing(boolean enabled) {
4458         if (mUseFallbackLineSpacing != enabled) {
4459             mUseFallbackLineSpacing = enabled;
4460             if (mLayout != null) {
4461                 nullLayouts();
4462                 requestLayout();
4463                 invalidate();
4464             }
4465         }
4466     }
4467 
4468     /**
4469      * @return whether fallback line spacing is enabled, {@code true} by default
4470      *
4471      * @see #setFallbackLineSpacing(boolean)
4472      *
4473      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4474      */
4475     @InspectableProperty
isFallbackLineSpacing()4476     public boolean isFallbackLineSpacing() {
4477         return mUseFallbackLineSpacing;
4478     }
4479 
4480     /**
4481      * Get the value of the TextView's elegant height metrics flag. This setting selects font
4482      * variants that have not been compacted to fit Latin-based vertical
4483      * metrics, and also increases top and bottom bounds to provide more space.
4484      * @return {@code true} if the elegant height metrics flag is set.
4485      *
4486      * @see #setElegantTextHeight(boolean)
4487      * @see Paint#setElegantTextHeight(boolean)
4488      */
4489     @InspectableProperty
isElegantTextHeight()4490     public boolean isElegantTextHeight() {
4491         return mTextPaint.isElegantTextHeight();
4492     }
4493 
4494     /**
4495      * Gets the text letter-space value, which determines the spacing between characters.
4496      * The value returned is in ems. Normally, this value is 0.0.
4497      * @return The text letter-space value in ems.
4498      *
4499      * @see #setLetterSpacing(float)
4500      * @see Paint#setLetterSpacing
4501      */
4502     @InspectableProperty
getLetterSpacing()4503     public float getLetterSpacing() {
4504         return mTextPaint.getLetterSpacing();
4505     }
4506 
4507     /**
4508      * Sets text letter-spacing in em units.  Typical values
4509      * for slight expansion will be around 0.05.  Negative values tighten text.
4510      *
4511      * @see #getLetterSpacing()
4512      * @see Paint#getLetterSpacing
4513      *
4514      * @param letterSpacing A text letter-space value in ems.
4515      * @attr ref android.R.styleable#TextView_letterSpacing
4516      */
4517     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)4518     public void setLetterSpacing(float letterSpacing) {
4519         if (letterSpacing != mTextPaint.getLetterSpacing()) {
4520             mTextPaint.setLetterSpacing(letterSpacing);
4521 
4522             if (mLayout != null) {
4523                 nullLayouts();
4524                 requestLayout();
4525                 invalidate();
4526             }
4527         }
4528     }
4529 
4530     /**
4531      * Returns the font feature settings. The format is the same as the CSS
4532      * font-feature-settings attribute:
4533      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4534      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4535      *
4536      * @return the currently set font feature settings.  Default is null.
4537      *
4538      * @see #setFontFeatureSettings(String)
4539      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
4540      */
4541     @InspectableProperty
4542     @Nullable
getFontFeatureSettings()4543     public String getFontFeatureSettings() {
4544         return mTextPaint.getFontFeatureSettings();
4545     }
4546 
4547     /**
4548      * Returns the font variation settings.
4549      *
4550      * @return the currently set font variation settings.  Returns null if no variation is
4551      * specified.
4552      *
4553      * @see #setFontVariationSettings(String)
4554      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
4555      */
4556     @Nullable
getFontVariationSettings()4557     public String getFontVariationSettings() {
4558         return mTextPaint.getFontVariationSettings();
4559     }
4560 
4561     /**
4562      * Sets the break strategy for breaking paragraphs into lines. The default value for
4563      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
4564      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
4565      * text "dancing" when being edited.
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      *
4573      * @attr ref android.R.styleable#TextView_breakStrategy
4574      * @see #getBreakStrategy()
4575      * @see #setHyphenationFrequency(int)
4576      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4577     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
4578         mBreakStrategy = breakStrategy;
4579         if (mLayout != null) {
4580             nullLayouts();
4581             requestLayout();
4582             invalidate();
4583         }
4584     }
4585 
4586     /**
4587      * Gets the current strategy for breaking paragraphs into lines.
4588      * @return the current strategy for breaking paragraphs into lines.
4589      *
4590      * @attr ref android.R.styleable#TextView_breakStrategy
4591      * @see #setBreakStrategy(int)
4592      */
4593     @InspectableProperty(enumMapping = {
4594             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
4595             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
4596             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
4597     })
4598     @Layout.BreakStrategy
getBreakStrategy()4599     public int getBreakStrategy() {
4600         return mBreakStrategy;
4601     }
4602 
4603     /**
4604      * Sets the frequency of automatic hyphenation to use when determining word breaks.
4605      * The default value for both TextView and {@link EditText} is
4606      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
4607      * is set from the theme.
4608      * <p/>
4609      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4610      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4611      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4612      * improves the structure of text layout however has performance impact and requires more time
4613      * to do the text layout.
4614      * <p/>
4615      * Note: Before Android Q, in the theme hyphenation frequency is set to
4616      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
4617      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
4618      *
4619      * @param hyphenationFrequency the hyphenation frequency to use, one of
4620      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
4621      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
4622      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
4623      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4624      * @see #getHyphenationFrequency()
4625      * @see #getBreakStrategy()
4626      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4627     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
4628         mHyphenationFrequency = hyphenationFrequency;
4629         if (mLayout != null) {
4630             nullLayouts();
4631             requestLayout();
4632             invalidate();
4633         }
4634     }
4635 
4636     /**
4637      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
4638      * @return the current frequency of automatic hyphenation to be used when determining word
4639      * breaks.
4640      *
4641      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4642      * @see #setHyphenationFrequency(int)
4643      */
4644     @InspectableProperty(enumMapping = {
4645             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
4646             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
4647             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
4648     })
4649     @Layout.HyphenationFrequency
getHyphenationFrequency()4650     public int getHyphenationFrequency() {
4651         return mHyphenationFrequency;
4652     }
4653 
4654     /**
4655      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
4656      *
4657      * @return a current {@link PrecomputedText.Params}
4658      * @see PrecomputedText
4659      */
getTextMetricsParams()4660     public @NonNull PrecomputedText.Params getTextMetricsParams() {
4661         return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
4662                 mBreakStrategy, mHyphenationFrequency);
4663     }
4664 
4665     /**
4666      * Apply the text layout parameter.
4667      *
4668      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
4669      * @see PrecomputedText
4670      */
setTextMetricsParams(@onNull PrecomputedText.Params params)4671     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
4672         mTextPaint.set(params.getTextPaint());
4673         mUserSetTextScaleX = true;
4674         mTextDir = params.getTextDirection();
4675         mBreakStrategy = params.getBreakStrategy();
4676         mHyphenationFrequency = params.getHyphenationFrequency();
4677         if (mLayout != null) {
4678             nullLayouts();
4679             requestLayout();
4680             invalidate();
4681         }
4682     }
4683 
4684     /**
4685      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
4686      * last line is too short for justification, the last line will be displayed with the
4687      * alignment set by {@link android.view.View#setTextAlignment}.
4688      *
4689      * @see #getJustificationMode()
4690      */
4691     @Layout.JustificationMode
setJustificationMode(@ayout.JustificationMode int justificationMode)4692     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
4693         mJustificationMode = justificationMode;
4694         if (mLayout != null) {
4695             nullLayouts();
4696             requestLayout();
4697             invalidate();
4698         }
4699     }
4700 
4701     /**
4702      * @return true if currently paragraph justification mode.
4703      *
4704      * @see #setJustificationMode(int)
4705      */
4706     @InspectableProperty(enumMapping = {
4707             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
4708             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
4709     })
getJustificationMode()4710     public @Layout.JustificationMode int getJustificationMode() {
4711         return mJustificationMode;
4712     }
4713 
4714     /**
4715      * Sets font feature settings. The format is the same as the CSS
4716      * font-feature-settings attribute:
4717      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4718      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4719      *
4720      * @param fontFeatureSettings font feature settings represented as CSS compatible string
4721      *
4722      * @see #getFontFeatureSettings()
4723      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
4724      *
4725      * @attr ref android.R.styleable#TextView_fontFeatureSettings
4726      */
4727     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)4728     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
4729         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
4730             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
4731 
4732             if (mLayout != null) {
4733                 nullLayouts();
4734                 requestLayout();
4735                 invalidate();
4736             }
4737         }
4738     }
4739 
4740 
4741     /**
4742      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
4743      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
4744      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
4745      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
4746      * are invalid. If a specified axis name is not defined in the font, the settings will be
4747      * ignored.
4748      *
4749      * <p>
4750      * Examples,
4751      * <ul>
4752      * <li>Set font width to 150.
4753      * <pre>
4754      * <code>
4755      *   TextView textView = (TextView) findViewById(R.id.textView);
4756      *   textView.setFontVariationSettings("'wdth' 150");
4757      * </code>
4758      * </pre>
4759      * </li>
4760      *
4761      * <li>Set the font slant to 20 degrees and ask for italic style.
4762      * <pre>
4763      * <code>
4764      *   TextView textView = (TextView) findViewById(R.id.textView);
4765      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
4766      * </code>
4767      * </pre>
4768      * </p>
4769      * </li>
4770      * </ul>
4771      *
4772      * @param fontVariationSettings font variation settings. You can pass null or empty string as
4773      *                              no variation settings.
4774      * @return true if the given settings is effective to at least one font file underlying this
4775      *         TextView. This function also returns true for empty settings string. Otherwise
4776      *         returns false.
4777      *
4778      * @throws IllegalArgumentException If given string is not a valid font variation settings
4779      *                                  format.
4780      *
4781      * @see #getFontVariationSettings()
4782      * @see FontVariationAxis
4783      *
4784      * @attr ref android.R.styleable#TextView_fontVariationSettings
4785      */
setFontVariationSettings(@ullable String fontVariationSettings)4786     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
4787         final String existingSettings = mTextPaint.getFontVariationSettings();
4788         if (fontVariationSettings == existingSettings
4789                 || (fontVariationSettings != null
4790                         && fontVariationSettings.equals(existingSettings))) {
4791             return true;
4792         }
4793         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
4794 
4795         if (effective && mLayout != null) {
4796             nullLayouts();
4797             requestLayout();
4798             invalidate();
4799         }
4800         return effective;
4801     }
4802 
4803     /**
4804      * Sets the text color for all the states (normal, selected,
4805      * focused) to be this color.
4806      *
4807      * @param color A color value in the form 0xAARRGGBB.
4808      * Do not pass a resource ID. To get a color value from a resource ID, call
4809      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
4810      *
4811      * @see #setTextColor(ColorStateList)
4812      * @see #getTextColors()
4813      *
4814      * @attr ref android.R.styleable#TextView_textColor
4815      */
4816     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)4817     public void setTextColor(@ColorInt int color) {
4818         mTextColor = ColorStateList.valueOf(color);
4819         updateTextColors();
4820     }
4821 
4822     /**
4823      * Sets the text color.
4824      *
4825      * @see #setTextColor(int)
4826      * @see #getTextColors()
4827      * @see #setHintTextColor(ColorStateList)
4828      * @see #setLinkTextColor(ColorStateList)
4829      *
4830      * @attr ref android.R.styleable#TextView_textColor
4831      */
4832     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)4833     public void setTextColor(ColorStateList colors) {
4834         if (colors == null) {
4835             throw new NullPointerException();
4836         }
4837 
4838         mTextColor = colors;
4839         updateTextColors();
4840     }
4841 
4842     /**
4843      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
4844      *
4845      * @see #setTextColor(ColorStateList)
4846      * @see #setTextColor(int)
4847      *
4848      * @attr ref android.R.styleable#TextView_textColor
4849      */
4850     @InspectableProperty(name = "textColor")
getTextColors()4851     public final ColorStateList getTextColors() {
4852         return mTextColor;
4853     }
4854 
4855     /**
4856      * Return the current color selected for normal text.
4857      *
4858      * @return Returns the current text color.
4859      */
4860     @ColorInt
getCurrentTextColor()4861     public final int getCurrentTextColor() {
4862         return mCurTextColor;
4863     }
4864 
4865     /**
4866      * Sets the color used to display the selection highlight.
4867      *
4868      * @attr ref android.R.styleable#TextView_textColorHighlight
4869      */
4870     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)4871     public void setHighlightColor(@ColorInt int color) {
4872         if (mHighlightColor != color) {
4873             mHighlightColor = color;
4874             invalidate();
4875         }
4876     }
4877 
4878     /**
4879      * @return the color used to display the selection highlight
4880      *
4881      * @see #setHighlightColor(int)
4882      *
4883      * @attr ref android.R.styleable#TextView_textColorHighlight
4884      */
4885     @InspectableProperty(name = "textColorHighlight")
4886     @ColorInt
getHighlightColor()4887     public int getHighlightColor() {
4888         return mHighlightColor;
4889     }
4890 
4891     /**
4892      * Sets whether the soft input method will be made visible when this
4893      * TextView gets focused. The default is true.
4894      */
4895     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)4896     public final void setShowSoftInputOnFocus(boolean show) {
4897         createEditorIfNeeded();
4898         mEditor.mShowSoftInputOnFocus = show;
4899     }
4900 
4901     /**
4902      * Returns whether the soft input method will be made visible when this
4903      * TextView gets focused. The default is true.
4904      */
getShowSoftInputOnFocus()4905     public final boolean getShowSoftInputOnFocus() {
4906         // When there is no Editor, return default true value
4907         return mEditor == null || mEditor.mShowSoftInputOnFocus;
4908     }
4909 
4910     /**
4911      * Gives the text a shadow of the specified blur radius and color, the specified
4912      * distance from its drawn position.
4913      * <p>
4914      * The text shadow produced does not interact with the properties on view
4915      * that are responsible for real time shadows,
4916      * {@link View#getElevation() elevation} and
4917      * {@link View#getTranslationZ() translationZ}.
4918      *
4919      * @see Paint#setShadowLayer(float, float, float, int)
4920      *
4921      * @attr ref android.R.styleable#TextView_shadowColor
4922      * @attr ref android.R.styleable#TextView_shadowDx
4923      * @attr ref android.R.styleable#TextView_shadowDy
4924      * @attr ref android.R.styleable#TextView_shadowRadius
4925      */
setShadowLayer(float radius, float dx, float dy, int color)4926     public void setShadowLayer(float radius, float dx, float dy, int color) {
4927         mTextPaint.setShadowLayer(radius, dx, dy, color);
4928 
4929         mShadowRadius = radius;
4930         mShadowDx = dx;
4931         mShadowDy = dy;
4932         mShadowColor = color;
4933 
4934         // Will change text clip region
4935         if (mEditor != null) {
4936             mEditor.invalidateTextDisplayList();
4937             mEditor.invalidateHandlesAndActionMode();
4938         }
4939         invalidate();
4940     }
4941 
4942     /**
4943      * Gets the radius of the shadow layer.
4944      *
4945      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
4946      *
4947      * @see #setShadowLayer(float, float, float, int)
4948      *
4949      * @attr ref android.R.styleable#TextView_shadowRadius
4950      */
4951     @InspectableProperty
getShadowRadius()4952     public float getShadowRadius() {
4953         return mShadowRadius;
4954     }
4955 
4956     /**
4957      * @return the horizontal offset of the shadow layer
4958      *
4959      * @see #setShadowLayer(float, float, float, int)
4960      *
4961      * @attr ref android.R.styleable#TextView_shadowDx
4962      */
4963     @InspectableProperty
getShadowDx()4964     public float getShadowDx() {
4965         return mShadowDx;
4966     }
4967 
4968     /**
4969      * Gets the vertical offset of the shadow layer.
4970      * @return The vertical offset of the shadow layer.
4971      *
4972      * @see #setShadowLayer(float, float, float, int)
4973      *
4974      * @attr ref android.R.styleable#TextView_shadowDy
4975      */
4976     @InspectableProperty
getShadowDy()4977     public float getShadowDy() {
4978         return mShadowDy;
4979     }
4980 
4981     /**
4982      * Gets the color of the shadow layer.
4983      * @return the color of the shadow layer
4984      *
4985      * @see #setShadowLayer(float, float, float, int)
4986      *
4987      * @attr ref android.R.styleable#TextView_shadowColor
4988      */
4989     @InspectableProperty
4990     @ColorInt
getShadowColor()4991     public int getShadowColor() {
4992         return mShadowColor;
4993     }
4994 
4995     /**
4996      * Gets the {@link TextPaint} used for the text.
4997      * Use this only to consult the Paint's properties and not to change them.
4998      * @return The base paint used for the text.
4999      */
getPaint()5000     public TextPaint getPaint() {
5001         return mTextPaint;
5002     }
5003 
5004     /**
5005      * Sets the autolink mask of the text.  See {@link
5006      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
5007      * possible values.
5008      *
5009      * <p class="note"><b>Note:</b>
5010      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
5011      * is deprecated and should be avoided; see its documentation.
5012      *
5013      * @attr ref android.R.styleable#TextView_autoLink
5014      */
5015     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)5016     public final void setAutoLinkMask(int mask) {
5017         mAutoLinkMask = mask;
5018     }
5019 
5020     /**
5021      * Sets whether the movement method will automatically be set to
5022      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5023      * set to nonzero and links are detected in {@link #setText}.
5024      * The default is true.
5025      *
5026      * @attr ref android.R.styleable#TextView_linksClickable
5027      */
5028     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)5029     public final void setLinksClickable(boolean whether) {
5030         mLinksClickable = whether;
5031     }
5032 
5033     /**
5034      * Returns whether the movement method will automatically be set to
5035      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5036      * set to nonzero and links are detected in {@link #setText}.
5037      * The default is true.
5038      *
5039      * @attr ref android.R.styleable#TextView_linksClickable
5040      */
5041     @InspectableProperty
getLinksClickable()5042     public final boolean getLinksClickable() {
5043         return mLinksClickable;
5044     }
5045 
5046     /**
5047      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5048      * (by {@link Linkify} or otherwise) if any.  You can call
5049      * {@link URLSpan#getURL} on them to find where they link to
5050      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5051      * to find the region of the text they are attached to.
5052      */
getUrls()5053     public URLSpan[] getUrls() {
5054         if (mText instanceof Spanned) {
5055             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5056         } else {
5057             return new URLSpan[0];
5058         }
5059     }
5060 
5061     /**
5062      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5063      * TextView.
5064      *
5065      * @see #setHintTextColor(ColorStateList)
5066      * @see #getHintTextColors()
5067      * @see #setTextColor(int)
5068      *
5069      * @attr ref android.R.styleable#TextView_textColorHint
5070      */
5071     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)5072     public final void setHintTextColor(@ColorInt int color) {
5073         mHintTextColor = ColorStateList.valueOf(color);
5074         updateTextColors();
5075     }
5076 
5077     /**
5078      * Sets the color of the hint text.
5079      *
5080      * @see #getHintTextColors()
5081      * @see #setHintTextColor(int)
5082      * @see #setTextColor(ColorStateList)
5083      * @see #setLinkTextColor(ColorStateList)
5084      *
5085      * @attr ref android.R.styleable#TextView_textColorHint
5086      */
setHintTextColor(ColorStateList colors)5087     public final void setHintTextColor(ColorStateList colors) {
5088         mHintTextColor = colors;
5089         updateTextColors();
5090     }
5091 
5092     /**
5093      * @return the color of the hint text, for the different states of this TextView.
5094      *
5095      * @see #setHintTextColor(ColorStateList)
5096      * @see #setHintTextColor(int)
5097      * @see #setTextColor(ColorStateList)
5098      * @see #setLinkTextColor(ColorStateList)
5099      *
5100      * @attr ref android.R.styleable#TextView_textColorHint
5101      */
5102     @InspectableProperty(name = "textColorHint")
getHintTextColors()5103     public final ColorStateList getHintTextColors() {
5104         return mHintTextColor;
5105     }
5106 
5107     /**
5108      * <p>Return the current color selected to paint the hint text.</p>
5109      *
5110      * @return Returns the current hint text color.
5111      */
5112     @ColorInt
getCurrentHintTextColor()5113     public final int getCurrentHintTextColor() {
5114         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5115     }
5116 
5117     /**
5118      * Sets the color of links in the text.
5119      *
5120      * @see #setLinkTextColor(ColorStateList)
5121      * @see #getLinkTextColors()
5122      *
5123      * @attr ref android.R.styleable#TextView_textColorLink
5124      */
5125     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)5126     public final void setLinkTextColor(@ColorInt int color) {
5127         mLinkTextColor = ColorStateList.valueOf(color);
5128         updateTextColors();
5129     }
5130 
5131     /**
5132      * Sets the color of links in the text.
5133      *
5134      * @see #setLinkTextColor(int)
5135      * @see #getLinkTextColors()
5136      * @see #setTextColor(ColorStateList)
5137      * @see #setHintTextColor(ColorStateList)
5138      *
5139      * @attr ref android.R.styleable#TextView_textColorLink
5140      */
setLinkTextColor(ColorStateList colors)5141     public final void setLinkTextColor(ColorStateList colors) {
5142         mLinkTextColor = colors;
5143         updateTextColors();
5144     }
5145 
5146     /**
5147      * @return the list of colors used to paint the links in the text, for the different states of
5148      * this TextView
5149      *
5150      * @see #setLinkTextColor(ColorStateList)
5151      * @see #setLinkTextColor(int)
5152      *
5153      * @attr ref android.R.styleable#TextView_textColorLink
5154      */
5155     @InspectableProperty(name = "textColorLink")
getLinkTextColors()5156     public final ColorStateList getLinkTextColors() {
5157         return mLinkTextColor;
5158     }
5159 
5160     /**
5161      * Sets the horizontal alignment of the text and the
5162      * vertical gravity that will be used when there is extra space
5163      * in the TextView beyond what is required for the text itself.
5164      *
5165      * @see android.view.Gravity
5166      * @attr ref android.R.styleable#TextView_gravity
5167      */
setGravity(int gravity)5168     public void setGravity(int gravity) {
5169         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5170             gravity |= Gravity.START;
5171         }
5172         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5173             gravity |= Gravity.TOP;
5174         }
5175 
5176         boolean newLayout = false;
5177 
5178         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5179                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5180             newLayout = true;
5181         }
5182 
5183         if (gravity != mGravity) {
5184             invalidate();
5185         }
5186 
5187         mGravity = gravity;
5188 
5189         if (mLayout != null && newLayout) {
5190             // XXX this is heavy-handed because no actual content changes.
5191             int want = mLayout.getWidth();
5192             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5193 
5194             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5195                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5196         }
5197     }
5198 
5199     /**
5200      * Returns the horizontal and vertical alignment of this TextView.
5201      *
5202      * @see android.view.Gravity
5203      * @attr ref android.R.styleable#TextView_gravity
5204      */
5205     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()5206     public int getGravity() {
5207         return mGravity;
5208     }
5209 
5210     /**
5211      * Gets the flags on the Paint being used to display the text.
5212      * @return The flags on the Paint being used to display the text.
5213      * @see Paint#getFlags
5214      */
getPaintFlags()5215     public int getPaintFlags() {
5216         return mTextPaint.getFlags();
5217     }
5218 
5219     /**
5220      * Sets flags on the Paint being used to display the text and
5221      * reflows the text if they are different from the old flags.
5222      * @see Paint#setFlags
5223      */
5224     @android.view.RemotableViewMethod
setPaintFlags(int flags)5225     public void setPaintFlags(int flags) {
5226         if (mTextPaint.getFlags() != flags) {
5227             mTextPaint.setFlags(flags);
5228 
5229             if (mLayout != null) {
5230                 nullLayouts();
5231                 requestLayout();
5232                 invalidate();
5233             }
5234         }
5235     }
5236 
5237     /**
5238      * Sets whether the text should be allowed to be wider than the
5239      * View is.  If false, it will be wrapped to the width of the View.
5240      *
5241      * @attr ref android.R.styleable#TextView_scrollHorizontally
5242      */
setHorizontallyScrolling(boolean whether)5243     public void setHorizontallyScrolling(boolean whether) {
5244         if (mHorizontallyScrolling != whether) {
5245             mHorizontallyScrolling = whether;
5246 
5247             if (mLayout != null) {
5248                 nullLayouts();
5249                 requestLayout();
5250                 invalidate();
5251             }
5252         }
5253     }
5254 
5255     /**
5256      * Returns whether the text is allowed to be wider than the View.
5257      * If false, the text will be wrapped to the width of the View.
5258      *
5259      * @attr ref android.R.styleable#TextView_scrollHorizontally
5260      * @see #setHorizontallyScrolling(boolean)
5261      */
5262     @InspectableProperty(name = "scrollHorizontally")
isHorizontallyScrollable()5263     public final boolean isHorizontallyScrollable() {
5264         return mHorizontallyScrolling;
5265     }
5266 
5267     /**
5268      * Returns whether the text is allowed to be wider than the View.
5269      * If false, the text will be wrapped to the width of the View.
5270      *
5271      * @attr ref android.R.styleable#TextView_scrollHorizontally
5272      * @hide
5273      */
5274     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
getHorizontallyScrolling()5275     public boolean getHorizontallyScrolling() {
5276         return mHorizontallyScrolling;
5277     }
5278 
5279     /**
5280      * Sets the height of the TextView to be at least {@code minLines} tall.
5281      * <p>
5282      * This value is used for height calculation if LayoutParams does not force TextView to have an
5283      * exact height. Setting this value overrides other previous minimum height configurations such
5284      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
5285      * this value to 1.
5286      *
5287      * @param minLines the minimum height of TextView in terms of number of lines
5288      *
5289      * @see #getMinLines()
5290      * @see #setLines(int)
5291      *
5292      * @attr ref android.R.styleable#TextView_minLines
5293      */
5294     @android.view.RemotableViewMethod
setMinLines(int minLines)5295     public void setMinLines(int minLines) {
5296         mMinimum = minLines;
5297         mMinMode = LINES;
5298 
5299         requestLayout();
5300         invalidate();
5301     }
5302 
5303     /**
5304      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
5305      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
5306      *
5307      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
5308      *         height is not defined in lines
5309      *
5310      * @see #setMinLines(int)
5311      * @see #setLines(int)
5312      *
5313      * @attr ref android.R.styleable#TextView_minLines
5314      */
5315     @InspectableProperty
getMinLines()5316     public int getMinLines() {
5317         return mMinMode == LINES ? mMinimum : -1;
5318     }
5319 
5320     /**
5321      * Sets the height of the TextView to be at least {@code minPixels} tall.
5322      * <p>
5323      * This value is used for height calculation if LayoutParams does not force TextView to have an
5324      * exact height. Setting this value overrides previous minimum height configurations such as
5325      * {@link #setMinLines(int)} or {@link #setLines(int)}.
5326      * <p>
5327      * The value given here is different than {@link #setMinimumHeight(int)}. Between
5328      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
5329      * used to decide the final height.
5330      *
5331      * @param minPixels the minimum height of TextView in terms of pixels
5332      *
5333      * @see #getMinHeight()
5334      * @see #setHeight(int)
5335      *
5336      * @attr ref android.R.styleable#TextView_minHeight
5337      */
5338     @android.view.RemotableViewMethod
setMinHeight(int minPixels)5339     public void setMinHeight(int minPixels) {
5340         mMinimum = minPixels;
5341         mMinMode = PIXELS;
5342 
5343         requestLayout();
5344         invalidate();
5345     }
5346 
5347     /**
5348      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
5349      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
5350      *
5351      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
5352      *         defined in pixels
5353      *
5354      * @see #setMinHeight(int)
5355      * @see #setHeight(int)
5356      *
5357      * @attr ref android.R.styleable#TextView_minHeight
5358      */
getMinHeight()5359     public int getMinHeight() {
5360         return mMinMode == PIXELS ? mMinimum : -1;
5361     }
5362 
5363     /**
5364      * Sets the height of the TextView to be at most {@code maxLines} tall.
5365      * <p>
5366      * This value is used for height calculation if LayoutParams does not force TextView to have an
5367      * exact height. Setting this value overrides previous maximum height configurations such as
5368      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
5369      *
5370      * @param maxLines the maximum height of TextView in terms of number of lines
5371      *
5372      * @see #getMaxLines()
5373      * @see #setLines(int)
5374      *
5375      * @attr ref android.R.styleable#TextView_maxLines
5376      */
5377     @android.view.RemotableViewMethod
setMaxLines(int maxLines)5378     public void setMaxLines(int maxLines) {
5379         mMaximum = maxLines;
5380         mMaxMode = LINES;
5381 
5382         requestLayout();
5383         invalidate();
5384     }
5385 
5386     /**
5387      * Returns the maximum height of TextView in terms of number of lines or -1 if the
5388      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
5389      *
5390      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
5391      *         is not defined in lines.
5392      *
5393      * @see #setMaxLines(int)
5394      * @see #setLines(int)
5395      *
5396      * @attr ref android.R.styleable#TextView_maxLines
5397      */
5398     @InspectableProperty
getMaxLines()5399     public int getMaxLines() {
5400         return mMaxMode == LINES ? mMaximum : -1;
5401     }
5402 
5403     /**
5404      * Sets the height of the TextView to be at most {@code maxPixels} tall.
5405      * <p>
5406      * This value is used for height calculation if LayoutParams does not force TextView to have an
5407      * exact height. Setting this value overrides previous maximum height configurations such as
5408      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
5409      *
5410      * @param maxPixels the maximum height of TextView in terms of pixels
5411      *
5412      * @see #getMaxHeight()
5413      * @see #setHeight(int)
5414      *
5415      * @attr ref android.R.styleable#TextView_maxHeight
5416      */
5417     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)5418     public void setMaxHeight(int maxPixels) {
5419         mMaximum = maxPixels;
5420         mMaxMode = PIXELS;
5421 
5422         requestLayout();
5423         invalidate();
5424     }
5425 
5426     /**
5427      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
5428      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
5429      *
5430      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
5431      *         is not defined in pixels
5432      *
5433      * @see #setMaxHeight(int)
5434      * @see #setHeight(int)
5435      *
5436      * @attr ref android.R.styleable#TextView_maxHeight
5437      */
5438     @InspectableProperty
getMaxHeight()5439     public int getMaxHeight() {
5440         return mMaxMode == PIXELS ? mMaximum : -1;
5441     }
5442 
5443     /**
5444      * Sets the height of the TextView to be exactly {@code lines} tall.
5445      * <p>
5446      * This value is used for height calculation if LayoutParams does not force TextView to have an
5447      * exact height. Setting this value overrides previous minimum/maximum height configurations
5448      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
5449      * set this value to 1.
5450      *
5451      * @param lines the exact height of the TextView in terms of lines
5452      *
5453      * @see #setHeight(int)
5454      *
5455      * @attr ref android.R.styleable#TextView_lines
5456      */
5457     @android.view.RemotableViewMethod
setLines(int lines)5458     public void setLines(int lines) {
5459         mMaximum = mMinimum = lines;
5460         mMaxMode = mMinMode = LINES;
5461 
5462         requestLayout();
5463         invalidate();
5464     }
5465 
5466     /**
5467      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
5468      * <p>
5469      * This value is used for height calculation if LayoutParams does not force TextView to have an
5470      * exact height. Setting this value overrides previous minimum/maximum height configurations
5471      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
5472      *
5473      * @param pixels the exact height of the TextView in terms of pixels
5474      *
5475      * @see #setLines(int)
5476      *
5477      * @attr ref android.R.styleable#TextView_height
5478      */
5479     @android.view.RemotableViewMethod
setHeight(int pixels)5480     public void setHeight(int pixels) {
5481         mMaximum = mMinimum = pixels;
5482         mMaxMode = mMinMode = PIXELS;
5483 
5484         requestLayout();
5485         invalidate();
5486     }
5487 
5488     /**
5489      * Sets the width of the TextView to be at least {@code minEms} wide.
5490      * <p>
5491      * This value is used for width calculation if LayoutParams does not force TextView to have an
5492      * exact width. Setting this value overrides previous minimum width configurations such as
5493      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5494      *
5495      * @param minEms the minimum width of TextView in terms of ems
5496      *
5497      * @see #getMinEms()
5498      * @see #setEms(int)
5499      *
5500      * @attr ref android.R.styleable#TextView_minEms
5501      */
5502     @android.view.RemotableViewMethod
setMinEms(int minEms)5503     public void setMinEms(int minEms) {
5504         mMinWidth = minEms;
5505         mMinWidthMode = EMS;
5506 
5507         requestLayout();
5508         invalidate();
5509     }
5510 
5511     /**
5512      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
5513      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5514      *
5515      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
5516      *         defined in ems
5517      *
5518      * @see #setMinEms(int)
5519      * @see #setEms(int)
5520      *
5521      * @attr ref android.R.styleable#TextView_minEms
5522      */
5523     @InspectableProperty
getMinEms()5524     public int getMinEms() {
5525         return mMinWidthMode == EMS ? mMinWidth : -1;
5526     }
5527 
5528     /**
5529      * Sets the width of the TextView to be at least {@code minPixels} wide.
5530      * <p>
5531      * This value is used for width calculation if LayoutParams does not force TextView to have an
5532      * exact width. Setting this value overrides previous minimum width configurations such as
5533      * {@link #setMinEms(int)} or {@link #setEms(int)}.
5534      * <p>
5535      * The value given here is different than {@link #setMinimumWidth(int)}. Between
5536      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
5537      * to decide the final width.
5538      *
5539      * @param minPixels the minimum width of TextView in terms of pixels
5540      *
5541      * @see #getMinWidth()
5542      * @see #setWidth(int)
5543      *
5544      * @attr ref android.R.styleable#TextView_minWidth
5545      */
5546     @android.view.RemotableViewMethod
setMinWidth(int minPixels)5547     public void setMinWidth(int minPixels) {
5548         mMinWidth = minPixels;
5549         mMinWidthMode = PIXELS;
5550 
5551         requestLayout();
5552         invalidate();
5553     }
5554 
5555     /**
5556      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
5557      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
5558      *
5559      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
5560      *         defined in pixels
5561      *
5562      * @see #setMinWidth(int)
5563      * @see #setWidth(int)
5564      *
5565      * @attr ref android.R.styleable#TextView_minWidth
5566      */
5567     @InspectableProperty
getMinWidth()5568     public int getMinWidth() {
5569         return mMinWidthMode == PIXELS ? mMinWidth : -1;
5570     }
5571 
5572     /**
5573      * Sets the width of the TextView to be at most {@code maxEms} wide.
5574      * <p>
5575      * This value is used for width calculation if LayoutParams does not force TextView to have an
5576      * exact width. Setting this value overrides previous maximum width configurations such as
5577      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5578      *
5579      * @param maxEms the maximum width of TextView in terms of ems
5580      *
5581      * @see #getMaxEms()
5582      * @see #setEms(int)
5583      *
5584      * @attr ref android.R.styleable#TextView_maxEms
5585      */
5586     @android.view.RemotableViewMethod
setMaxEms(int maxEms)5587     public void setMaxEms(int maxEms) {
5588         mMaxWidth = maxEms;
5589         mMaxWidthMode = EMS;
5590 
5591         requestLayout();
5592         invalidate();
5593     }
5594 
5595     /**
5596      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
5597      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5598      *
5599      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
5600      *         defined in ems
5601      *
5602      * @see #setMaxEms(int)
5603      * @see #setEms(int)
5604      *
5605      * @attr ref android.R.styleable#TextView_maxEms
5606      */
5607     @InspectableProperty
getMaxEms()5608     public int getMaxEms() {
5609         return mMaxWidthMode == EMS ? mMaxWidth : -1;
5610     }
5611 
5612     /**
5613      * Sets the width of the TextView to be at most {@code maxPixels} wide.
5614      * <p>
5615      * This value is used for width calculation if LayoutParams does not force TextView to have an
5616      * exact width. Setting this value overrides previous maximum width configurations such as
5617      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
5618      *
5619      * @param maxPixels the maximum width of TextView in terms of pixels
5620      *
5621      * @see #getMaxWidth()
5622      * @see #setWidth(int)
5623      *
5624      * @attr ref android.R.styleable#TextView_maxWidth
5625      */
5626     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)5627     public void setMaxWidth(int maxPixels) {
5628         mMaxWidth = maxPixels;
5629         mMaxWidthMode = PIXELS;
5630 
5631         requestLayout();
5632         invalidate();
5633     }
5634 
5635     /**
5636      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
5637      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
5638      *
5639      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
5640      *         defined in pixels
5641      *
5642      * @see #setMaxWidth(int)
5643      * @see #setWidth(int)
5644      *
5645      * @attr ref android.R.styleable#TextView_maxWidth
5646      */
5647     @InspectableProperty
getMaxWidth()5648     public int getMaxWidth() {
5649         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
5650     }
5651 
5652     /**
5653      * Sets the width of the TextView to be exactly {@code ems} wide.
5654      *
5655      * This value is used for width calculation if LayoutParams does not force TextView to have an
5656      * exact width. Setting this value overrides previous minimum/maximum configurations such as
5657      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
5658      *
5659      * @param ems the exact width of the TextView in terms of ems
5660      *
5661      * @see #setWidth(int)
5662      *
5663      * @attr ref android.R.styleable#TextView_ems
5664      */
5665     @android.view.RemotableViewMethod
setEms(int ems)5666     public void setEms(int ems) {
5667         mMaxWidth = mMinWidth = ems;
5668         mMaxWidthMode = mMinWidthMode = EMS;
5669 
5670         requestLayout();
5671         invalidate();
5672     }
5673 
5674     /**
5675      * Sets the width of the TextView to be exactly {@code pixels} wide.
5676      * <p>
5677      * This value is used for width calculation if LayoutParams does not force TextView to have an
5678      * exact width. Setting this value overrides previous minimum/maximum width configurations
5679      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
5680      *
5681      * @param pixels the exact width of the TextView in terms of pixels
5682      *
5683      * @see #setEms(int)
5684      *
5685      * @attr ref android.R.styleable#TextView_width
5686      */
5687     @android.view.RemotableViewMethod
setWidth(int pixels)5688     public void setWidth(int pixels) {
5689         mMaxWidth = mMinWidth = pixels;
5690         mMaxWidthMode = mMinWidthMode = PIXELS;
5691 
5692         requestLayout();
5693         invalidate();
5694     }
5695 
5696     /**
5697      * Sets line spacing for this TextView.  Each line other than the last line will have its height
5698      * multiplied by {@code mult} and have {@code add} added to it.
5699      *
5700      * @param add The value in pixels that should be added to each line other than the last line.
5701      *            This will be applied after the multiplier
5702      * @param mult The value by which each line height other than the last line will be multiplied
5703      *             by
5704      *
5705      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5706      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5707      */
setLineSpacing(float add, float mult)5708     public void setLineSpacing(float add, float mult) {
5709         if (mSpacingAdd != add || mSpacingMult != mult) {
5710             mSpacingAdd = add;
5711             mSpacingMult = mult;
5712 
5713             if (mLayout != null) {
5714                 nullLayouts();
5715                 requestLayout();
5716                 invalidate();
5717             }
5718         }
5719     }
5720 
5721     /**
5722      * Gets the line spacing multiplier
5723      *
5724      * @return the value by which each line's height is multiplied to get its actual height.
5725      *
5726      * @see #setLineSpacing(float, float)
5727      * @see #getLineSpacingExtra()
5728      *
5729      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5730      */
5731     @InspectableProperty
getLineSpacingMultiplier()5732     public float getLineSpacingMultiplier() {
5733         return mSpacingMult;
5734     }
5735 
5736     /**
5737      * Gets the line spacing extra space
5738      *
5739      * @return the extra space that is added to the height of each lines of this TextView.
5740      *
5741      * @see #setLineSpacing(float, float)
5742      * @see #getLineSpacingMultiplier()
5743      *
5744      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5745      */
5746     @InspectableProperty
getLineSpacingExtra()5747     public float getLineSpacingExtra() {
5748         return mSpacingAdd;
5749     }
5750 
5751     /**
5752      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
5753      * between subsequent baselines in the TextView.
5754      *
5755      * @param lineHeight the line height in pixels
5756      *
5757      * @see #setLineSpacing(float, float)
5758      * @see #getLineSpacingExtra()
5759      *
5760      * @attr ref android.R.styleable#TextView_lineHeight
5761      */
setLineHeight(@x @ntRangefrom = 0) int lineHeight)5762     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
5763         Preconditions.checkArgumentNonnegative(lineHeight);
5764 
5765         final int fontHeight = getPaint().getFontMetricsInt(null);
5766         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
5767         if (lineHeight != fontHeight) {
5768             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
5769             setLineSpacing(lineHeight - fontHeight, 1f);
5770         }
5771     }
5772 
5773     /**
5774      * Convenience method to append the specified text to the TextView's
5775      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5776      * if it was not already editable.
5777      *
5778      * @param text text to be appended to the already displayed text
5779      */
append(CharSequence text)5780     public final void append(CharSequence text) {
5781         append(text, 0, text.length());
5782     }
5783 
5784     /**
5785      * Convenience method to append the specified text slice to the TextView's
5786      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5787      * if it was not already editable.
5788      *
5789      * @param text text to be appended to the already displayed text
5790      * @param start the index of the first character in the {@code text}
5791      * @param end the index of the character following the last character in the {@code text}
5792      *
5793      * @see Appendable#append(CharSequence, int, int)
5794      */
append(CharSequence text, int start, int end)5795     public void append(CharSequence text, int start, int end) {
5796         if (!(mText instanceof Editable)) {
5797             setText(mText, BufferType.EDITABLE);
5798         }
5799 
5800         ((Editable) mText).append(text, start, end);
5801 
5802         if (mAutoLinkMask != 0) {
5803             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
5804             // Do not change the movement method for text that support text selection as it
5805             // would prevent an arbitrary cursor displacement.
5806             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
5807                 setMovementMethod(LinkMovementMethod.getInstance());
5808             }
5809         }
5810     }
5811 
updateTextColors()5812     private void updateTextColors() {
5813         boolean inval = false;
5814         final int[] drawableState = getDrawableState();
5815         int color = mTextColor.getColorForState(drawableState, 0);
5816         if (color != mCurTextColor) {
5817             mCurTextColor = color;
5818             inval = true;
5819         }
5820         if (mLinkTextColor != null) {
5821             color = mLinkTextColor.getColorForState(drawableState, 0);
5822             if (color != mTextPaint.linkColor) {
5823                 mTextPaint.linkColor = color;
5824                 inval = true;
5825             }
5826         }
5827         if (mHintTextColor != null) {
5828             color = mHintTextColor.getColorForState(drawableState, 0);
5829             if (color != mCurHintTextColor) {
5830                 mCurHintTextColor = color;
5831                 if (mText.length() == 0) {
5832                     inval = true;
5833                 }
5834             }
5835         }
5836         if (inval) {
5837             // Text needs to be redrawn with the new color
5838             if (mEditor != null) mEditor.invalidateTextDisplayList();
5839             invalidate();
5840         }
5841     }
5842 
5843     @Override
drawableStateChanged()5844     protected void drawableStateChanged() {
5845         super.drawableStateChanged();
5846 
5847         if (mTextColor != null && mTextColor.isStateful()
5848                 || (mHintTextColor != null && mHintTextColor.isStateful())
5849                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
5850             updateTextColors();
5851         }
5852 
5853         if (mDrawables != null) {
5854             final int[] state = getDrawableState();
5855             for (Drawable dr : mDrawables.mShowing) {
5856                 if (dr != null && dr.isStateful() && dr.setState(state)) {
5857                     invalidateDrawable(dr);
5858                 }
5859             }
5860         }
5861     }
5862 
5863     @Override
drawableHotspotChanged(float x, float y)5864     public void drawableHotspotChanged(float x, float y) {
5865         super.drawableHotspotChanged(x, y);
5866 
5867         if (mDrawables != null) {
5868             for (Drawable dr : mDrawables.mShowing) {
5869                 if (dr != null) {
5870                     dr.setHotspot(x, y);
5871                 }
5872             }
5873         }
5874     }
5875 
5876     @Override
onSaveInstanceState()5877     public Parcelable onSaveInstanceState() {
5878         Parcelable superState = super.onSaveInstanceState();
5879 
5880         // Save state if we are forced to
5881         final boolean freezesText = getFreezesText();
5882         boolean hasSelection = false;
5883         int start = -1;
5884         int end = -1;
5885 
5886         if (mText != null) {
5887             start = getSelectionStart();
5888             end = getSelectionEnd();
5889             if (start >= 0 || end >= 0) {
5890                 // Or save state if there is a selection
5891                 hasSelection = true;
5892             }
5893         }
5894 
5895         if (freezesText || hasSelection) {
5896             SavedState ss = new SavedState(superState);
5897 
5898             if (freezesText) {
5899                 if (mText instanceof Spanned) {
5900                     final Spannable sp = new SpannableStringBuilder(mText);
5901 
5902                     if (mEditor != null) {
5903                         removeMisspelledSpans(sp);
5904                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
5905                     }
5906 
5907                     ss.text = sp;
5908                 } else {
5909                     ss.text = mText.toString();
5910                 }
5911             }
5912 
5913             if (hasSelection) {
5914                 // XXX Should also save the current scroll position!
5915                 ss.selStart = start;
5916                 ss.selEnd = end;
5917             }
5918 
5919             if (isFocused() && start >= 0 && end >= 0) {
5920                 ss.frozenWithFocus = true;
5921             }
5922 
5923             ss.error = getError();
5924 
5925             if (mEditor != null) {
5926                 ss.editorState = mEditor.saveInstanceState();
5927             }
5928             return ss;
5929         }
5930 
5931         return superState;
5932     }
5933 
removeMisspelledSpans(Spannable spannable)5934     void removeMisspelledSpans(Spannable spannable) {
5935         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
5936                 SuggestionSpan.class);
5937         for (int i = 0; i < suggestionSpans.length; i++) {
5938             int flags = suggestionSpans[i].getFlags();
5939             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
5940                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
5941                 spannable.removeSpan(suggestionSpans[i]);
5942             }
5943         }
5944     }
5945 
5946     @Override
onRestoreInstanceState(Parcelable state)5947     public void onRestoreInstanceState(Parcelable state) {
5948         if (!(state instanceof SavedState)) {
5949             super.onRestoreInstanceState(state);
5950             return;
5951         }
5952 
5953         SavedState ss = (SavedState) state;
5954         super.onRestoreInstanceState(ss.getSuperState());
5955 
5956         // XXX restore buffer type too, as well as lots of other stuff
5957         if (ss.text != null) {
5958             setText(ss.text);
5959         }
5960 
5961         if (ss.selStart >= 0 && ss.selEnd >= 0) {
5962             if (mSpannable != null) {
5963                 int len = mText.length();
5964 
5965                 if (ss.selStart > len || ss.selEnd > len) {
5966                     String restored = "";
5967 
5968                     if (ss.text != null) {
5969                         restored = "(restored) ";
5970                     }
5971 
5972                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
5973                             + " out of range for " + restored + "text " + mText);
5974                 } else {
5975                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
5976 
5977                     if (ss.frozenWithFocus) {
5978                         createEditorIfNeeded();
5979                         mEditor.mFrozenWithFocus = true;
5980                     }
5981                 }
5982             }
5983         }
5984 
5985         if (ss.error != null) {
5986             final CharSequence error = ss.error;
5987             // Display the error later, after the first layout pass
5988             post(new Runnable() {
5989                 public void run() {
5990                     if (mEditor == null || !mEditor.mErrorWasChanged) {
5991                         setError(error);
5992                     }
5993                 }
5994             });
5995         }
5996 
5997         if (ss.editorState != null) {
5998             createEditorIfNeeded();
5999             mEditor.restoreInstanceState(ss.editorState);
6000         }
6001     }
6002 
6003     /**
6004      * Control whether this text view saves its entire text contents when
6005      * freezing to an icicle, in addition to dynamic state such as cursor
6006      * position.  By default this is false, not saving the text.  Set to true
6007      * if the text in the text view is not being saved somewhere else in
6008      * persistent storage (such as in a content provider) so that if the
6009      * view is later thawed the user will not lose their data. For
6010      * {@link android.widget.EditText} it is always enabled, regardless of
6011      * the value of the attribute.
6012      *
6013      * @param freezesText Controls whether a frozen icicle should include the
6014      * entire text data: true to include it, false to not.
6015      *
6016      * @attr ref android.R.styleable#TextView_freezesText
6017      */
6018     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)6019     public void setFreezesText(boolean freezesText) {
6020         mFreezesText = freezesText;
6021     }
6022 
6023     /**
6024      * Return whether this text view is including its entire text contents
6025      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
6026      *
6027      * @return Returns true if text is included, false if it isn't.
6028      *
6029      * @see #setFreezesText
6030      */
6031     @InspectableProperty
getFreezesText()6032     public boolean getFreezesText() {
6033         return mFreezesText;
6034     }
6035 
6036     ///////////////////////////////////////////////////////////////////////////
6037 
6038     /**
6039      * Sets the Factory used to create new {@link Editable Editables}.
6040      *
6041      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
6042      *
6043      * @see android.text.Editable.Factory
6044      * @see android.widget.TextView.BufferType#EDITABLE
6045      */
setEditableFactory(Editable.Factory factory)6046     public final void setEditableFactory(Editable.Factory factory) {
6047         mEditableFactory = factory;
6048         setText(mText);
6049     }
6050 
6051     /**
6052      * Sets the Factory used to create new {@link Spannable Spannables}.
6053      *
6054      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
6055      *
6056      * @see android.text.Spannable.Factory
6057      * @see android.widget.TextView.BufferType#SPANNABLE
6058      */
setSpannableFactory(Spannable.Factory factory)6059     public final void setSpannableFactory(Spannable.Factory factory) {
6060         mSpannableFactory = factory;
6061         setText(mText);
6062     }
6063 
6064     /**
6065      * Sets the text to be displayed. TextView <em>does not</em> accept
6066      * HTML-like formatting, which you can do with text strings in XML resource files.
6067      * To style your strings, attach android.text.style.* objects to a
6068      * {@link android.text.SpannableString}, or see the
6069      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
6070      * Available Resource Types</a> documentation for an example of setting
6071      * formatted text in the XML resource file.
6072      * <p/>
6073      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6074      * intermediate {@link Spannable Spannables}. Likewise it will use
6075      * {@link android.text.Editable.Factory} to create final or intermediate
6076      * {@link Editable Editables}.
6077      *
6078      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
6079      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
6080      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
6081      *
6082      * @param text text to be displayed
6083      *
6084      * @attr ref android.R.styleable#TextView_text
6085      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
6086      *                                  parameters used to create the PrecomputedText mismatches
6087      *                                  with this TextView.
6088      */
6089     @android.view.RemotableViewMethod
setText(CharSequence text)6090     public final void setText(CharSequence text) {
6091         setText(text, mBufferType);
6092     }
6093 
6094     /**
6095      * Sets the text to be displayed but retains the cursor position. Same as
6096      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
6097      * new text.
6098      * <p/>
6099      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6100      * intermediate {@link Spannable Spannables}. Likewise it will use
6101      * {@link android.text.Editable.Factory} to create final or intermediate
6102      * {@link Editable Editables}.
6103      *
6104      * @param text text to be displayed
6105      *
6106      * @see #setText(CharSequence)
6107      */
6108     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)6109     public final void setTextKeepState(CharSequence text) {
6110         setTextKeepState(text, mBufferType);
6111     }
6112 
6113     /**
6114      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
6115      * <p/>
6116      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6117      * intermediate {@link Spannable Spannables}. Likewise it will use
6118      * {@link android.text.Editable.Factory} to create final or intermediate
6119      * {@link Editable Editables}.
6120      *
6121      * Subclasses overriding this method should ensure that the following post condition holds,
6122      * in order to guarantee the safety of the view's measurement and layout operations:
6123      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
6124      * will be different from {@code null}.
6125      *
6126      * @param text text to be displayed
6127      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6128      *              stored as a static text, styleable/spannable text, or editable text
6129      *
6130      * @see #setText(CharSequence)
6131      * @see android.widget.TextView.BufferType
6132      * @see #setSpannableFactory(Spannable.Factory)
6133      * @see #setEditableFactory(Editable.Factory)
6134      *
6135      * @attr ref android.R.styleable#TextView_text
6136      * @attr ref android.R.styleable#TextView_bufferType
6137      */
setText(CharSequence text, BufferType type)6138     public void setText(CharSequence text, BufferType type) {
6139         setText(text, type, true, 0);
6140 
6141         if (mCharWrapper != null) {
6142             mCharWrapper.mChars = null;
6143         }
6144     }
6145 
6146     @UnsupportedAppUsage
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6147     private void setText(CharSequence text, BufferType type,
6148                          boolean notifyBefore, int oldlen) {
6149         mTextSetFromXmlOrResourceId = false;
6150         if (text == null) {
6151             text = "";
6152         }
6153 
6154         // If suggestions are not enabled, remove the suggestion spans from the text
6155         if (!isSuggestionsEnabled()) {
6156             text = removeSuggestionSpans(text);
6157         }
6158 
6159         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
6160 
6161         if (text instanceof Spanned
6162                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
6163             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
6164                 setHorizontalFadingEdgeEnabled(true);
6165                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
6166             } else {
6167                 setHorizontalFadingEdgeEnabled(false);
6168                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
6169             }
6170             setEllipsize(TextUtils.TruncateAt.MARQUEE);
6171         }
6172 
6173         int n = mFilters.length;
6174         for (int i = 0; i < n; i++) {
6175             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
6176             if (out != null) {
6177                 text = out;
6178             }
6179         }
6180 
6181         if (notifyBefore) {
6182             if (mText != null) {
6183                 oldlen = mText.length();
6184                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
6185             } else {
6186                 sendBeforeTextChanged("", 0, 0, text.length());
6187             }
6188         }
6189 
6190         boolean needEditableForNotification = false;
6191 
6192         if (mListeners != null && mListeners.size() != 0) {
6193             needEditableForNotification = true;
6194         }
6195 
6196         PrecomputedText precomputed =
6197                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
6198         if (type == BufferType.EDITABLE || getKeyListener() != null
6199                 || needEditableForNotification) {
6200             createEditorIfNeeded();
6201             mEditor.forgetUndoRedo();
6202             Editable t = mEditableFactory.newEditable(text);
6203             text = t;
6204             setFilters(t, mFilters);
6205             InputMethodManager imm = getInputMethodManager();
6206             if (imm != null) imm.restartInput(this);
6207         } else if (precomputed != null) {
6208             if (mTextDir == null) {
6209                 mTextDir = getTextDirectionHeuristic();
6210             }
6211             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
6212                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
6213                             mHyphenationFrequency);
6214             switch (checkResult) {
6215                 case PrecomputedText.Params.UNUSABLE:
6216                     throw new IllegalArgumentException(
6217                         "PrecomputedText's Parameters don't match the parameters of this TextView."
6218                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
6219                         + "to override the settings of this TextView: "
6220                         + "PrecomputedText: " + precomputed.getParams()
6221                         + "TextView: " + getTextMetricsParams());
6222                 case PrecomputedText.Params.NEED_RECOMPUTE:
6223                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
6224                     break;
6225                 case PrecomputedText.Params.USABLE:
6226                     // pass through
6227             }
6228         } else if (type == BufferType.SPANNABLE || mMovement != null) {
6229             text = mSpannableFactory.newSpannable(text);
6230         } else if (!(text instanceof CharWrapper)) {
6231             text = TextUtils.stringOrSpannedString(text);
6232         }
6233 
6234         if (mAutoLinkMask != 0) {
6235             Spannable s2;
6236 
6237             if (type == BufferType.EDITABLE || text instanceof Spannable) {
6238                 s2 = (Spannable) text;
6239             } else {
6240                 s2 = mSpannableFactory.newSpannable(text);
6241             }
6242 
6243             if (Linkify.addLinks(s2, mAutoLinkMask)) {
6244                 text = s2;
6245                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
6246 
6247                 /*
6248                  * We must go ahead and set the text before changing the
6249                  * movement method, because setMovementMethod() may call
6250                  * setText() again to try to upgrade the buffer type.
6251                  */
6252                 setTextInternal(text);
6253 
6254                 // Do not change the movement method for text that support text selection as it
6255                 // would prevent an arbitrary cursor displacement.
6256                 if (mLinksClickable && !textCanBeSelected()) {
6257                     setMovementMethod(LinkMovementMethod.getInstance());
6258                 }
6259             }
6260         }
6261 
6262         mBufferType = type;
6263         setTextInternal(text);
6264 
6265         if (mTransformation == null) {
6266             mTransformed = text;
6267         } else {
6268             mTransformed = mTransformation.getTransformation(text, this);
6269         }
6270         if (mTransformed == null) {
6271             // Should not happen if the transformation method follows the non-null postcondition.
6272             mTransformed = "";
6273         }
6274 
6275         final int textLength = text.length();
6276 
6277         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
6278             Spannable sp = (Spannable) text;
6279 
6280             // Remove any ChangeWatchers that might have come from other TextViews.
6281             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
6282             final int count = watchers.length;
6283             for (int i = 0; i < count; i++) {
6284                 sp.removeSpan(watchers[i]);
6285             }
6286 
6287             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
6288 
6289             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
6290                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
6291 
6292             if (mEditor != null) mEditor.addSpanWatchers(sp);
6293 
6294             if (mTransformation != null) {
6295                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
6296             }
6297 
6298             if (mMovement != null) {
6299                 mMovement.initialize(this, (Spannable) text);
6300 
6301                 /*
6302                  * Initializing the movement method will have set the
6303                  * selection, so reset mSelectionMoved to keep that from
6304                  * interfering with the normal on-focus selection-setting.
6305                  */
6306                 if (mEditor != null) mEditor.mSelectionMoved = false;
6307             }
6308         }
6309 
6310         if (mLayout != null) {
6311             checkForRelayout();
6312         }
6313 
6314         sendOnTextChanged(text, 0, oldlen, textLength);
6315         onTextChanged(text, 0, oldlen, textLength);
6316 
6317         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
6318 
6319         if (needEditableForNotification) {
6320             sendAfterTextChanged((Editable) text);
6321         } else {
6322             notifyListeningManagersAfterTextChanged();
6323         }
6324 
6325         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
6326         if (mEditor != null) mEditor.prepareCursorControllers();
6327     }
6328 
6329     /**
6330      * Sets the TextView to display the specified slice of the specified
6331      * char array. You must promise that you will not change the contents
6332      * of the array except for right before another call to setText(),
6333      * since the TextView has no way to know that the text
6334      * has changed and that it needs to invalidate and re-layout.
6335      *
6336      * @param text char array to be displayed
6337      * @param start start index in the char array
6338      * @param len length of char count after {@code start}
6339      */
setText(char[] text, int start, int len)6340     public final void setText(char[] text, int start, int len) {
6341         int oldlen = 0;
6342 
6343         if (start < 0 || len < 0 || start + len > text.length) {
6344             throw new IndexOutOfBoundsException(start + ", " + len);
6345         }
6346 
6347         /*
6348          * We must do the before-notification here ourselves because if
6349          * the old text is a CharWrapper we destroy it before calling
6350          * into the normal path.
6351          */
6352         if (mText != null) {
6353             oldlen = mText.length();
6354             sendBeforeTextChanged(mText, 0, oldlen, len);
6355         } else {
6356             sendBeforeTextChanged("", 0, 0, len);
6357         }
6358 
6359         if (mCharWrapper == null) {
6360             mCharWrapper = new CharWrapper(text, start, len);
6361         } else {
6362             mCharWrapper.set(text, start, len);
6363         }
6364 
6365         setText(mCharWrapper, mBufferType, false, oldlen);
6366     }
6367 
6368     /**
6369      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
6370      * the cursor position. Same as
6371      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
6372      * position (if any) is retained in the new text.
6373      * <p/>
6374      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6375      * intermediate {@link Spannable Spannables}. Likewise it will use
6376      * {@link android.text.Editable.Factory} to create final or intermediate
6377      * {@link Editable Editables}.
6378      *
6379      * @param text text to be displayed
6380      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6381      *              stored as a static text, styleable/spannable text, or editable text
6382      *
6383      * @see #setText(CharSequence, android.widget.TextView.BufferType)
6384      */
setTextKeepState(CharSequence text, BufferType type)6385     public final void setTextKeepState(CharSequence text, BufferType type) {
6386         int start = getSelectionStart();
6387         int end = getSelectionEnd();
6388         int len = text.length();
6389 
6390         setText(text, type);
6391 
6392         if (start >= 0 || end >= 0) {
6393             if (mSpannable != null) {
6394                 Selection.setSelection(mSpannable,
6395                                        Math.max(0, Math.min(start, len)),
6396                                        Math.max(0, Math.min(end, len)));
6397             }
6398         }
6399     }
6400 
6401     /**
6402      * Sets the text to be displayed using a string resource identifier.
6403      *
6404      * @param resid the resource identifier of the string resource to be displayed
6405      *
6406      * @see #setText(CharSequence)
6407      *
6408      * @attr ref android.R.styleable#TextView_text
6409      */
6410     @android.view.RemotableViewMethod
setText(@tringRes int resid)6411     public final void setText(@StringRes int resid) {
6412         setText(getContext().getResources().getText(resid));
6413         mTextSetFromXmlOrResourceId = true;
6414         mTextId = resid;
6415     }
6416 
6417     /**
6418      * Sets the text to be displayed using a string resource identifier and the
6419      * {@link android.widget.TextView.BufferType}.
6420      * <p/>
6421      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6422      * intermediate {@link Spannable Spannables}. Likewise it will use
6423      * {@link android.text.Editable.Factory} to create final or intermediate
6424      * {@link Editable Editables}.
6425      *
6426      * @param resid the resource identifier of the string resource to be displayed
6427      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6428      *              stored as a static text, styleable/spannable text, or editable text
6429      *
6430      * @see #setText(int)
6431      * @see #setText(CharSequence)
6432      * @see android.widget.TextView.BufferType
6433      * @see #setSpannableFactory(Spannable.Factory)
6434      * @see #setEditableFactory(Editable.Factory)
6435      *
6436      * @attr ref android.R.styleable#TextView_text
6437      * @attr ref android.R.styleable#TextView_bufferType
6438      */
setText(@tringRes int resid, BufferType type)6439     public final void setText(@StringRes int resid, BufferType type) {
6440         setText(getContext().getResources().getText(resid), type);
6441         mTextSetFromXmlOrResourceId = true;
6442         mTextId = resid;
6443     }
6444 
6445     /**
6446      * Sets the text to be displayed when the text of the TextView is empty.
6447      * Null means to use the normal empty text. The hint does not currently
6448      * participate in determining the size of the view.
6449      *
6450      * @attr ref android.R.styleable#TextView_hint
6451      */
6452     @android.view.RemotableViewMethod
setHint(CharSequence hint)6453     public final void setHint(CharSequence hint) {
6454         setHintInternal(hint);
6455 
6456         if (mEditor != null && isInputMethodTarget()) {
6457             mEditor.reportExtractedText();
6458         }
6459     }
6460 
setHintInternal(CharSequence hint)6461     private void setHintInternal(CharSequence hint) {
6462         mHint = TextUtils.stringOrSpannedString(hint);
6463 
6464         if (mLayout != null) {
6465             checkForRelayout();
6466         }
6467 
6468         if (mText.length() == 0) {
6469             invalidate();
6470         }
6471 
6472         // Invalidate display list if hint is currently used
6473         if (mEditor != null && mText.length() == 0 && mHint != null) {
6474             mEditor.invalidateTextDisplayList();
6475         }
6476     }
6477 
6478     /**
6479      * Sets the text to be displayed when the text of the TextView is empty,
6480      * from a resource.
6481      *
6482      * @attr ref android.R.styleable#TextView_hint
6483      */
6484     @android.view.RemotableViewMethod
setHint(@tringRes int resid)6485     public final void setHint(@StringRes int resid) {
6486         mHintId = resid;
6487         setHint(getContext().getResources().getText(resid));
6488     }
6489 
6490     /**
6491      * Returns the hint that is displayed when the text of the TextView
6492      * is empty.
6493      *
6494      * @attr ref android.R.styleable#TextView_hint
6495      */
6496     @InspectableProperty
6497     @ViewDebug.CapturedViewProperty
getHint()6498     public CharSequence getHint() {
6499         return mHint;
6500     }
6501 
6502     /**
6503      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
6504      * line characters instead of letting it wrap onto multiple lines.
6505      *
6506      * @attr ref android.R.styleable#TextView_singleLine
6507      */
6508     @InspectableProperty
isSingleLine()6509     public boolean isSingleLine() {
6510         return mSingleLine;
6511     }
6512 
isMultilineInputType(int type)6513     private static boolean isMultilineInputType(int type) {
6514         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
6515                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
6516     }
6517 
6518     /**
6519      * Removes the suggestion spans.
6520      */
removeSuggestionSpans(CharSequence text)6521     CharSequence removeSuggestionSpans(CharSequence text) {
6522         if (text instanceof Spanned) {
6523             Spannable spannable;
6524             if (text instanceof Spannable) {
6525                 spannable = (Spannable) text;
6526             } else {
6527                 spannable = mSpannableFactory.newSpannable(text);
6528             }
6529 
6530             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
6531             if (spans.length == 0) {
6532                 return text;
6533             } else {
6534                 text = spannable;
6535             }
6536 
6537             for (int i = 0; i < spans.length; i++) {
6538                 spannable.removeSpan(spans[i]);
6539             }
6540         }
6541         return text;
6542     }
6543 
6544     /**
6545      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
6546      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
6547      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
6548      * then a soft keyboard will not be displayed for this text view.
6549      *
6550      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
6551      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
6552      * type.
6553      *
6554      * @see #getInputType()
6555      * @see #setRawInputType(int)
6556      * @see android.text.InputType
6557      * @attr ref android.R.styleable#TextView_inputType
6558      */
setInputType(int type)6559     public void setInputType(int type) {
6560         final boolean wasPassword = isPasswordInputType(getInputType());
6561         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
6562         setInputType(type, false);
6563         final boolean isPassword = isPasswordInputType(type);
6564         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
6565         boolean forceUpdate = false;
6566         if (isPassword) {
6567             setTransformationMethod(PasswordTransformationMethod.getInstance());
6568             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6569                     Typeface.NORMAL, -1 /* weight, not specifeid */);
6570         } else if (isVisiblePassword) {
6571             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6572                 forceUpdate = true;
6573             }
6574             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6575                     Typeface.NORMAL, -1 /* weight, not specified */);
6576         } else if (wasPassword || wasVisiblePassword) {
6577             // not in password mode, clean up typeface and transformation
6578             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
6579                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
6580                     -1 /* weight, not specified */);
6581             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6582                 forceUpdate = true;
6583             }
6584         }
6585 
6586         boolean singleLine = !isMultilineInputType(type);
6587 
6588         // We need to update the single line mode if it has changed or we
6589         // were previously in password mode.
6590         if (mSingleLine != singleLine || forceUpdate) {
6591             // Change single line mode, but only change the transformation if
6592             // we are not in password mode.
6593             applySingleLine(singleLine, !isPassword, true);
6594         }
6595 
6596         if (!isSuggestionsEnabled()) {
6597             setTextInternal(removeSuggestionSpans(mText));
6598         }
6599 
6600         InputMethodManager imm = getInputMethodManager();
6601         if (imm != null) imm.restartInput(this);
6602     }
6603 
6604     /**
6605      * It would be better to rely on the input type for everything. A password inputType should have
6606      * a password transformation. We should hence use isPasswordInputType instead of this method.
6607      *
6608      * We should:
6609      * - Call setInputType in setKeyListener instead of changing the input type directly (which
6610      * would install the correct transformation).
6611      * - Refuse the installation of a non-password transformation in setTransformation if the input
6612      * type is password.
6613      *
6614      * However, this is like this for legacy reasons and we cannot break existing apps. This method
6615      * is useful since it matches what the user can see (obfuscated text or not).
6616      *
6617      * @return true if the current transformation method is of the password type.
6618      */
hasPasswordTransformationMethod()6619     boolean hasPasswordTransformationMethod() {
6620         return mTransformation instanceof PasswordTransformationMethod;
6621     }
6622 
6623     /**
6624      * Returns true if the current inputType is any type of password.
6625      *
6626      * @hide
6627      */
isAnyPasswordInputType()6628     public boolean isAnyPasswordInputType() {
6629         final int inputType = getInputType();
6630         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
6631     }
6632 
isPasswordInputType(int inputType)6633     static boolean isPasswordInputType(int inputType) {
6634         final int variation =
6635                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6636         return variation
6637                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
6638                 || variation
6639                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
6640                 || variation
6641                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
6642     }
6643 
isVisiblePasswordInputType(int inputType)6644     private static boolean isVisiblePasswordInputType(int inputType) {
6645         final int variation =
6646                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6647         return variation
6648                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
6649     }
6650 
6651     /**
6652      * Directly change the content type integer of the text view, without
6653      * modifying any other state.
6654      * @see #setInputType(int)
6655      * @see android.text.InputType
6656      * @attr ref android.R.styleable#TextView_inputType
6657      */
setRawInputType(int type)6658     public void setRawInputType(int type) {
6659         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
6660         createEditorIfNeeded();
6661         mEditor.mInputType = type;
6662     }
6663 
6664     /**
6665      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
6666      *         a {@code Locale} object that can be used to customize key various listeners.
6667      * @see DateKeyListener#getInstance(Locale)
6668      * @see DateTimeKeyListener#getInstance(Locale)
6669      * @see DigitsKeyListener#getInstance(Locale)
6670      * @see TimeKeyListener#getInstance(Locale)
6671      */
6672     @Nullable
getCustomLocaleForKeyListenerOrNull()6673     private Locale getCustomLocaleForKeyListenerOrNull() {
6674         if (!mUseInternationalizedInput) {
6675             // If the application does not target O, stick to the previous behavior.
6676             return null;
6677         }
6678         final LocaleList locales = getImeHintLocales();
6679         if (locales == null) {
6680             // If the application does not explicitly specify IME hint locale, also stick to the
6681             // previous behavior.
6682             return null;
6683         }
6684         return locales.get(0);
6685     }
6686 
6687     @UnsupportedAppUsage
setInputType(int type, boolean direct)6688     private void setInputType(int type, boolean direct) {
6689         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
6690         KeyListener input;
6691         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
6692             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
6693             TextKeyListener.Capitalize cap;
6694             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
6695                 cap = TextKeyListener.Capitalize.CHARACTERS;
6696             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
6697                 cap = TextKeyListener.Capitalize.WORDS;
6698             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
6699                 cap = TextKeyListener.Capitalize.SENTENCES;
6700             } else {
6701                 cap = TextKeyListener.Capitalize.NONE;
6702             }
6703             input = TextKeyListener.getInstance(autotext, cap);
6704         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
6705             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6706             input = DigitsKeyListener.getInstance(
6707                     locale,
6708                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
6709                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
6710             if (locale != null) {
6711                 // Override type, if necessary for i18n.
6712                 int newType = input.getInputType();
6713                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
6714                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
6715                     // The class is different from the original class. So we need to override
6716                     // 'type'. But we want to keep the password flag if it's there.
6717                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
6718                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
6719                     }
6720                     type = newType;
6721                 }
6722             }
6723         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
6724             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6725             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
6726                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
6727                     input = DateKeyListener.getInstance(locale);
6728                     break;
6729                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
6730                     input = TimeKeyListener.getInstance(locale);
6731                     break;
6732                 default:
6733                     input = DateTimeKeyListener.getInstance(locale);
6734                     break;
6735             }
6736             if (mUseInternationalizedInput) {
6737                 type = input.getInputType(); // Override type, if necessary for i18n.
6738             }
6739         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
6740             input = DialerKeyListener.getInstance();
6741         } else {
6742             input = TextKeyListener.getInstance();
6743         }
6744         setRawInputType(type);
6745         mListenerChanged = false;
6746         if (direct) {
6747             createEditorIfNeeded();
6748             mEditor.mKeyListener = input;
6749         } else {
6750             setKeyListenerOnly(input);
6751         }
6752     }
6753 
6754     /**
6755      * Get the type of the editable content.
6756      *
6757      * @see #setInputType(int)
6758      * @see android.text.InputType
6759      */
6760     @InspectableProperty(flagMapping = {
6761             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
6762             @FlagEntry(
6763                     name = "text",
6764                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6765                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
6766             @FlagEntry(
6767                     name = "textUri",
6768                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6769                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
6770             @FlagEntry(
6771                     name = "textEmailAddress",
6772                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6773                     target = InputType.TYPE_CLASS_TEXT
6774                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
6775             @FlagEntry(
6776                     name = "textEmailSubject",
6777                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6778                     target = InputType.TYPE_CLASS_TEXT
6779                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
6780             @FlagEntry(
6781                     name = "textShortMessage",
6782                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6783                     target = InputType.TYPE_CLASS_TEXT
6784                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
6785             @FlagEntry(
6786                     name = "textLongMessage",
6787                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6788                     target = InputType.TYPE_CLASS_TEXT
6789                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
6790             @FlagEntry(
6791                     name = "textPersonName",
6792                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6793                     target = InputType.TYPE_CLASS_TEXT
6794                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
6795             @FlagEntry(
6796                     name = "textPostalAddress",
6797                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6798                     target = InputType.TYPE_CLASS_TEXT
6799                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
6800             @FlagEntry(
6801                     name = "textPassword",
6802                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6803                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
6804             @FlagEntry(
6805                     name = "textVisiblePassword",
6806                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6807                     target = InputType.TYPE_CLASS_TEXT
6808                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
6809             @FlagEntry(
6810                     name = "textWebEditText",
6811                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6812                     target = InputType.TYPE_CLASS_TEXT
6813                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
6814             @FlagEntry(
6815                     name = "textFilter",
6816                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6817                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
6818             @FlagEntry(
6819                     name = "textPhonetic",
6820                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6821                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
6822             @FlagEntry(
6823                     name = "textWebEmailAddress",
6824                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6825                     target = InputType.TYPE_CLASS_TEXT
6826                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
6827             @FlagEntry(
6828                     name = "textWebPassword",
6829                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6830                     target = InputType.TYPE_CLASS_TEXT
6831                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
6832             @FlagEntry(
6833                     name = "number",
6834                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6835                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
6836             @FlagEntry(
6837                     name = "numberPassword",
6838                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6839                     target = InputType.TYPE_CLASS_NUMBER
6840                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
6841             @FlagEntry(
6842                     name = "phone",
6843                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6844                     target = InputType.TYPE_CLASS_PHONE),
6845             @FlagEntry(
6846                     name = "datetime",
6847                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6848                     target = InputType.TYPE_CLASS_DATETIME
6849                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
6850             @FlagEntry(
6851                     name = "date",
6852                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6853                     target = InputType.TYPE_CLASS_DATETIME
6854                             | InputType.TYPE_DATETIME_VARIATION_DATE),
6855             @FlagEntry(
6856                     name = "time",
6857                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6858                     target = InputType.TYPE_CLASS_DATETIME
6859                             | InputType.TYPE_DATETIME_VARIATION_TIME),
6860             @FlagEntry(
6861                     name = "textCapCharacters",
6862                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6863                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
6864             @FlagEntry(
6865                     name = "textCapWords",
6866                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6867                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
6868             @FlagEntry(
6869                     name = "textCapSentences",
6870                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6871                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
6872             @FlagEntry(
6873                     name = "textAutoCorrect",
6874                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6875                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
6876             @FlagEntry(
6877                     name = "textAutoComplete",
6878                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6879                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
6880             @FlagEntry(
6881                     name = "textMultiLine",
6882                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6883                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
6884             @FlagEntry(
6885                     name = "textImeMultiLine",
6886                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6887                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
6888             @FlagEntry(
6889                     name = "textNoSuggestions",
6890                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6891                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
6892             @FlagEntry(
6893                     name = "numberSigned",
6894                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6895                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
6896             @FlagEntry(
6897                     name = "numberDecimal",
6898                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6899                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
6900     })
getInputType()6901     public int getInputType() {
6902         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
6903     }
6904 
6905     /**
6906      * Change the editor type integer associated with the text view, which
6907      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
6908      * when it has focus.
6909      * @see #getImeOptions
6910      * @see android.view.inputmethod.EditorInfo
6911      * @attr ref android.R.styleable#TextView_imeOptions
6912      */
setImeOptions(int imeOptions)6913     public void setImeOptions(int imeOptions) {
6914         createEditorIfNeeded();
6915         mEditor.createInputContentTypeIfNeeded();
6916         mEditor.mInputContentType.imeOptions = imeOptions;
6917     }
6918 
6919     /**
6920      * Get the type of the Input Method Editor (IME).
6921      * @return the type of the IME
6922      * @see #setImeOptions(int)
6923      * @see EditorInfo
6924      */
6925     @InspectableProperty(flagMapping = {
6926             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
6927             @FlagEntry(
6928                     name = "actionUnspecified",
6929                     mask = EditorInfo.IME_MASK_ACTION,
6930                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
6931             @FlagEntry(
6932                     name = "actionNone",
6933                     mask = EditorInfo.IME_MASK_ACTION,
6934                     target = EditorInfo.IME_ACTION_NONE),
6935             @FlagEntry(
6936                     name = "actionGo",
6937                     mask = EditorInfo.IME_MASK_ACTION,
6938                     target = EditorInfo.IME_ACTION_GO),
6939             @FlagEntry(
6940                     name = "actionSearch",
6941                     mask = EditorInfo.IME_MASK_ACTION,
6942                     target = EditorInfo.IME_ACTION_SEARCH),
6943             @FlagEntry(
6944                     name = "actionSend",
6945                     mask = EditorInfo.IME_MASK_ACTION,
6946                     target = EditorInfo.IME_ACTION_SEND),
6947             @FlagEntry(
6948                     name = "actionNext",
6949                     mask = EditorInfo.IME_MASK_ACTION,
6950                     target = EditorInfo.IME_ACTION_NEXT),
6951             @FlagEntry(
6952                     name = "actionDone",
6953                     mask = EditorInfo.IME_MASK_ACTION,
6954                     target = EditorInfo.IME_ACTION_DONE),
6955             @FlagEntry(
6956                     name = "actionPrevious",
6957                     mask = EditorInfo.IME_MASK_ACTION,
6958                     target = EditorInfo.IME_ACTION_PREVIOUS),
6959             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
6960             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
6961             @FlagEntry(
6962                     name = "flagNavigatePrevious",
6963                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
6964             @FlagEntry(
6965                     name = "flagNoAccessoryAction",
6966                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
6967             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
6968             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
6969             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
6970             @FlagEntry(
6971                     name = "flagNoPersonalizedLearning",
6972                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
6973     })
getImeOptions()6974     public int getImeOptions() {
6975         return mEditor != null && mEditor.mInputContentType != null
6976                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
6977     }
6978 
6979     /**
6980      * Change the custom IME action associated with the text view, which
6981      * will be reported to an IME with {@link EditorInfo#actionLabel}
6982      * and {@link EditorInfo#actionId} when it has focus.
6983      * @see #getImeActionLabel
6984      * @see #getImeActionId
6985      * @see android.view.inputmethod.EditorInfo
6986      * @attr ref android.R.styleable#TextView_imeActionLabel
6987      * @attr ref android.R.styleable#TextView_imeActionId
6988      */
setImeActionLabel(CharSequence label, int actionId)6989     public void setImeActionLabel(CharSequence label, int actionId) {
6990         createEditorIfNeeded();
6991         mEditor.createInputContentTypeIfNeeded();
6992         mEditor.mInputContentType.imeActionLabel = label;
6993         mEditor.mInputContentType.imeActionId = actionId;
6994     }
6995 
6996     /**
6997      * Get the IME action label previous set with {@link #setImeActionLabel}.
6998      *
6999      * @see #setImeActionLabel
7000      * @see android.view.inputmethod.EditorInfo
7001      */
7002     @InspectableProperty
getImeActionLabel()7003     public CharSequence getImeActionLabel() {
7004         return mEditor != null && mEditor.mInputContentType != null
7005                 ? mEditor.mInputContentType.imeActionLabel : null;
7006     }
7007 
7008     /**
7009      * Get the IME action ID previous set with {@link #setImeActionLabel}.
7010      *
7011      * @see #setImeActionLabel
7012      * @see android.view.inputmethod.EditorInfo
7013      */
7014     @InspectableProperty
getImeActionId()7015     public int getImeActionId() {
7016         return mEditor != null && mEditor.mInputContentType != null
7017                 ? mEditor.mInputContentType.imeActionId : 0;
7018     }
7019 
7020     /**
7021      * Set a special listener to be called when an action is performed
7022      * on the text view.  This will be called when the enter key is pressed,
7023      * or when an action supplied to the IME is selected by the user.  Setting
7024      * this means that the normal hard key event will not insert a newline
7025      * into the text view, even if it is multi-line; holding down the ALT
7026      * modifier will, however, allow the user to insert a newline character.
7027      */
setOnEditorActionListener(OnEditorActionListener l)7028     public void setOnEditorActionListener(OnEditorActionListener l) {
7029         createEditorIfNeeded();
7030         mEditor.createInputContentTypeIfNeeded();
7031         mEditor.mInputContentType.onEditorActionListener = l;
7032     }
7033 
7034     /**
7035      * Called when an attached input method calls
7036      * {@link InputConnection#performEditorAction(int)
7037      * InputConnection.performEditorAction()}
7038      * for this text view.  The default implementation will call your action
7039      * listener supplied to {@link #setOnEditorActionListener}, or perform
7040      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
7041      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
7042      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
7043      * EditorInfo.IME_ACTION_DONE}.
7044      *
7045      * <p>For backwards compatibility, if no IME options have been set and the
7046      * text view would not normally advance focus on enter, then
7047      * the NEXT and DONE actions received here will be turned into an enter
7048      * key down/up pair to go through the normal key handling.
7049      *
7050      * @param actionCode The code of the action being performed.
7051      *
7052      * @see #setOnEditorActionListener
7053      */
onEditorAction(int actionCode)7054     public void onEditorAction(int actionCode) {
7055         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
7056         if (ict != null) {
7057             if (ict.onEditorActionListener != null) {
7058                 if (ict.onEditorActionListener.onEditorAction(this,
7059                         actionCode, null)) {
7060                     return;
7061                 }
7062             }
7063 
7064             // This is the handling for some default action.
7065             // Note that for backwards compatibility we don't do this
7066             // default handling if explicit ime options have not been given,
7067             // instead turning this into the normal enter key codes that an
7068             // app may be expecting.
7069             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
7070                 View v = focusSearch(FOCUS_FORWARD);
7071                 if (v != null) {
7072                     if (!v.requestFocus(FOCUS_FORWARD)) {
7073                         throw new IllegalStateException("focus search returned a view "
7074                                 + "that wasn't able to take focus!");
7075                     }
7076                 }
7077                 return;
7078 
7079             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
7080                 View v = focusSearch(FOCUS_BACKWARD);
7081                 if (v != null) {
7082                     if (!v.requestFocus(FOCUS_BACKWARD)) {
7083                         throw new IllegalStateException("focus search returned a view "
7084                                 + "that wasn't able to take focus!");
7085                     }
7086                 }
7087                 return;
7088 
7089             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
7090                 InputMethodManager imm = getInputMethodManager();
7091                 if (imm != null && imm.isActive(this)) {
7092                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7093                 }
7094                 return;
7095             }
7096         }
7097 
7098         ViewRootImpl viewRootImpl = getViewRootImpl();
7099         if (viewRootImpl != null) {
7100             long eventTime = SystemClock.uptimeMillis();
7101             viewRootImpl.dispatchKeyFromIme(
7102                     new KeyEvent(eventTime, eventTime,
7103                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
7104                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7105                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7106                     | KeyEvent.FLAG_EDITOR_ACTION));
7107             viewRootImpl.dispatchKeyFromIme(
7108                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
7109                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
7110                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7111                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7112                     | KeyEvent.FLAG_EDITOR_ACTION));
7113         }
7114     }
7115 
7116     /**
7117      * Set the private content type of the text, which is the
7118      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
7119      * field that will be filled in when creating an input connection.
7120      *
7121      * @see #getPrivateImeOptions()
7122      * @see EditorInfo#privateImeOptions
7123      * @attr ref android.R.styleable#TextView_privateImeOptions
7124      */
setPrivateImeOptions(String type)7125     public void setPrivateImeOptions(String type) {
7126         createEditorIfNeeded();
7127         mEditor.createInputContentTypeIfNeeded();
7128         mEditor.mInputContentType.privateImeOptions = type;
7129     }
7130 
7131     /**
7132      * Get the private type of the content.
7133      *
7134      * @see #setPrivateImeOptions(String)
7135      * @see EditorInfo#privateImeOptions
7136      */
7137     @InspectableProperty
getPrivateImeOptions()7138     public String getPrivateImeOptions() {
7139         return mEditor != null && mEditor.mInputContentType != null
7140                 ? mEditor.mInputContentType.privateImeOptions : null;
7141     }
7142 
7143     /**
7144      * Set the extra input data of the text, which is the
7145      * {@link EditorInfo#extras TextBoxAttribute.extras}
7146      * Bundle that will be filled in when creating an input connection.  The
7147      * given integer is the resource identifier of an XML resource holding an
7148      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
7149      *
7150      * @see #getInputExtras(boolean)
7151      * @see EditorInfo#extras
7152      * @attr ref android.R.styleable#TextView_editorExtras
7153      */
setInputExtras(@mlRes int xmlResId)7154     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
7155         createEditorIfNeeded();
7156         XmlResourceParser parser = getResources().getXml(xmlResId);
7157         mEditor.createInputContentTypeIfNeeded();
7158         mEditor.mInputContentType.extras = new Bundle();
7159         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
7160     }
7161 
7162     /**
7163      * Retrieve the input extras currently associated with the text view, which
7164      * can be viewed as well as modified.
7165      *
7166      * @param create If true, the extras will be created if they don't already
7167      * exist.  Otherwise, null will be returned if none have been created.
7168      * @see #setInputExtras(int)
7169      * @see EditorInfo#extras
7170      * @attr ref android.R.styleable#TextView_editorExtras
7171      */
getInputExtras(boolean create)7172     public Bundle getInputExtras(boolean create) {
7173         if (mEditor == null && !create) return null;
7174         createEditorIfNeeded();
7175         if (mEditor.mInputContentType == null) {
7176             if (!create) return null;
7177             mEditor.createInputContentTypeIfNeeded();
7178         }
7179         if (mEditor.mInputContentType.extras == null) {
7180             if (!create) return null;
7181             mEditor.mInputContentType.extras = new Bundle();
7182         }
7183         return mEditor.mInputContentType.extras;
7184     }
7185 
7186     /**
7187      * Change "hint" locales associated with the text view, which will be reported to an IME with
7188      * {@link EditorInfo#hintLocales} when it has focus.
7189      *
7190      * Starting with Android O, this also causes internationalized listeners to be created (or
7191      * change locale) based on the first locale in the input locale list.
7192      *
7193      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
7194      * call {@link InputMethodManager#restartInput(View)}.</p>
7195      * @param hintLocales List of the languages that the user is supposed to switch to no matter
7196      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
7197      * @see #getImeHintLocales()
7198      * @see android.view.inputmethod.EditorInfo#hintLocales
7199      */
setImeHintLocales(@ullable LocaleList hintLocales)7200     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
7201         createEditorIfNeeded();
7202         mEditor.createInputContentTypeIfNeeded();
7203         mEditor.mInputContentType.imeHintLocales = hintLocales;
7204         if (mUseInternationalizedInput) {
7205             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
7206         }
7207     }
7208 
7209     /**
7210      * @return The current languages list "hint". {@code null} when no "hint" is available.
7211      * @see #setImeHintLocales(LocaleList)
7212      * @see android.view.inputmethod.EditorInfo#hintLocales
7213      */
7214     @Nullable
getImeHintLocales()7215     public LocaleList getImeHintLocales() {
7216         if (mEditor == null) {
7217             return null;
7218         }
7219         if (mEditor.mInputContentType == null) {
7220             return null;
7221         }
7222         return mEditor.mInputContentType.imeHintLocales;
7223     }
7224 
7225     /**
7226      * Returns the error message that was set to be displayed with
7227      * {@link #setError}, or <code>null</code> if no error was set
7228      * or if it the error was cleared by the widget after user input.
7229      */
getError()7230     public CharSequence getError() {
7231         return mEditor == null ? null : mEditor.mError;
7232     }
7233 
7234     /**
7235      * Sets the right-hand compound drawable of the TextView to the "error"
7236      * icon and sets an error message that will be displayed in a popup when
7237      * the TextView has focus.  The icon and error message will be reset to
7238      * null when any key events cause changes to the TextView's text.  If the
7239      * <code>error</code> is <code>null</code>, the error message and icon
7240      * will be cleared.
7241      */
7242     @android.view.RemotableViewMethod
setError(CharSequence error)7243     public void setError(CharSequence error) {
7244         if (error == null) {
7245             setError(null, null);
7246         } else {
7247             Drawable dr = getContext().getDrawable(
7248                     com.android.internal.R.drawable.indicator_input_error);
7249 
7250             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
7251             setError(error, dr);
7252         }
7253     }
7254 
7255     /**
7256      * Sets the right-hand compound drawable of the TextView to the specified
7257      * icon and sets an error message that will be displayed in a popup when
7258      * the TextView has focus.  The icon and error message will be reset to
7259      * null when any key events cause changes to the TextView's text.  The
7260      * drawable must already have had {@link Drawable#setBounds} set on it.
7261      * If the <code>error</code> is <code>null</code>, the error message will
7262      * be cleared (and you should provide a <code>null</code> icon as well).
7263      */
setError(CharSequence error, Drawable icon)7264     public void setError(CharSequence error, Drawable icon) {
7265         createEditorIfNeeded();
7266         mEditor.setError(error, icon);
7267         notifyViewAccessibilityStateChangedIfNeeded(
7268                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7269     }
7270 
7271     @Override
setFrame(int l, int t, int r, int b)7272     protected boolean setFrame(int l, int t, int r, int b) {
7273         boolean result = super.setFrame(l, t, r, b);
7274 
7275         if (mEditor != null) mEditor.setFrame();
7276 
7277         restartMarqueeIfNeeded();
7278 
7279         return result;
7280     }
7281 
restartMarqueeIfNeeded()7282     private void restartMarqueeIfNeeded() {
7283         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7284             mRestartMarquee = false;
7285             startMarquee();
7286         }
7287     }
7288 
7289     /**
7290      * Sets the list of input filters that will be used if the buffer is
7291      * Editable. Has no effect otherwise.
7292      *
7293      * @attr ref android.R.styleable#TextView_maxLength
7294      */
setFilters(InputFilter[] filters)7295     public void setFilters(InputFilter[] filters) {
7296         if (filters == null) {
7297             throw new IllegalArgumentException();
7298         }
7299 
7300         mFilters = filters;
7301 
7302         if (mText instanceof Editable) {
7303             setFilters((Editable) mText, filters);
7304         }
7305     }
7306 
7307     /**
7308      * Sets the list of input filters on the specified Editable,
7309      * and includes mInput in the list if it is an InputFilter.
7310      */
setFilters(Editable e, InputFilter[] filters)7311     private void setFilters(Editable e, InputFilter[] filters) {
7312         if (mEditor != null) {
7313             final boolean undoFilter = mEditor.mUndoInputFilter != null;
7314             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
7315             int num = 0;
7316             if (undoFilter) num++;
7317             if (keyFilter) num++;
7318             if (num > 0) {
7319                 InputFilter[] nf = new InputFilter[filters.length + num];
7320 
7321                 System.arraycopy(filters, 0, nf, 0, filters.length);
7322                 num = 0;
7323                 if (undoFilter) {
7324                     nf[filters.length] = mEditor.mUndoInputFilter;
7325                     num++;
7326                 }
7327                 if (keyFilter) {
7328                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
7329                 }
7330 
7331                 e.setFilters(nf);
7332                 return;
7333             }
7334         }
7335         e.setFilters(filters);
7336     }
7337 
7338     /**
7339      * Returns the current list of input filters.
7340      *
7341      * @attr ref android.R.styleable#TextView_maxLength
7342      */
getFilters()7343     public InputFilter[] getFilters() {
7344         return mFilters;
7345     }
7346 
7347     /////////////////////////////////////////////////////////////////////////
7348 
getBoxHeight(Layout l)7349     private int getBoxHeight(Layout l) {
7350         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
7351         int padding = (l == mHintLayout)
7352                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
7353                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
7354         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
7355     }
7356 
7357     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)7358     int getVerticalOffset(boolean forceNormal) {
7359         int voffset = 0;
7360         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7361 
7362         Layout l = mLayout;
7363         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7364             l = mHintLayout;
7365         }
7366 
7367         if (gravity != Gravity.TOP) {
7368             int boxht = getBoxHeight(l);
7369             int textht = l.getHeight();
7370 
7371             if (textht < boxht) {
7372                 if (gravity == Gravity.BOTTOM) {
7373                     voffset = boxht - textht;
7374                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7375                     voffset = (boxht - textht) >> 1;
7376                 }
7377             }
7378         }
7379         return voffset;
7380     }
7381 
getBottomVerticalOffset(boolean forceNormal)7382     private int getBottomVerticalOffset(boolean forceNormal) {
7383         int voffset = 0;
7384         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7385 
7386         Layout l = mLayout;
7387         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7388             l = mHintLayout;
7389         }
7390 
7391         if (gravity != Gravity.BOTTOM) {
7392             int boxht = getBoxHeight(l);
7393             int textht = l.getHeight();
7394 
7395             if (textht < boxht) {
7396                 if (gravity == Gravity.TOP) {
7397                     voffset = boxht - textht;
7398                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7399                     voffset = (boxht - textht) >> 1;
7400                 }
7401             }
7402         }
7403         return voffset;
7404     }
7405 
invalidateCursorPath()7406     void invalidateCursorPath() {
7407         if (mHighlightPathBogus) {
7408             invalidateCursor();
7409         } else {
7410             final int horizontalPadding = getCompoundPaddingLeft();
7411             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7412 
7413             if (mEditor.mDrawableForCursor == null) {
7414                 synchronized (TEMP_RECTF) {
7415                     /*
7416                      * The reason for this concern about the thickness of the
7417                      * cursor and doing the floor/ceil on the coordinates is that
7418                      * some EditTexts (notably textfields in the Browser) have
7419                      * anti-aliased text where not all the characters are
7420                      * necessarily at integer-multiple locations.  This should
7421                      * make sure the entire cursor gets invalidated instead of
7422                      * sometimes missing half a pixel.
7423                      */
7424                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
7425                     if (thick < 1.0f) {
7426                         thick = 1.0f;
7427                     }
7428 
7429                     thick /= 2.0f;
7430 
7431                     // mHighlightPath is guaranteed to be non null at that point.
7432                     mHighlightPath.computeBounds(TEMP_RECTF, false);
7433 
7434                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
7435                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
7436                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
7437                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
7438                 }
7439             } else {
7440                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7441                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
7442                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
7443             }
7444         }
7445     }
7446 
invalidateCursor()7447     void invalidateCursor() {
7448         int where = getSelectionEnd();
7449 
7450         invalidateCursor(where, where, where);
7451     }
7452 
invalidateCursor(int a, int b, int c)7453     private void invalidateCursor(int a, int b, int c) {
7454         if (a >= 0 || b >= 0 || c >= 0) {
7455             int start = Math.min(Math.min(a, b), c);
7456             int end = Math.max(Math.max(a, b), c);
7457             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
7458         }
7459     }
7460 
7461     /**
7462      * Invalidates the region of text enclosed between the start and end text offsets.
7463      */
invalidateRegion(int start, int end, boolean invalidateCursor)7464     void invalidateRegion(int start, int end, boolean invalidateCursor) {
7465         if (mLayout == null) {
7466             invalidate();
7467         } else {
7468             int lineStart = mLayout.getLineForOffset(start);
7469             int top = mLayout.getLineTop(lineStart);
7470 
7471             // This is ridiculous, but the descent from the line above
7472             // can hang down into the line we really want to redraw,
7473             // so we have to invalidate part of the line above to make
7474             // sure everything that needs to be redrawn really is.
7475             // (But not the whole line above, because that would cause
7476             // the same problem with the descenders on the line above it!)
7477             if (lineStart > 0) {
7478                 top -= mLayout.getLineDescent(lineStart - 1);
7479             }
7480 
7481             int lineEnd;
7482 
7483             if (start == end) {
7484                 lineEnd = lineStart;
7485             } else {
7486                 lineEnd = mLayout.getLineForOffset(end);
7487             }
7488 
7489             int bottom = mLayout.getLineBottom(lineEnd);
7490 
7491             // mEditor can be null in case selection is set programmatically.
7492             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
7493                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7494                 top = Math.min(top, bounds.top);
7495                 bottom = Math.max(bottom, bounds.bottom);
7496             }
7497 
7498             final int compoundPaddingLeft = getCompoundPaddingLeft();
7499             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7500 
7501             int left, right;
7502             if (lineStart == lineEnd && !invalidateCursor) {
7503                 left = (int) mLayout.getPrimaryHorizontal(start);
7504                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
7505                 left += compoundPaddingLeft;
7506                 right += compoundPaddingLeft;
7507             } else {
7508                 // Rectangle bounding box when the region spans several lines
7509                 left = compoundPaddingLeft;
7510                 right = getWidth() - getCompoundPaddingRight();
7511             }
7512 
7513             invalidate(mScrollX + left, verticalPadding + top,
7514                     mScrollX + right, verticalPadding + bottom);
7515         }
7516     }
7517 
registerForPreDraw()7518     private void registerForPreDraw() {
7519         if (!mPreDrawRegistered) {
7520             getViewTreeObserver().addOnPreDrawListener(this);
7521             mPreDrawRegistered = true;
7522         }
7523     }
7524 
unregisterForPreDraw()7525     private void unregisterForPreDraw() {
7526         getViewTreeObserver().removeOnPreDrawListener(this);
7527         mPreDrawRegistered = false;
7528         mPreDrawListenerDetached = false;
7529     }
7530 
7531     /**
7532      * {@inheritDoc}
7533      */
7534     @Override
onPreDraw()7535     public boolean onPreDraw() {
7536         if (mLayout == null) {
7537             assumeLayout();
7538         }
7539 
7540         if (mMovement != null) {
7541             /* This code also provides auto-scrolling when a cursor is moved using a
7542              * CursorController (insertion point or selection limits).
7543              * For selection, ensure start or end is visible depending on controller's state.
7544              */
7545             int curs = getSelectionEnd();
7546             // Do not create the controller if it is not already created.
7547             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
7548                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
7549                 curs = getSelectionStart();
7550             }
7551 
7552             /*
7553              * TODO: This should really only keep the end in view if
7554              * it already was before the text changed.  I'm not sure
7555              * of a good way to tell from here if it was.
7556              */
7557             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7558                 curs = mText.length();
7559             }
7560 
7561             if (curs >= 0) {
7562                 bringPointIntoView(curs);
7563             }
7564         } else {
7565             bringTextIntoView();
7566         }
7567 
7568         // This has to be checked here since:
7569         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
7570         //   a screen rotation) since layout is not yet initialized at that point.
7571         if (mEditor != null && mEditor.mCreatedWithASelection) {
7572             mEditor.refreshTextActionMode();
7573             mEditor.mCreatedWithASelection = false;
7574         }
7575 
7576         unregisterForPreDraw();
7577 
7578         return true;
7579     }
7580 
7581     @Override
onAttachedToWindow()7582     protected void onAttachedToWindow() {
7583         super.onAttachedToWindow();
7584 
7585         if (mEditor != null) mEditor.onAttachedToWindow();
7586 
7587         if (mPreDrawListenerDetached) {
7588             getViewTreeObserver().addOnPreDrawListener(this);
7589             mPreDrawListenerDetached = false;
7590         }
7591     }
7592 
7593     /** @hide */
7594     @Override
onDetachedFromWindowInternal()7595     protected void onDetachedFromWindowInternal() {
7596         if (mPreDrawRegistered) {
7597             getViewTreeObserver().removeOnPreDrawListener(this);
7598             mPreDrawListenerDetached = true;
7599         }
7600 
7601         resetResolvedDrawables();
7602 
7603         if (mEditor != null) mEditor.onDetachedFromWindow();
7604 
7605         super.onDetachedFromWindowInternal();
7606     }
7607 
7608     @Override
onScreenStateChanged(int screenState)7609     public void onScreenStateChanged(int screenState) {
7610         super.onScreenStateChanged(screenState);
7611         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
7612     }
7613 
7614     @Override
isPaddingOffsetRequired()7615     protected boolean isPaddingOffsetRequired() {
7616         return mShadowRadius != 0 || mDrawables != null;
7617     }
7618 
7619     @Override
getLeftPaddingOffset()7620     protected int getLeftPaddingOffset() {
7621         return getCompoundPaddingLeft() - mPaddingLeft
7622                 + (int) Math.min(0, mShadowDx - mShadowRadius);
7623     }
7624 
7625     @Override
getTopPaddingOffset()7626     protected int getTopPaddingOffset() {
7627         return (int) Math.min(0, mShadowDy - mShadowRadius);
7628     }
7629 
7630     @Override
getBottomPaddingOffset()7631     protected int getBottomPaddingOffset() {
7632         return (int) Math.max(0, mShadowDy + mShadowRadius);
7633     }
7634 
7635     @Override
getRightPaddingOffset()7636     protected int getRightPaddingOffset() {
7637         return -(getCompoundPaddingRight() - mPaddingRight)
7638                 + (int) Math.max(0, mShadowDx + mShadowRadius);
7639     }
7640 
7641     @Override
verifyDrawable(@onNull Drawable who)7642     protected boolean verifyDrawable(@NonNull Drawable who) {
7643         final boolean verified = super.verifyDrawable(who);
7644         if (!verified && mDrawables != null) {
7645             for (Drawable dr : mDrawables.mShowing) {
7646                 if (who == dr) {
7647                     return true;
7648                 }
7649             }
7650         }
7651         return verified;
7652     }
7653 
7654     @Override
jumpDrawablesToCurrentState()7655     public void jumpDrawablesToCurrentState() {
7656         super.jumpDrawablesToCurrentState();
7657         if (mDrawables != null) {
7658             for (Drawable dr : mDrawables.mShowing) {
7659                 if (dr != null) {
7660                     dr.jumpToCurrentState();
7661                 }
7662             }
7663         }
7664     }
7665 
7666     @Override
invalidateDrawable(@onNull Drawable drawable)7667     public void invalidateDrawable(@NonNull Drawable drawable) {
7668         boolean handled = false;
7669 
7670         if (verifyDrawable(drawable)) {
7671             final Rect dirty = drawable.getBounds();
7672             int scrollX = mScrollX;
7673             int scrollY = mScrollY;
7674 
7675             // IMPORTANT: The coordinates below are based on the coordinates computed
7676             // for each compound drawable in onDraw(). Make sure to update each section
7677             // accordingly.
7678             final TextView.Drawables drawables = mDrawables;
7679             if (drawables != null) {
7680                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
7681                     final int compoundPaddingTop = getCompoundPaddingTop();
7682                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7683                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7684 
7685                     scrollX += mPaddingLeft;
7686                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
7687                     handled = true;
7688                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
7689                     final int compoundPaddingTop = getCompoundPaddingTop();
7690                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7691                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7692 
7693                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
7694                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
7695                     handled = true;
7696                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
7697                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7698                     final int compoundPaddingRight = getCompoundPaddingRight();
7699                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7700 
7701                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
7702                     scrollY += mPaddingTop;
7703                     handled = true;
7704                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
7705                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7706                     final int compoundPaddingRight = getCompoundPaddingRight();
7707                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7708 
7709                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
7710                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
7711                     handled = true;
7712                 }
7713             }
7714 
7715             if (handled) {
7716                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
7717                         dirty.right + scrollX, dirty.bottom + scrollY);
7718             }
7719         }
7720 
7721         if (!handled) {
7722             super.invalidateDrawable(drawable);
7723         }
7724     }
7725 
7726     @Override
hasOverlappingRendering()7727     public boolean hasOverlappingRendering() {
7728         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
7729         return ((getBackground() != null && getBackground().getCurrent() != null)
7730                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
7731                 || mShadowColor != 0);
7732     }
7733 
7734     /**
7735      *
7736      * Returns the state of the {@code textIsSelectable} flag (See
7737      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
7738      * to allow users to select and copy text in a non-editable TextView, the content of an
7739      * {@link EditText} can always be selected, independently of the value of this flag.
7740      * <p>
7741      *
7742      * @return True if the text displayed in this TextView can be selected by the user.
7743      *
7744      * @attr ref android.R.styleable#TextView_textIsSelectable
7745      */
7746     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()7747     public boolean isTextSelectable() {
7748         return mEditor == null ? false : mEditor.mTextIsSelectable;
7749     }
7750 
7751     /**
7752      * Sets whether the content of this view is selectable by the user. The default is
7753      * {@code false}, meaning that the content is not selectable.
7754      * <p>
7755      * When you use a TextView to display a useful piece of information to the user (such as a
7756      * contact's address), make it selectable, so that the user can select and copy its
7757      * content. You can also use set the XML attribute
7758      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
7759      * <p>
7760      * When you call this method to set the value of {@code textIsSelectable}, it sets
7761      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
7762      * and {@code longClickable} to the same value. These flags correspond to the attributes
7763      * {@link android.R.styleable#View_focusable android:focusable},
7764      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
7765      * {@link android.R.styleable#View_clickable android:clickable}, and
7766      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
7767      * flags to a state you had set previously, call one or more of the following methods:
7768      * {@link #setFocusable(boolean) setFocusable()},
7769      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
7770      * {@link #setClickable(boolean) setClickable()} or
7771      * {@link #setLongClickable(boolean) setLongClickable()}.
7772      *
7773      * @param selectable Whether the content of this TextView should be selectable.
7774      */
setTextIsSelectable(boolean selectable)7775     public void setTextIsSelectable(boolean selectable) {
7776         if (!selectable && mEditor == null) return; // false is default value with no edit data
7777 
7778         createEditorIfNeeded();
7779         if (mEditor.mTextIsSelectable == selectable) return;
7780 
7781         mEditor.mTextIsSelectable = selectable;
7782         setFocusableInTouchMode(selectable);
7783         setFocusable(FOCUSABLE_AUTO);
7784         setClickable(selectable);
7785         setLongClickable(selectable);
7786 
7787         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
7788 
7789         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
7790         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
7791 
7792         // Called by setText above, but safer in case of future code changes
7793         mEditor.prepareCursorControllers();
7794     }
7795 
7796     @Override
onCreateDrawableState(int extraSpace)7797     protected int[] onCreateDrawableState(int extraSpace) {
7798         final int[] drawableState;
7799 
7800         if (mSingleLine) {
7801             drawableState = super.onCreateDrawableState(extraSpace);
7802         } else {
7803             drawableState = super.onCreateDrawableState(extraSpace + 1);
7804             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
7805         }
7806 
7807         if (isTextSelectable()) {
7808             // Disable pressed state, which was introduced when TextView was made clickable.
7809             // Prevents text color change.
7810             // setClickable(false) would have a similar effect, but it also disables focus changes
7811             // and long press actions, which are both needed by text selection.
7812             final int length = drawableState.length;
7813             for (int i = 0; i < length; i++) {
7814                 if (drawableState[i] == R.attr.state_pressed) {
7815                     final int[] nonPressedState = new int[length - 1];
7816                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
7817                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
7818                     return nonPressedState;
7819                 }
7820             }
7821         }
7822 
7823         return drawableState;
7824     }
7825 
7826     @UnsupportedAppUsage
getUpdatedHighlightPath()7827     private Path getUpdatedHighlightPath() {
7828         Path highlight = null;
7829         Paint highlightPaint = mHighlightPaint;
7830 
7831         final int selStart = getSelectionStart();
7832         final int selEnd = getSelectionEnd();
7833         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
7834             if (selStart == selEnd) {
7835                 if (mEditor != null && mEditor.shouldRenderCursor()) {
7836                     if (mHighlightPathBogus) {
7837                         if (mHighlightPath == null) mHighlightPath = new Path();
7838                         mHighlightPath.reset();
7839                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
7840                         mEditor.updateCursorPosition();
7841                         mHighlightPathBogus = false;
7842                     }
7843 
7844                     // XXX should pass to skin instead of drawing directly
7845                     highlightPaint.setColor(mCurTextColor);
7846                     highlightPaint.setStyle(Paint.Style.STROKE);
7847                     highlight = mHighlightPath;
7848                 }
7849             } else {
7850                 if (mHighlightPathBogus) {
7851                     if (mHighlightPath == null) mHighlightPath = new Path();
7852                     mHighlightPath.reset();
7853                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7854                     mHighlightPathBogus = false;
7855                 }
7856 
7857                 // XXX should pass to skin instead of drawing directly
7858                 highlightPaint.setColor(mHighlightColor);
7859                 highlightPaint.setStyle(Paint.Style.FILL);
7860 
7861                 highlight = mHighlightPath;
7862             }
7863         }
7864         return highlight;
7865     }
7866 
7867     /**
7868      * @hide
7869      */
getHorizontalOffsetForDrawables()7870     public int getHorizontalOffsetForDrawables() {
7871         return 0;
7872     }
7873 
7874     @Override
onDraw(Canvas canvas)7875     protected void onDraw(Canvas canvas) {
7876         restartMarqueeIfNeeded();
7877 
7878         // Draw the background for this view
7879         super.onDraw(canvas);
7880 
7881         final int compoundPaddingLeft = getCompoundPaddingLeft();
7882         final int compoundPaddingTop = getCompoundPaddingTop();
7883         final int compoundPaddingRight = getCompoundPaddingRight();
7884         final int compoundPaddingBottom = getCompoundPaddingBottom();
7885         final int scrollX = mScrollX;
7886         final int scrollY = mScrollY;
7887         final int right = mRight;
7888         final int left = mLeft;
7889         final int bottom = mBottom;
7890         final int top = mTop;
7891         final boolean isLayoutRtl = isLayoutRtl();
7892         final int offset = getHorizontalOffsetForDrawables();
7893         final int leftOffset = isLayoutRtl ? 0 : offset;
7894         final int rightOffset = isLayoutRtl ? offset : 0;
7895 
7896         final Drawables dr = mDrawables;
7897         if (dr != null) {
7898             /*
7899              * Compound, not extended, because the icon is not clipped
7900              * if the text height is smaller.
7901              */
7902 
7903             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
7904             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
7905 
7906             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7907             // Make sure to update invalidateDrawable() when changing this code.
7908             if (dr.mShowing[Drawables.LEFT] != null) {
7909                 canvas.save();
7910                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
7911                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
7912                 dr.mShowing[Drawables.LEFT].draw(canvas);
7913                 canvas.restore();
7914             }
7915 
7916             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7917             // Make sure to update invalidateDrawable() when changing this code.
7918             if (dr.mShowing[Drawables.RIGHT] != null) {
7919                 canvas.save();
7920                 canvas.translate(scrollX + right - left - mPaddingRight
7921                         - dr.mDrawableSizeRight - rightOffset,
7922                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
7923                 dr.mShowing[Drawables.RIGHT].draw(canvas);
7924                 canvas.restore();
7925             }
7926 
7927             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7928             // Make sure to update invalidateDrawable() when changing this code.
7929             if (dr.mShowing[Drawables.TOP] != null) {
7930                 canvas.save();
7931                 canvas.translate(scrollX + compoundPaddingLeft
7932                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
7933                 dr.mShowing[Drawables.TOP].draw(canvas);
7934                 canvas.restore();
7935             }
7936 
7937             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7938             // Make sure to update invalidateDrawable() when changing this code.
7939             if (dr.mShowing[Drawables.BOTTOM] != null) {
7940                 canvas.save();
7941                 canvas.translate(scrollX + compoundPaddingLeft
7942                         + (hspace - dr.mDrawableWidthBottom) / 2,
7943                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
7944                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
7945                 canvas.restore();
7946             }
7947         }
7948 
7949         int color = mCurTextColor;
7950 
7951         if (mLayout == null) {
7952             assumeLayout();
7953         }
7954 
7955         Layout layout = mLayout;
7956 
7957         if (mHint != null && mText.length() == 0) {
7958             if (mHintTextColor != null) {
7959                 color = mCurHintTextColor;
7960             }
7961 
7962             layout = mHintLayout;
7963         }
7964 
7965         mTextPaint.setColor(color);
7966         mTextPaint.drawableState = getDrawableState();
7967 
7968         canvas.save();
7969         /*  Would be faster if we didn't have to do this. Can we chop the
7970             (displayable) text so that we don't need to do this ever?
7971         */
7972 
7973         int extendedPaddingTop = getExtendedPaddingTop();
7974         int extendedPaddingBottom = getExtendedPaddingBottom();
7975 
7976         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7977         final int maxScrollY = mLayout.getHeight() - vspace;
7978 
7979         float clipLeft = compoundPaddingLeft + scrollX;
7980         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
7981         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
7982         float clipBottom = bottom - top + scrollY
7983                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
7984 
7985         if (mShadowRadius != 0) {
7986             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
7987             clipRight += Math.max(0, mShadowDx + mShadowRadius);
7988 
7989             clipTop += Math.min(0, mShadowDy - mShadowRadius);
7990             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
7991         }
7992 
7993         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
7994 
7995         int voffsetText = 0;
7996         int voffsetCursor = 0;
7997 
7998         // translate in by our padding
7999         /* shortcircuit calling getVerticaOffset() */
8000         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8001             voffsetText = getVerticalOffset(false);
8002             voffsetCursor = getVerticalOffset(true);
8003         }
8004         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
8005 
8006         final int layoutDirection = getLayoutDirection();
8007         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8008         if (isMarqueeFadeEnabled()) {
8009             if (!mSingleLine && getLineCount() == 1 && canMarquee()
8010                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
8011                 final int width = mRight - mLeft;
8012                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
8013                 final float dx = mLayout.getLineRight(0) - (width - padding);
8014                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8015             }
8016 
8017             if (mMarquee != null && mMarquee.isRunning()) {
8018                 final float dx = -mMarquee.getScroll();
8019                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8020             }
8021         }
8022 
8023         final int cursorOffsetVertical = voffsetCursor - voffsetText;
8024 
8025         Path highlight = getUpdatedHighlightPath();
8026         if (mEditor != null) {
8027             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
8028         } else {
8029             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
8030         }
8031 
8032         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
8033             final float dx = mMarquee.getGhostOffset();
8034             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8035             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
8036         }
8037 
8038         canvas.restore();
8039     }
8040 
8041     @Override
getFocusedRect(Rect r)8042     public void getFocusedRect(Rect r) {
8043         if (mLayout == null) {
8044             super.getFocusedRect(r);
8045             return;
8046         }
8047 
8048         int selEnd = getSelectionEnd();
8049         if (selEnd < 0) {
8050             super.getFocusedRect(r);
8051             return;
8052         }
8053 
8054         int selStart = getSelectionStart();
8055         if (selStart < 0 || selStart >= selEnd) {
8056             int line = mLayout.getLineForOffset(selEnd);
8057             r.top = mLayout.getLineTop(line);
8058             r.bottom = mLayout.getLineBottom(line);
8059             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
8060             r.right = r.left + 4;
8061         } else {
8062             int lineStart = mLayout.getLineForOffset(selStart);
8063             int lineEnd = mLayout.getLineForOffset(selEnd);
8064             r.top = mLayout.getLineTop(lineStart);
8065             r.bottom = mLayout.getLineBottom(lineEnd);
8066             if (lineStart == lineEnd) {
8067                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
8068                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
8069             } else {
8070                 // Selection extends across multiple lines -- make the focused
8071                 // rect cover the entire width.
8072                 if (mHighlightPathBogus) {
8073                     if (mHighlightPath == null) mHighlightPath = new Path();
8074                     mHighlightPath.reset();
8075                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8076                     mHighlightPathBogus = false;
8077                 }
8078                 synchronized (TEMP_RECTF) {
8079                     mHighlightPath.computeBounds(TEMP_RECTF, true);
8080                     r.left = (int) TEMP_RECTF.left - 1;
8081                     r.right = (int) TEMP_RECTF.right + 1;
8082                 }
8083             }
8084         }
8085 
8086         // Adjust for padding and gravity.
8087         int paddingLeft = getCompoundPaddingLeft();
8088         int paddingTop = getExtendedPaddingTop();
8089         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8090             paddingTop += getVerticalOffset(false);
8091         }
8092         r.offset(paddingLeft, paddingTop);
8093         int paddingBottom = getExtendedPaddingBottom();
8094         r.bottom += paddingBottom;
8095     }
8096 
8097     /**
8098      * Return the number of lines of text, or 0 if the internal Layout has not
8099      * been built.
8100      */
getLineCount()8101     public int getLineCount() {
8102         return mLayout != null ? mLayout.getLineCount() : 0;
8103     }
8104 
8105     /**
8106      * Return the baseline for the specified line (0...getLineCount() - 1)
8107      * If bounds is not null, return the top, left, right, bottom extents
8108      * of the specified line in it. If the internal Layout has not been built,
8109      * return 0 and set bounds to (0, 0, 0, 0)
8110      * @param line which line to examine (0..getLineCount() - 1)
8111      * @param bounds Optional. If not null, it returns the extent of the line
8112      * @return the Y-coordinate of the baseline
8113      */
getLineBounds(int line, Rect bounds)8114     public int getLineBounds(int line, Rect bounds) {
8115         if (mLayout == null) {
8116             if (bounds != null) {
8117                 bounds.set(0, 0, 0, 0);
8118             }
8119             return 0;
8120         } else {
8121             int baseline = mLayout.getLineBounds(line, bounds);
8122 
8123             int voffset = getExtendedPaddingTop();
8124             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8125                 voffset += getVerticalOffset(true);
8126             }
8127             if (bounds != null) {
8128                 bounds.offset(getCompoundPaddingLeft(), voffset);
8129             }
8130             return baseline + voffset;
8131         }
8132     }
8133 
8134     @Override
getBaseline()8135     public int getBaseline() {
8136         if (mLayout == null) {
8137             return super.getBaseline();
8138         }
8139 
8140         return getBaselineOffset() + mLayout.getLineBaseline(0);
8141     }
8142 
getBaselineOffset()8143     int getBaselineOffset() {
8144         int voffset = 0;
8145         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8146             voffset = getVerticalOffset(true);
8147         }
8148 
8149         if (isLayoutModeOptical(mParent)) {
8150             voffset -= getOpticalInsets().top;
8151         }
8152 
8153         return getExtendedPaddingTop() + voffset;
8154     }
8155 
8156     /**
8157      * @hide
8158      */
8159     @Override
getFadeTop(boolean offsetRequired)8160     protected int getFadeTop(boolean offsetRequired) {
8161         if (mLayout == null) return 0;
8162 
8163         int voffset = 0;
8164         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8165             voffset = getVerticalOffset(true);
8166         }
8167 
8168         if (offsetRequired) voffset += getTopPaddingOffset();
8169 
8170         return getExtendedPaddingTop() + voffset;
8171     }
8172 
8173     /**
8174      * @hide
8175      */
8176     @Override
getFadeHeight(boolean offsetRequired)8177     protected int getFadeHeight(boolean offsetRequired) {
8178         return mLayout != null ? mLayout.getHeight() : 0;
8179     }
8180 
8181     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)8182     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
8183         if (mSpannable != null && mLinksClickable) {
8184             final float x = event.getX(pointerIndex);
8185             final float y = event.getY(pointerIndex);
8186             final int offset = getOffsetForPosition(x, y);
8187             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
8188                     ClickableSpan.class);
8189             if (clickables.length > 0) {
8190                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
8191             }
8192         }
8193         if (isTextSelectable() || isTextEditable()) {
8194             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
8195         }
8196         return super.onResolvePointerIcon(event, pointerIndex);
8197     }
8198 
8199     @Override
onKeyPreIme(int keyCode, KeyEvent event)8200     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
8201         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
8202         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
8203         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
8204         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
8205             return true;
8206         }
8207         return super.onKeyPreIme(keyCode, event);
8208     }
8209 
8210     /**
8211      * @hide
8212      */
handleBackInTextActionModeIfNeeded(KeyEvent event)8213     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
8214         // Do nothing unless mEditor is in text action mode.
8215         if (mEditor == null || mEditor.getTextActionMode() == null) {
8216             return false;
8217         }
8218 
8219         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
8220             KeyEvent.DispatcherState state = getKeyDispatcherState();
8221             if (state != null) {
8222                 state.startTracking(event, this);
8223             }
8224             return true;
8225         } else if (event.getAction() == KeyEvent.ACTION_UP) {
8226             KeyEvent.DispatcherState state = getKeyDispatcherState();
8227             if (state != null) {
8228                 state.handleUpEvent(event);
8229             }
8230             if (event.isTracking() && !event.isCanceled()) {
8231                 stopTextActionMode();
8232                 return true;
8233             }
8234         }
8235         return false;
8236     }
8237 
8238     @Override
onKeyDown(int keyCode, KeyEvent event)8239     public boolean onKeyDown(int keyCode, KeyEvent event) {
8240         final int which = doKeyDown(keyCode, event, null);
8241         if (which == KEY_EVENT_NOT_HANDLED) {
8242             return super.onKeyDown(keyCode, event);
8243         }
8244 
8245         return true;
8246     }
8247 
8248     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8249     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
8250         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
8251         final int which = doKeyDown(keyCode, down, event);
8252         if (which == KEY_EVENT_NOT_HANDLED) {
8253             // Go through default dispatching.
8254             return super.onKeyMultiple(keyCode, repeatCount, event);
8255         }
8256         if (which == KEY_EVENT_HANDLED) {
8257             // Consumed the whole thing.
8258             return true;
8259         }
8260 
8261         repeatCount--;
8262 
8263         // We are going to dispatch the remaining events to either the input
8264         // or movement method.  To do this, we will just send a repeated stream
8265         // of down and up events until we have done the complete repeatCount.
8266         // It would be nice if those interfaces had an onKeyMultiple() method,
8267         // but adding that is a more complicated change.
8268         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
8269         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
8270             // mEditor and mEditor.mInput are not null from doKeyDown
8271             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8272             while (--repeatCount > 0) {
8273                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
8274                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8275             }
8276             hideErrorIfUnchanged();
8277 
8278         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
8279             // mMovement is not null from doKeyDown
8280             mMovement.onKeyUp(this, mSpannable, keyCode, up);
8281             while (--repeatCount > 0) {
8282                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
8283                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
8284             }
8285         }
8286 
8287         return true;
8288     }
8289 
8290     /**
8291      * Returns true if pressing ENTER in this field advances focus instead
8292      * of inserting the character.  This is true mostly in single-line fields,
8293      * but also in mail addresses and subjects which will display on multiple
8294      * lines but where it doesn't make sense to insert newlines.
8295      */
shouldAdvanceFocusOnEnter()8296     private boolean shouldAdvanceFocusOnEnter() {
8297         if (getKeyListener() == null) {
8298             return false;
8299         }
8300 
8301         if (mSingleLine) {
8302             return true;
8303         }
8304 
8305         if (mEditor != null
8306                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8307                         == EditorInfo.TYPE_CLASS_TEXT) {
8308             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8309             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
8310                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
8311                 return true;
8312             }
8313         }
8314 
8315         return false;
8316     }
8317 
isDirectionalNavigationKey(int keyCode)8318     private boolean isDirectionalNavigationKey(int keyCode) {
8319         switch(keyCode) {
8320             case KeyEvent.KEYCODE_DPAD_UP:
8321             case KeyEvent.KEYCODE_DPAD_DOWN:
8322             case KeyEvent.KEYCODE_DPAD_LEFT:
8323             case KeyEvent.KEYCODE_DPAD_RIGHT:
8324                 return true;
8325         }
8326         return false;
8327     }
8328 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8329     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
8330         if (!isEnabled()) {
8331             return KEY_EVENT_NOT_HANDLED;
8332         }
8333 
8334         // If this is the initial keydown, we don't want to prevent a movement away from this view.
8335         // While this shouldn't be necessary because any time we're preventing default movement we
8336         // should be restricting the focus to remain within this view, thus we'll also receive
8337         // the key up event, occasionally key up events will get dropped and we don't want to
8338         // prevent the user from traversing out of this on the next key down.
8339         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8340             mPreventDefaultMovement = false;
8341         }
8342 
8343         switch (keyCode) {
8344             case KeyEvent.KEYCODE_ENTER:
8345             case KeyEvent.KEYCODE_NUMPAD_ENTER:
8346                 if (event.hasNoModifiers()) {
8347                     // When mInputContentType is set, we know that we are
8348                     // running in a "modern" cupcake environment, so don't need
8349                     // to worry about the application trying to capture
8350                     // enter key events.
8351                     if (mEditor != null && mEditor.mInputContentType != null) {
8352                         // If there is an action listener, given them a
8353                         // chance to consume the event.
8354                         if (mEditor.mInputContentType.onEditorActionListener != null
8355                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8356                                         this, EditorInfo.IME_NULL, event)) {
8357                             mEditor.mInputContentType.enterDown = true;
8358                             // We are consuming the enter key for them.
8359                             return KEY_EVENT_HANDLED;
8360                         }
8361                     }
8362 
8363                     // If our editor should move focus when enter is pressed, or
8364                     // this is a generated event from an IME action button, then
8365                     // don't let it be inserted into the text.
8366                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8367                             || shouldAdvanceFocusOnEnter()) {
8368                         if (hasOnClickListeners()) {
8369                             return KEY_EVENT_NOT_HANDLED;
8370                         }
8371                         return KEY_EVENT_HANDLED;
8372                     }
8373                 }
8374                 break;
8375 
8376             case KeyEvent.KEYCODE_DPAD_CENTER:
8377                 if (event.hasNoModifiers()) {
8378                     if (shouldAdvanceFocusOnEnter()) {
8379                         return KEY_EVENT_NOT_HANDLED;
8380                     }
8381                 }
8382                 break;
8383 
8384             case KeyEvent.KEYCODE_TAB:
8385                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
8386                     // Tab is used to move focus.
8387                     return KEY_EVENT_NOT_HANDLED;
8388                 }
8389                 break;
8390 
8391                 // Has to be done on key down (and not on key up) to correctly be intercepted.
8392             case KeyEvent.KEYCODE_BACK:
8393                 if (mEditor != null && mEditor.getTextActionMode() != null) {
8394                     stopTextActionMode();
8395                     return KEY_EVENT_HANDLED;
8396                 }
8397                 break;
8398 
8399             case KeyEvent.KEYCODE_CUT:
8400                 if (event.hasNoModifiers() && canCut()) {
8401                     if (onTextContextMenuItem(ID_CUT)) {
8402                         return KEY_EVENT_HANDLED;
8403                     }
8404                 }
8405                 break;
8406 
8407             case KeyEvent.KEYCODE_COPY:
8408                 if (event.hasNoModifiers() && canCopy()) {
8409                     if (onTextContextMenuItem(ID_COPY)) {
8410                         return KEY_EVENT_HANDLED;
8411                     }
8412                 }
8413                 break;
8414 
8415             case KeyEvent.KEYCODE_PASTE:
8416                 if (event.hasNoModifiers() && canPaste()) {
8417                     if (onTextContextMenuItem(ID_PASTE)) {
8418                         return KEY_EVENT_HANDLED;
8419                     }
8420                 }
8421                 break;
8422 
8423             case KeyEvent.KEYCODE_FORWARD_DEL:
8424                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
8425                     if (onTextContextMenuItem(ID_CUT)) {
8426                         return KEY_EVENT_HANDLED;
8427                     }
8428                 }
8429                 break;
8430 
8431             case KeyEvent.KEYCODE_INSERT:
8432                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
8433                     if (onTextContextMenuItem(ID_COPY)) {
8434                         return KEY_EVENT_HANDLED;
8435                     }
8436                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
8437                     if (onTextContextMenuItem(ID_PASTE)) {
8438                         return KEY_EVENT_HANDLED;
8439                     }
8440                 }
8441                 break;
8442         }
8443 
8444         if (mEditor != null && mEditor.mKeyListener != null) {
8445             boolean doDown = true;
8446             if (otherEvent != null) {
8447                 try {
8448                     beginBatchEdit();
8449                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
8450                             otherEvent);
8451                     hideErrorIfUnchanged();
8452                     doDown = false;
8453                     if (handled) {
8454                         return KEY_EVENT_HANDLED;
8455                     }
8456                 } catch (AbstractMethodError e) {
8457                     // onKeyOther was added after 1.0, so if it isn't
8458                     // implemented we need to try to dispatch as a regular down.
8459                 } finally {
8460                     endBatchEdit();
8461                 }
8462             }
8463 
8464             if (doDown) {
8465                 beginBatchEdit();
8466                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
8467                         keyCode, event);
8468                 endBatchEdit();
8469                 hideErrorIfUnchanged();
8470                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
8471             }
8472         }
8473 
8474         // bug 650865: sometimes we get a key event before a layout.
8475         // don't try to move around if we don't know the layout.
8476 
8477         if (mMovement != null && mLayout != null) {
8478             boolean doDown = true;
8479             if (otherEvent != null) {
8480                 try {
8481                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
8482                     doDown = false;
8483                     if (handled) {
8484                         return KEY_EVENT_HANDLED;
8485                     }
8486                 } catch (AbstractMethodError e) {
8487                     // onKeyOther was added after 1.0, so if it isn't
8488                     // implemented we need to try to dispatch as a regular down.
8489                 }
8490             }
8491             if (doDown) {
8492                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
8493                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8494                         mPreventDefaultMovement = true;
8495                     }
8496                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
8497                 }
8498             }
8499             // Consume arrows from keyboard devices to prevent focus leaving the editor.
8500             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
8501             // to move focus with arrows.
8502             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
8503                     && isDirectionalNavigationKey(keyCode)) {
8504                 return KEY_EVENT_HANDLED;
8505             }
8506         }
8507 
8508         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
8509                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
8510     }
8511 
8512     /**
8513      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
8514      * can be recorded.
8515      * @hide
8516      */
resetErrorChangedFlag()8517     public void resetErrorChangedFlag() {
8518         /*
8519          * Keep track of what the error was before doing the input
8520          * so that if an input filter changed the error, we leave
8521          * that error showing.  Otherwise, we take down whatever
8522          * error was showing when the user types something.
8523          */
8524         if (mEditor != null) mEditor.mErrorWasChanged = false;
8525     }
8526 
8527     /**
8528      * @hide
8529      */
hideErrorIfUnchanged()8530     public void hideErrorIfUnchanged() {
8531         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
8532             setError(null, null);
8533         }
8534     }
8535 
8536     @Override
onKeyUp(int keyCode, KeyEvent event)8537     public boolean onKeyUp(int keyCode, KeyEvent event) {
8538         if (!isEnabled()) {
8539             return super.onKeyUp(keyCode, event);
8540         }
8541 
8542         if (!KeyEvent.isModifierKey(keyCode)) {
8543             mPreventDefaultMovement = false;
8544         }
8545 
8546         switch (keyCode) {
8547             case KeyEvent.KEYCODE_DPAD_CENTER:
8548                 if (event.hasNoModifiers()) {
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 show the soft
8554                      * input method.  (It will also
8555                      * call performClick(), but that won't do anything in
8556                      * this case.)
8557                      */
8558                     if (!hasOnClickListeners()) {
8559                         if (mMovement != null && mText instanceof Editable
8560                                 && mLayout != null && onCheckIsTextEditor()) {
8561                             InputMethodManager imm = getInputMethodManager();
8562                             viewClicked(imm);
8563                             if (imm != null && getShowSoftInputOnFocus()) {
8564                                 imm.showSoftInput(this, 0);
8565                             }
8566                         }
8567                     }
8568                 }
8569                 return super.onKeyUp(keyCode, event);
8570 
8571             case KeyEvent.KEYCODE_ENTER:
8572             case KeyEvent.KEYCODE_NUMPAD_ENTER:
8573                 if (event.hasNoModifiers()) {
8574                     if (mEditor != null && mEditor.mInputContentType != null
8575                             && mEditor.mInputContentType.onEditorActionListener != null
8576                             && mEditor.mInputContentType.enterDown) {
8577                         mEditor.mInputContentType.enterDown = false;
8578                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8579                                 this, EditorInfo.IME_NULL, event)) {
8580                             return true;
8581                         }
8582                     }
8583 
8584                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8585                             || shouldAdvanceFocusOnEnter()) {
8586                         /*
8587                          * If there is a click listener, just call through to
8588                          * super, which will invoke it.
8589                          *
8590                          * If there isn't a click listener, try to advance focus,
8591                          * but still call through to super, which will reset the
8592                          * pressed state and longpress state.  (It will also
8593                          * call performClick(), but that won't do anything in
8594                          * this case.)
8595                          */
8596                         if (!hasOnClickListeners()) {
8597                             View v = focusSearch(FOCUS_DOWN);
8598 
8599                             if (v != null) {
8600                                 if (!v.requestFocus(FOCUS_DOWN)) {
8601                                     throw new IllegalStateException("focus search returned a view "
8602                                             + "that wasn't able to take focus!");
8603                                 }
8604 
8605                                 /*
8606                                  * Return true because we handled the key; super
8607                                  * will return false because there was no click
8608                                  * listener.
8609                                  */
8610                                 super.onKeyUp(keyCode, event);
8611                                 return true;
8612                             } else if ((event.getFlags()
8613                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
8614                                 // No target for next focus, but make sure the IME
8615                                 // if this came from it.
8616                                 InputMethodManager imm = getInputMethodManager();
8617                                 if (imm != null && imm.isActive(this)) {
8618                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
8619                                 }
8620                             }
8621                         }
8622                     }
8623                     return super.onKeyUp(keyCode, event);
8624                 }
8625                 break;
8626         }
8627 
8628         if (mEditor != null && mEditor.mKeyListener != null) {
8629             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
8630                 return true;
8631             }
8632         }
8633 
8634         if (mMovement != null && mLayout != null) {
8635             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
8636                 return true;
8637             }
8638         }
8639 
8640         return super.onKeyUp(keyCode, event);
8641     }
8642 
8643     @Override
onCheckIsTextEditor()8644     public boolean onCheckIsTextEditor() {
8645         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
8646     }
8647 
8648     @Override
onCreateInputConnection(EditorInfo outAttrs)8649     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
8650         if (onCheckIsTextEditor() && isEnabled()) {
8651             mEditor.createInputMethodStateIfNeeded();
8652             outAttrs.inputType = getInputType();
8653             if (mEditor.mInputContentType != null) {
8654                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
8655                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
8656                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
8657                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
8658                 outAttrs.extras = mEditor.mInputContentType.extras;
8659                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
8660             } else {
8661                 outAttrs.imeOptions = EditorInfo.IME_NULL;
8662                 outAttrs.hintLocales = null;
8663             }
8664             if (focusSearch(FOCUS_DOWN) != null) {
8665                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
8666             }
8667             if (focusSearch(FOCUS_UP) != null) {
8668                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
8669             }
8670             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
8671                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
8672                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
8673                     // An action has not been set, but the enter key will move to
8674                     // the next focus, so set the action to that.
8675                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
8676                 } else {
8677                     // An action has not been set, and there is no focus to move
8678                     // to, so let's just supply a "done" action.
8679                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
8680                 }
8681                 if (!shouldAdvanceFocusOnEnter()) {
8682                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8683                 }
8684             }
8685             if (isMultilineInputType(outAttrs.inputType)) {
8686                 // Multi-line text editors should always show an enter key.
8687                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8688             }
8689             outAttrs.hintText = mHint;
8690             outAttrs.targetInputMethodUser = mTextOperationUser;
8691             if (mText instanceof Editable) {
8692                 InputConnection ic = new EditableInputConnection(this);
8693                 outAttrs.initialSelStart = getSelectionStart();
8694                 outAttrs.initialSelEnd = getSelectionEnd();
8695                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
8696                 outAttrs.setInitialSurroundingText(mText);
8697                 return ic;
8698             }
8699         }
8700         return null;
8701     }
8702 
8703     /**
8704      * If this TextView contains editable content, extract a portion of it
8705      * based on the information in <var>request</var> in to <var>outText</var>.
8706      * @return Returns true if the text was successfully extracted, else false.
8707      */
extractText(ExtractedTextRequest request, ExtractedText outText)8708     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
8709         createEditorIfNeeded();
8710         return mEditor.extractText(request, outText);
8711     }
8712 
8713     /**
8714      * This is used to remove all style-impacting spans from text before new
8715      * extracted text is being replaced into it, so that we don't have any
8716      * lingering spans applied during the replace.
8717      */
removeParcelableSpans(Spannable spannable, int start, int end)8718     static void removeParcelableSpans(Spannable spannable, int start, int end) {
8719         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
8720         int i = spans.length;
8721         while (i > 0) {
8722             i--;
8723             spannable.removeSpan(spans[i]);
8724         }
8725     }
8726 
8727     /**
8728      * Apply to this text view the given extracted text, as previously
8729      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
8730      */
setExtractedText(ExtractedText text)8731     public void setExtractedText(ExtractedText text) {
8732         Editable content = getEditableText();
8733         if (text.text != null) {
8734             if (content == null) {
8735                 setText(text.text, TextView.BufferType.EDITABLE);
8736             } else {
8737                 int start = 0;
8738                 int end = content.length();
8739 
8740                 if (text.partialStartOffset >= 0) {
8741                     final int N = content.length();
8742                     start = text.partialStartOffset;
8743                     if (start > N) start = N;
8744                     end = text.partialEndOffset;
8745                     if (end > N) end = N;
8746                 }
8747 
8748                 removeParcelableSpans(content, start, end);
8749                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
8750                     if (text.text instanceof Spanned) {
8751                         // OK to copy spans only.
8752                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
8753                                 Object.class, content, start);
8754                     }
8755                 } else {
8756                     content.replace(start, end, text.text);
8757                 }
8758             }
8759         }
8760 
8761         // Now set the selection position...  make sure it is in range, to
8762         // avoid crashes.  If this is a partial update, it is possible that
8763         // the underlying text may have changed, causing us problems here.
8764         // Also we just don't want to trust clients to do the right thing.
8765         Spannable sp = (Spannable) getText();
8766         final int N = sp.length();
8767         int start = text.selectionStart;
8768         if (start < 0) {
8769             start = 0;
8770         } else if (start > N) {
8771             start = N;
8772         }
8773         int end = text.selectionEnd;
8774         if (end < 0) {
8775             end = 0;
8776         } else if (end > N) {
8777             end = N;
8778         }
8779         Selection.setSelection(sp, start, end);
8780 
8781         // Finally, update the selection mode.
8782         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
8783             MetaKeyKeyListener.startSelecting(this, sp);
8784         } else {
8785             MetaKeyKeyListener.stopSelecting(this, sp);
8786         }
8787 
8788         setHintInternal(text.hint);
8789     }
8790 
8791     /**
8792      * @hide
8793      */
setExtracting(ExtractedTextRequest req)8794     public void setExtracting(ExtractedTextRequest req) {
8795         if (mEditor.mInputMethodState != null) {
8796             mEditor.mInputMethodState.mExtractedTextRequest = req;
8797         }
8798         // This would stop a possible selection mode, but no such mode is started in case
8799         // extracted mode will start. Some text is selected though, and will trigger an action mode
8800         // in the extracted view.
8801         mEditor.hideCursorAndSpanControllers();
8802         stopTextActionMode();
8803         if (mEditor.mSelectionModifierCursorController != null) {
8804             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
8805         }
8806     }
8807 
8808     /**
8809      * Called by the framework in response to a text completion from
8810      * the current input method, provided by it calling
8811      * {@link InputConnection#commitCompletion
8812      * InputConnection.commitCompletion()}.  The default implementation does
8813      * nothing; text views that are supporting auto-completion should override
8814      * this to do their desired behavior.
8815      *
8816      * @param text The auto complete text the user has selected.
8817      */
onCommitCompletion(CompletionInfo text)8818     public void onCommitCompletion(CompletionInfo text) {
8819         // intentionally empty
8820     }
8821 
8822     /**
8823      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
8824      * dictionary) from the current input method, provided by it calling
8825      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
8826      * The default implementation flashes the background of the corrected word to provide
8827      * feedback to the user.
8828      *
8829      * @param info The auto correct info about the text that was corrected.
8830      */
onCommitCorrection(CorrectionInfo info)8831     public void onCommitCorrection(CorrectionInfo info) {
8832         if (mEditor != null) mEditor.onCommitCorrection(info);
8833     }
8834 
beginBatchEdit()8835     public void beginBatchEdit() {
8836         if (mEditor != null) mEditor.beginBatchEdit();
8837     }
8838 
endBatchEdit()8839     public void endBatchEdit() {
8840         if (mEditor != null) mEditor.endBatchEdit();
8841     }
8842 
8843     /**
8844      * Called by the framework in response to a request to begin a batch
8845      * of edit operations through a call to link {@link #beginBatchEdit()}.
8846      */
onBeginBatchEdit()8847     public void onBeginBatchEdit() {
8848         // intentionally empty
8849     }
8850 
8851     /**
8852      * Called by the framework in response to a request to end a batch
8853      * of edit operations through a call to link {@link #endBatchEdit}.
8854      */
onEndBatchEdit()8855     public void onEndBatchEdit() {
8856         // intentionally empty
8857     }
8858 
8859     /**
8860      * Called by the framework in response to a private command from the
8861      * current method, provided by it calling
8862      * {@link InputConnection#performPrivateCommand
8863      * InputConnection.performPrivateCommand()}.
8864      *
8865      * @param action The action name of the command.
8866      * @param data Any additional data for the command.  This may be null.
8867      * @return Return true if you handled the command, else false.
8868      */
onPrivateIMECommand(String action, Bundle data)8869     public boolean onPrivateIMECommand(String action, Bundle data) {
8870         return false;
8871     }
8872 
8873     /** @hide */
8874     @VisibleForTesting
8875     @UnsupportedAppUsage
nullLayouts()8876     public void nullLayouts() {
8877         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
8878             mSavedLayout = (BoringLayout) mLayout;
8879         }
8880         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
8881             mSavedHintLayout = (BoringLayout) mHintLayout;
8882         }
8883 
8884         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
8885 
8886         mBoring = mHintBoring = null;
8887 
8888         // Since it depends on the value of mLayout
8889         if (mEditor != null) mEditor.prepareCursorControllers();
8890     }
8891 
8892     /**
8893      * Make a new Layout based on the already-measured size of the view,
8894      * on the assumption that it was measured correctly at some point.
8895      */
8896     @UnsupportedAppUsage
assumeLayout()8897     private void assumeLayout() {
8898         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8899 
8900         if (width < 1) {
8901             width = 0;
8902         }
8903 
8904         int physicalWidth = width;
8905 
8906         if (mHorizontallyScrolling) {
8907             width = VERY_WIDE;
8908         }
8909 
8910         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
8911                       physicalWidth, false);
8912     }
8913 
8914     @UnsupportedAppUsage
getLayoutAlignment()8915     private Layout.Alignment getLayoutAlignment() {
8916         Layout.Alignment alignment;
8917         switch (getTextAlignment()) {
8918             case TEXT_ALIGNMENT_GRAVITY:
8919                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
8920                     case Gravity.START:
8921                         alignment = Layout.Alignment.ALIGN_NORMAL;
8922                         break;
8923                     case Gravity.END:
8924                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
8925                         break;
8926                     case Gravity.LEFT:
8927                         alignment = Layout.Alignment.ALIGN_LEFT;
8928                         break;
8929                     case Gravity.RIGHT:
8930                         alignment = Layout.Alignment.ALIGN_RIGHT;
8931                         break;
8932                     case Gravity.CENTER_HORIZONTAL:
8933                         alignment = Layout.Alignment.ALIGN_CENTER;
8934                         break;
8935                     default:
8936                         alignment = Layout.Alignment.ALIGN_NORMAL;
8937                         break;
8938                 }
8939                 break;
8940             case TEXT_ALIGNMENT_TEXT_START:
8941                 alignment = Layout.Alignment.ALIGN_NORMAL;
8942                 break;
8943             case TEXT_ALIGNMENT_TEXT_END:
8944                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
8945                 break;
8946             case TEXT_ALIGNMENT_CENTER:
8947                 alignment = Layout.Alignment.ALIGN_CENTER;
8948                 break;
8949             case TEXT_ALIGNMENT_VIEW_START:
8950                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8951                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8952                 break;
8953             case TEXT_ALIGNMENT_VIEW_END:
8954                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8955                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8956                 break;
8957             case TEXT_ALIGNMENT_INHERIT:
8958                 // This should never happen as we have already resolved the text alignment
8959                 // but better safe than sorry so we just fall through
8960             default:
8961                 alignment = Layout.Alignment.ALIGN_NORMAL;
8962                 break;
8963         }
8964         return alignment;
8965     }
8966 
8967     /**
8968      * The width passed in is now the desired layout width,
8969      * not the full view width with padding.
8970      * {@hide}
8971      */
8972     @VisibleForTesting
8973     @UnsupportedAppUsage
makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8974     public void makeNewLayout(int wantWidth, int hintWidth,
8975                                  BoringLayout.Metrics boring,
8976                                  BoringLayout.Metrics hintBoring,
8977                                  int ellipsisWidth, boolean bringIntoView) {
8978         stopMarquee();
8979 
8980         // Update "old" cached values
8981         mOldMaximum = mMaximum;
8982         mOldMaxMode = mMaxMode;
8983 
8984         mHighlightPathBogus = true;
8985 
8986         if (wantWidth < 0) {
8987             wantWidth = 0;
8988         }
8989         if (hintWidth < 0) {
8990             hintWidth = 0;
8991         }
8992 
8993         Layout.Alignment alignment = getLayoutAlignment();
8994         final boolean testDirChange = mSingleLine && mLayout != null
8995                 && (alignment == Layout.Alignment.ALIGN_NORMAL
8996                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
8997         int oldDir = 0;
8998         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
8999         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
9000         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
9001                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
9002         TruncateAt effectiveEllipsize = mEllipsize;
9003         if (mEllipsize == TruncateAt.MARQUEE
9004                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
9005             effectiveEllipsize = TruncateAt.END_SMALL;
9006         }
9007 
9008         if (mTextDir == null) {
9009             mTextDir = getTextDirectionHeuristic();
9010         }
9011 
9012         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
9013                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
9014         if (switchEllipsize) {
9015             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
9016                     ? TruncateAt.END : TruncateAt.MARQUEE;
9017             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
9018                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
9019         }
9020 
9021         shouldEllipsize = mEllipsize != null;
9022         mHintLayout = null;
9023 
9024         if (mHint != null) {
9025             if (shouldEllipsize) hintWidth = wantWidth;
9026 
9027             if (hintBoring == UNKNOWN_BORING) {
9028                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
9029                                                    mHintBoring);
9030                 if (hintBoring != null) {
9031                     mHintBoring = hintBoring;
9032                 }
9033             }
9034 
9035             if (hintBoring != null) {
9036                 if (hintBoring.width <= hintWidth
9037                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
9038                     if (mSavedHintLayout != null) {
9039                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9040                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9041                                 hintBoring, mIncludePad);
9042                     } else {
9043                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9044                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9045                                 hintBoring, mIncludePad);
9046                     }
9047 
9048                     mSavedHintLayout = (BoringLayout) mHintLayout;
9049                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
9050                     if (mSavedHintLayout != null) {
9051                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9052                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9053                                 hintBoring, mIncludePad, mEllipsize,
9054                                 ellipsisWidth);
9055                     } else {
9056                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9057                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9058                                 hintBoring, mIncludePad, mEllipsize,
9059                                 ellipsisWidth);
9060                     }
9061                 }
9062             }
9063             // TODO: code duplication with makeSingleLayout()
9064             if (mHintLayout == null) {
9065                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
9066                         mHint.length(), mTextPaint, hintWidth)
9067                         .setAlignment(alignment)
9068                         .setTextDirection(mTextDir)
9069                         .setLineSpacing(mSpacingAdd, mSpacingMult)
9070                         .setIncludePad(mIncludePad)
9071                         .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9072                         .setBreakStrategy(mBreakStrategy)
9073                         .setHyphenationFrequency(mHyphenationFrequency)
9074                         .setJustificationMode(mJustificationMode)
9075                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9076                 if (shouldEllipsize) {
9077                     builder.setEllipsize(mEllipsize)
9078                             .setEllipsizedWidth(ellipsisWidth);
9079                 }
9080                 mHintLayout = builder.build();
9081             }
9082         }
9083 
9084         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
9085             registerForPreDraw();
9086         }
9087 
9088         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9089             if (!compressText(ellipsisWidth)) {
9090                 final int height = mLayoutParams.height;
9091                 // If the size of the view does not depend on the size of the text, try to
9092                 // start the marquee immediately
9093                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
9094                     startMarquee();
9095                 } else {
9096                     // Defer the start of the marquee until we know our width (see setFrame())
9097                     mRestartMarquee = true;
9098                 }
9099             }
9100         }
9101 
9102         // CursorControllers need a non-null mLayout
9103         if (mEditor != null) mEditor.prepareCursorControllers();
9104     }
9105 
9106     /**
9107      * Returns true if DynamicLayout is required
9108      *
9109      * @hide
9110      */
9111     @VisibleForTesting
useDynamicLayout()9112     public boolean useDynamicLayout() {
9113         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
9114     }
9115 
9116     /**
9117      * @hide
9118      */
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9119     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
9120             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
9121             boolean useSaved) {
9122         Layout result = null;
9123         if (useDynamicLayout()) {
9124             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
9125                     wantWidth)
9126                     .setDisplayText(mTransformed)
9127                     .setAlignment(alignment)
9128                     .setTextDirection(mTextDir)
9129                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9130                     .setIncludePad(mIncludePad)
9131                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9132                     .setBreakStrategy(mBreakStrategy)
9133                     .setHyphenationFrequency(mHyphenationFrequency)
9134                     .setJustificationMode(mJustificationMode)
9135                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
9136                     .setEllipsizedWidth(ellipsisWidth);
9137             result = builder.build();
9138         } else {
9139             if (boring == UNKNOWN_BORING) {
9140                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9141                 if (boring != null) {
9142                     mBoring = boring;
9143                 }
9144             }
9145 
9146             if (boring != null) {
9147                 if (boring.width <= wantWidth
9148                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
9149                     if (useSaved && mSavedLayout != null) {
9150                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9151                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9152                                 boring, mIncludePad);
9153                     } else {
9154                         result = BoringLayout.make(mTransformed, mTextPaint,
9155                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9156                                 boring, mIncludePad);
9157                     }
9158 
9159                     if (useSaved) {
9160                         mSavedLayout = (BoringLayout) result;
9161                     }
9162                 } else if (shouldEllipsize && boring.width <= wantWidth) {
9163                     if (useSaved && mSavedLayout != null) {
9164                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9165                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9166                                 boring, mIncludePad, effectiveEllipsize,
9167                                 ellipsisWidth);
9168                     } else {
9169                         result = BoringLayout.make(mTransformed, mTextPaint,
9170                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9171                                 boring, mIncludePad, effectiveEllipsize,
9172                                 ellipsisWidth);
9173                     }
9174                 }
9175             }
9176         }
9177         if (result == null) {
9178             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
9179                     0, mTransformed.length(), mTextPaint, wantWidth)
9180                     .setAlignment(alignment)
9181                     .setTextDirection(mTextDir)
9182                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9183                     .setIncludePad(mIncludePad)
9184                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9185                     .setBreakStrategy(mBreakStrategy)
9186                     .setHyphenationFrequency(mHyphenationFrequency)
9187                     .setJustificationMode(mJustificationMode)
9188                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9189             if (shouldEllipsize) {
9190                 builder.setEllipsize(effectiveEllipsize)
9191                         .setEllipsizedWidth(ellipsisWidth);
9192             }
9193             result = builder.build();
9194         }
9195         return result;
9196     }
9197 
9198     @UnsupportedAppUsage
compressText(float width)9199     private boolean compressText(float width) {
9200         if (isHardwareAccelerated()) return false;
9201 
9202         // Only compress the text if it hasn't been compressed by the previous pass
9203         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
9204                 && mTextPaint.getTextScaleX() == 1.0f) {
9205             final float textWidth = mLayout.getLineWidth(0);
9206             final float overflow = (textWidth + 1.0f - width) / width;
9207             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
9208                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
9209                 post(new Runnable() {
9210                     public void run() {
9211                         requestLayout();
9212                     }
9213                 });
9214                 return true;
9215             }
9216         }
9217 
9218         return false;
9219     }
9220 
desired(Layout layout)9221     private static int desired(Layout layout) {
9222         int n = layout.getLineCount();
9223         CharSequence text = layout.getText();
9224         float max = 0;
9225 
9226         // if any line was wrapped, we can't use it.
9227         // but it's ok for the last line not to have a newline
9228 
9229         for (int i = 0; i < n - 1; i++) {
9230             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
9231                 return -1;
9232             }
9233         }
9234 
9235         for (int i = 0; i < n; i++) {
9236             max = Math.max(max, layout.getLineWidth(i));
9237         }
9238 
9239         return (int) Math.ceil(max);
9240     }
9241 
9242     /**
9243      * Set whether the TextView includes extra top and bottom padding to make
9244      * room for accents that go above the normal ascent and descent.
9245      * The default is true.
9246      *
9247      * @see #getIncludeFontPadding()
9248      *
9249      * @attr ref android.R.styleable#TextView_includeFontPadding
9250      */
setIncludeFontPadding(boolean includepad)9251     public void setIncludeFontPadding(boolean includepad) {
9252         if (mIncludePad != includepad) {
9253             mIncludePad = includepad;
9254 
9255             if (mLayout != null) {
9256                 nullLayouts();
9257                 requestLayout();
9258                 invalidate();
9259             }
9260         }
9261     }
9262 
9263     /**
9264      * Gets whether the TextView includes extra top and bottom padding to make
9265      * room for accents that go above the normal ascent and descent.
9266      *
9267      * @see #setIncludeFontPadding(boolean)
9268      *
9269      * @attr ref android.R.styleable#TextView_includeFontPadding
9270      */
9271     @InspectableProperty
getIncludeFontPadding()9272     public boolean getIncludeFontPadding() {
9273         return mIncludePad;
9274     }
9275 
9276     /** @hide */
9277     @VisibleForTesting
9278     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
9279 
9280     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)9281     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
9282         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
9283         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
9284         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
9285         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
9286 
9287         int width;
9288         int height;
9289 
9290         BoringLayout.Metrics boring = UNKNOWN_BORING;
9291         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
9292 
9293         if (mTextDir == null) {
9294             mTextDir = getTextDirectionHeuristic();
9295         }
9296 
9297         int des = -1;
9298         boolean fromexisting = false;
9299         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
9300                 ?  (float) widthSize : Float.MAX_VALUE;
9301 
9302         if (widthMode == MeasureSpec.EXACTLY) {
9303             // Parent has told us how big to be. So be it.
9304             width = widthSize;
9305         } else {
9306             if (mLayout != null && mEllipsize == null) {
9307                 des = desired(mLayout);
9308             }
9309 
9310             if (des < 0) {
9311                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9312                 if (boring != null) {
9313                     mBoring = boring;
9314                 }
9315             } else {
9316                 fromexisting = true;
9317             }
9318 
9319             if (boring == null || boring == UNKNOWN_BORING) {
9320                 if (des < 0) {
9321                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
9322                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
9323                 }
9324                 width = des;
9325             } else {
9326                 width = boring.width;
9327             }
9328 
9329             final Drawables dr = mDrawables;
9330             if (dr != null) {
9331                 width = Math.max(width, dr.mDrawableWidthTop);
9332                 width = Math.max(width, dr.mDrawableWidthBottom);
9333             }
9334 
9335             if (mHint != null) {
9336                 int hintDes = -1;
9337                 int hintWidth;
9338 
9339                 if (mHintLayout != null && mEllipsize == null) {
9340                     hintDes = desired(mHintLayout);
9341                 }
9342 
9343                 if (hintDes < 0) {
9344                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
9345                     if (hintBoring != null) {
9346                         mHintBoring = hintBoring;
9347                     }
9348                 }
9349 
9350                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
9351                     if (hintDes < 0) {
9352                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
9353                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
9354                     }
9355                     hintWidth = hintDes;
9356                 } else {
9357                     hintWidth = hintBoring.width;
9358                 }
9359 
9360                 if (hintWidth > width) {
9361                     width = hintWidth;
9362                 }
9363             }
9364 
9365             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
9366 
9367             if (mMaxWidthMode == EMS) {
9368                 width = Math.min(width, mMaxWidth * getLineHeight());
9369             } else {
9370                 width = Math.min(width, mMaxWidth);
9371             }
9372 
9373             if (mMinWidthMode == EMS) {
9374                 width = Math.max(width, mMinWidth * getLineHeight());
9375             } else {
9376                 width = Math.max(width, mMinWidth);
9377             }
9378 
9379             // Check against our minimum width
9380             width = Math.max(width, getSuggestedMinimumWidth());
9381 
9382             if (widthMode == MeasureSpec.AT_MOST) {
9383                 width = Math.min(widthSize, width);
9384             }
9385         }
9386 
9387         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
9388         int unpaddedWidth = want;
9389 
9390         if (mHorizontallyScrolling) want = VERY_WIDE;
9391 
9392         int hintWant = want;
9393         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
9394 
9395         if (mLayout == null) {
9396             makeNewLayout(want, hintWant, boring, hintBoring,
9397                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9398         } else {
9399             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
9400                     || (mLayout.getEllipsizedWidth()
9401                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
9402 
9403             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
9404                     && (want > mLayout.getWidth())
9405                     && (mLayout instanceof BoringLayout
9406                             || (fromexisting && des >= 0 && des <= want));
9407 
9408             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
9409 
9410             if (layoutChanged || maximumChanged) {
9411                 if (!maximumChanged && widthChanged) {
9412                     mLayout.increaseWidthTo(want);
9413                 } else {
9414                     makeNewLayout(want, hintWant, boring, hintBoring,
9415                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9416                 }
9417             } else {
9418                 // Nothing has changed
9419             }
9420         }
9421 
9422         if (heightMode == MeasureSpec.EXACTLY) {
9423             // Parent has told us how big to be. So be it.
9424             height = heightSize;
9425             mDesiredHeightAtMeasure = -1;
9426         } else {
9427             int desired = getDesiredHeight();
9428 
9429             height = desired;
9430             mDesiredHeightAtMeasure = desired;
9431 
9432             if (heightMode == MeasureSpec.AT_MOST) {
9433                 height = Math.min(desired, heightSize);
9434             }
9435         }
9436 
9437         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
9438         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
9439             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
9440         }
9441 
9442         /*
9443          * We didn't let makeNewLayout() register to bring the cursor into view,
9444          * so do it here if there is any possibility that it is needed.
9445          */
9446         if (mMovement != null
9447                 || mLayout.getWidth() > unpaddedWidth
9448                 || mLayout.getHeight() > unpaddedHeight) {
9449             registerForPreDraw();
9450         } else {
9451             scrollTo(0, 0);
9452         }
9453 
9454         setMeasuredDimension(width, height);
9455     }
9456 
9457     /**
9458      * Automatically computes and sets the text size.
9459      */
autoSizeText()9460     private void autoSizeText() {
9461         if (!isAutoSizeEnabled()) {
9462             return;
9463         }
9464 
9465         if (mNeedsAutoSizeText) {
9466             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
9467                 return;
9468             }
9469 
9470             final int availableWidth = mHorizontallyScrolling
9471                     ? VERY_WIDE
9472                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
9473             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
9474                     - getExtendedPaddingTop();
9475 
9476             if (availableWidth <= 0 || availableHeight <= 0) {
9477                 return;
9478             }
9479 
9480             synchronized (TEMP_RECTF) {
9481                 TEMP_RECTF.setEmpty();
9482                 TEMP_RECTF.right = availableWidth;
9483                 TEMP_RECTF.bottom = availableHeight;
9484                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
9485 
9486                 if (optimalTextSize != getTextSize()) {
9487                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
9488                             false /* shouldRequestLayout */);
9489 
9490                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
9491                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9492                             false /* bringIntoView */);
9493                 }
9494             }
9495         }
9496         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
9497         // after the next layout pass should set this to false.
9498         mNeedsAutoSizeText = true;
9499     }
9500 
9501     /**
9502      * Performs a binary search to find the largest text size that will still fit within the size
9503      * available to this view.
9504      */
findLargestTextSizeWhichFits(RectF availableSpace)9505     private int findLargestTextSizeWhichFits(RectF availableSpace) {
9506         final int sizesCount = mAutoSizeTextSizesInPx.length;
9507         if (sizesCount == 0) {
9508             throw new IllegalStateException("No available text sizes to choose from.");
9509         }
9510 
9511         int bestSizeIndex = 0;
9512         int lowIndex = bestSizeIndex + 1;
9513         int highIndex = sizesCount - 1;
9514         int sizeToTryIndex;
9515         while (lowIndex <= highIndex) {
9516             sizeToTryIndex = (lowIndex + highIndex) / 2;
9517             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
9518                 bestSizeIndex = lowIndex;
9519                 lowIndex = sizeToTryIndex + 1;
9520             } else {
9521                 highIndex = sizeToTryIndex - 1;
9522                 bestSizeIndex = highIndex;
9523             }
9524         }
9525 
9526         return mAutoSizeTextSizesInPx[bestSizeIndex];
9527     }
9528 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9529     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
9530         final CharSequence text = mTransformed != null
9531                 ? mTransformed
9532                 : getText();
9533         final int maxLines = getMaxLines();
9534         if (mTempTextPaint == null) {
9535             mTempTextPaint = new TextPaint();
9536         } else {
9537             mTempTextPaint.reset();
9538         }
9539         mTempTextPaint.set(getPaint());
9540         mTempTextPaint.setTextSize(suggestedSizeInPx);
9541 
9542         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
9543                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
9544 
9545         layoutBuilder.setAlignment(getLayoutAlignment())
9546                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
9547                 .setIncludePad(getIncludeFontPadding())
9548                 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9549                 .setBreakStrategy(getBreakStrategy())
9550                 .setHyphenationFrequency(getHyphenationFrequency())
9551                 .setJustificationMode(getJustificationMode())
9552                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9553                 .setTextDirection(getTextDirectionHeuristic());
9554 
9555         final StaticLayout layout = layoutBuilder.build();
9556 
9557         // Lines overflow.
9558         if (maxLines != -1 && layout.getLineCount() > maxLines) {
9559             return false;
9560         }
9561 
9562         // Height overflow.
9563         if (layout.getHeight() > availableSpace.bottom) {
9564             return false;
9565         }
9566 
9567         return true;
9568     }
9569 
getDesiredHeight()9570     private int getDesiredHeight() {
9571         return Math.max(
9572                 getDesiredHeight(mLayout, true),
9573                 getDesiredHeight(mHintLayout, mEllipsize != null));
9574     }
9575 
getDesiredHeight(Layout layout, boolean cap)9576     private int getDesiredHeight(Layout layout, boolean cap) {
9577         if (layout == null) {
9578             return 0;
9579         }
9580 
9581         /*
9582         * Don't cap the hint to a certain number of lines.
9583         * (Do cap it, though, if we have a maximum pixel height.)
9584         */
9585         int desired = layout.getHeight(cap);
9586 
9587         final Drawables dr = mDrawables;
9588         if (dr != null) {
9589             desired = Math.max(desired, dr.mDrawableHeightLeft);
9590             desired = Math.max(desired, dr.mDrawableHeightRight);
9591         }
9592 
9593         int linecount = layout.getLineCount();
9594         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
9595         desired += padding;
9596 
9597         if (mMaxMode != LINES) {
9598             desired = Math.min(desired, mMaximum);
9599         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
9600                 || layout instanceof BoringLayout)) {
9601             desired = layout.getLineTop(mMaximum);
9602 
9603             if (dr != null) {
9604                 desired = Math.max(desired, dr.mDrawableHeightLeft);
9605                 desired = Math.max(desired, dr.mDrawableHeightRight);
9606             }
9607 
9608             desired += padding;
9609             linecount = mMaximum;
9610         }
9611 
9612         if (mMinMode == LINES) {
9613             if (linecount < mMinimum) {
9614                 desired += getLineHeight() * (mMinimum - linecount);
9615             }
9616         } else {
9617             desired = Math.max(desired, mMinimum);
9618         }
9619 
9620         // Check against our minimum height
9621         desired = Math.max(desired, getSuggestedMinimumHeight());
9622 
9623         return desired;
9624     }
9625 
9626     /**
9627      * Check whether a change to the existing text layout requires a
9628      * new view layout.
9629      */
checkForResize()9630     private void checkForResize() {
9631         boolean sizeChanged = false;
9632 
9633         if (mLayout != null) {
9634             // Check if our width changed
9635             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
9636                 sizeChanged = true;
9637                 invalidate();
9638             }
9639 
9640             // Check if our height changed
9641             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
9642                 int desiredHeight = getDesiredHeight();
9643 
9644                 if (desiredHeight != this.getHeight()) {
9645                     sizeChanged = true;
9646                 }
9647             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
9648                 if (mDesiredHeightAtMeasure >= 0) {
9649                     int desiredHeight = getDesiredHeight();
9650 
9651                     if (desiredHeight != mDesiredHeightAtMeasure) {
9652                         sizeChanged = true;
9653                     }
9654                 }
9655             }
9656         }
9657 
9658         if (sizeChanged) {
9659             requestLayout();
9660             // caller will have already invalidated
9661         }
9662     }
9663 
9664     /**
9665      * Check whether entirely new text requires a new view layout
9666      * or merely a new text layout.
9667      */
9668     @UnsupportedAppUsage
checkForRelayout()9669     private void checkForRelayout() {
9670         // If we have a fixed width, we can just swap in a new text layout
9671         // if the text height stays the same or if the view height is fixed.
9672 
9673         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
9674                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
9675                 && (mHint == null || mHintLayout != null)
9676                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
9677             // Static width, so try making a new text layout.
9678 
9679             int oldht = mLayout.getHeight();
9680             int want = mLayout.getWidth();
9681             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
9682 
9683             /*
9684              * No need to bring the text into view, since the size is not
9685              * changing (unless we do the requestLayout(), in which case it
9686              * will happen at measure).
9687              */
9688             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
9689                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9690                           false);
9691 
9692             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
9693                 // In a fixed-height view, so use our new text layout.
9694                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
9695                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
9696                     autoSizeText();
9697                     invalidate();
9698                     return;
9699                 }
9700 
9701                 // Dynamic height, but height has stayed the same,
9702                 // so use our new text layout.
9703                 if (mLayout.getHeight() == oldht
9704                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
9705                     autoSizeText();
9706                     invalidate();
9707                     return;
9708                 }
9709             }
9710 
9711             // We lose: the height has changed and we have a dynamic height.
9712             // Request a new view layout using our new text layout.
9713             requestLayout();
9714             invalidate();
9715         } else {
9716             // Dynamic width, so we have no choice but to request a new
9717             // view layout with a new text layout.
9718             nullLayouts();
9719             requestLayout();
9720             invalidate();
9721         }
9722     }
9723 
9724     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)9725     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
9726         super.onLayout(changed, left, top, right, bottom);
9727         if (mDeferScroll >= 0) {
9728             int curs = mDeferScroll;
9729             mDeferScroll = -1;
9730             bringPointIntoView(Math.min(curs, mText.length()));
9731         }
9732         // Call auto-size after the width and height have been calculated.
9733         autoSizeText();
9734     }
9735 
isShowingHint()9736     private boolean isShowingHint() {
9737         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
9738     }
9739 
9740     /**
9741      * Returns true if anything changed.
9742      */
9743     @UnsupportedAppUsage
bringTextIntoView()9744     private boolean bringTextIntoView() {
9745         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9746         int line = 0;
9747         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9748             line = layout.getLineCount() - 1;
9749         }
9750 
9751         Layout.Alignment a = layout.getParagraphAlignment(line);
9752         int dir = layout.getParagraphDirection(line);
9753         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9754         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9755         int ht = layout.getHeight();
9756 
9757         int scrollx, scrolly;
9758 
9759         // Convert to left, center, or right alignment.
9760         if (a == Layout.Alignment.ALIGN_NORMAL) {
9761             a = dir == Layout.DIR_LEFT_TO_RIGHT
9762                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
9763         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
9764             a = dir == Layout.DIR_LEFT_TO_RIGHT
9765                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
9766         }
9767 
9768         if (a == Layout.Alignment.ALIGN_CENTER) {
9769             /*
9770              * Keep centered if possible, or, if it is too wide to fit,
9771              * keep leading edge in view.
9772              */
9773 
9774             int left = (int) Math.floor(layout.getLineLeft(line));
9775             int right = (int) Math.ceil(layout.getLineRight(line));
9776 
9777             if (right - left < hspace) {
9778                 scrollx = (right + left) / 2 - hspace / 2;
9779             } else {
9780                 if (dir < 0) {
9781                     scrollx = right - hspace;
9782                 } else {
9783                     scrollx = left;
9784                 }
9785             }
9786         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
9787             int right = (int) Math.ceil(layout.getLineRight(line));
9788             scrollx = right - hspace;
9789         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
9790             scrollx = (int) Math.floor(layout.getLineLeft(line));
9791         }
9792 
9793         if (ht < vspace) {
9794             scrolly = 0;
9795         } else {
9796             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9797                 scrolly = ht - vspace;
9798             } else {
9799                 scrolly = 0;
9800             }
9801         }
9802 
9803         if (scrollx != mScrollX || scrolly != mScrollY) {
9804             scrollTo(scrollx, scrolly);
9805             return true;
9806         } else {
9807             return false;
9808         }
9809     }
9810 
9811     /**
9812      * Move the point, specified by the offset, into the view if it is needed.
9813      * This has to be called after layout. Returns true if anything changed.
9814      */
bringPointIntoView(int offset)9815     public boolean bringPointIntoView(int offset) {
9816         if (isLayoutRequested()) {
9817             mDeferScroll = offset;
9818             return false;
9819         }
9820         boolean changed = false;
9821 
9822         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9823 
9824         if (layout == null) return changed;
9825 
9826         int line = layout.getLineForOffset(offset);
9827 
9828         int grav;
9829 
9830         switch (layout.getParagraphAlignment(line)) {
9831             case ALIGN_LEFT:
9832                 grav = 1;
9833                 break;
9834             case ALIGN_RIGHT:
9835                 grav = -1;
9836                 break;
9837             case ALIGN_NORMAL:
9838                 grav = layout.getParagraphDirection(line);
9839                 break;
9840             case ALIGN_OPPOSITE:
9841                 grav = -layout.getParagraphDirection(line);
9842                 break;
9843             case ALIGN_CENTER:
9844             default:
9845                 grav = 0;
9846                 break;
9847         }
9848 
9849         // We only want to clamp the cursor to fit within the layout width
9850         // in left-to-right modes, because in a right to left alignment,
9851         // we want to scroll to keep the line-right on the screen, as other
9852         // lines are likely to have text flush with the right margin, which
9853         // we want to keep visible.
9854         // A better long-term solution would probably be to measure both
9855         // the full line and a blank-trimmed version, and, for example, use
9856         // the latter measurement for centering and right alignment, but for
9857         // the time being we only implement the cursor clamping in left to
9858         // right where it is most likely to be annoying.
9859         final boolean clamped = grav > 0;
9860         // FIXME: Is it okay to truncate this, or should we round?
9861         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
9862         final int top = layout.getLineTop(line);
9863         final int bottom = layout.getLineTop(line + 1);
9864 
9865         int left = (int) Math.floor(layout.getLineLeft(line));
9866         int right = (int) Math.ceil(layout.getLineRight(line));
9867         int ht = layout.getHeight();
9868 
9869         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9870         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9871         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
9872             // If cursor has been clamped, make sure we don't scroll.
9873             right = Math.max(x, left + hspace);
9874         }
9875 
9876         int hslack = (bottom - top) / 2;
9877         int vslack = hslack;
9878 
9879         if (vslack > vspace / 4) {
9880             vslack = vspace / 4;
9881         }
9882         if (hslack > hspace / 4) {
9883             hslack = hspace / 4;
9884         }
9885 
9886         int hs = mScrollX;
9887         int vs = mScrollY;
9888 
9889         if (top - vs < vslack) {
9890             vs = top - vslack;
9891         }
9892         if (bottom - vs > vspace - vslack) {
9893             vs = bottom - (vspace - vslack);
9894         }
9895         if (ht - vs < vspace) {
9896             vs = ht - vspace;
9897         }
9898         if (0 - vs > 0) {
9899             vs = 0;
9900         }
9901 
9902         if (grav != 0) {
9903             if (x - hs < hslack) {
9904                 hs = x - hslack;
9905             }
9906             if (x - hs > hspace - hslack) {
9907                 hs = x - (hspace - hslack);
9908             }
9909         }
9910 
9911         if (grav < 0) {
9912             if (left - hs > 0) {
9913                 hs = left;
9914             }
9915             if (right - hs < hspace) {
9916                 hs = right - hspace;
9917             }
9918         } else if (grav > 0) {
9919             if (right - hs < hspace) {
9920                 hs = right - hspace;
9921             }
9922             if (left - hs > 0) {
9923                 hs = left;
9924             }
9925         } else /* grav == 0 */ {
9926             if (right - left <= hspace) {
9927                 /*
9928                  * If the entire text fits, center it exactly.
9929                  */
9930                 hs = left - (hspace - (right - left)) / 2;
9931             } else if (x > right - hslack) {
9932                 /*
9933                  * If we are near the right edge, keep the right edge
9934                  * at the edge of the view.
9935                  */
9936                 hs = right - hspace;
9937             } else if (x < left + hslack) {
9938                 /*
9939                  * If we are near the left edge, keep the left edge
9940                  * at the edge of the view.
9941                  */
9942                 hs = left;
9943             } else if (left > hs) {
9944                 /*
9945                  * Is there whitespace visible at the left?  Fix it if so.
9946                  */
9947                 hs = left;
9948             } else if (right < hs + hspace) {
9949                 /*
9950                  * Is there whitespace visible at the right?  Fix it if so.
9951                  */
9952                 hs = right - hspace;
9953             } else {
9954                 /*
9955                  * Otherwise, float as needed.
9956                  */
9957                 if (x - hs < hslack) {
9958                     hs = x - hslack;
9959                 }
9960                 if (x - hs > hspace - hslack) {
9961                     hs = x - (hspace - hslack);
9962                 }
9963             }
9964         }
9965 
9966         if (hs != mScrollX || vs != mScrollY) {
9967             if (mScroller == null) {
9968                 scrollTo(hs, vs);
9969             } else {
9970                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
9971                 int dx = hs - mScrollX;
9972                 int dy = vs - mScrollY;
9973 
9974                 if (duration > ANIMATED_SCROLL_GAP) {
9975                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
9976                     awakenScrollBars(mScroller.getDuration());
9977                     invalidate();
9978                 } else {
9979                     if (!mScroller.isFinished()) {
9980                         mScroller.abortAnimation();
9981                     }
9982 
9983                     scrollBy(dx, dy);
9984                 }
9985 
9986                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
9987             }
9988 
9989             changed = true;
9990         }
9991 
9992         if (isFocused()) {
9993             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
9994             // requestRectangleOnScreen() is in terms of content coordinates.
9995 
9996             // The offsets here are to ensure the rectangle we are using is
9997             // within our view bounds, in case the cursor is on the far left
9998             // or right.  If it isn't withing the bounds, then this request
9999             // will be ignored.
10000             if (mTempRect == null) mTempRect = new Rect();
10001             mTempRect.set(x - 2, top, x + 2, bottom);
10002             getInterestingRect(mTempRect, line);
10003             mTempRect.offset(mScrollX, mScrollY);
10004 
10005             if (requestRectangleOnScreen(mTempRect)) {
10006                 changed = true;
10007             }
10008         }
10009 
10010         return changed;
10011     }
10012 
10013     /**
10014      * Move the cursor, if needed, so that it is at an offset that is visible
10015      * to the user.  This will not move the cursor if it represents more than
10016      * one character (a selection range).  This will only work if the
10017      * TextView contains spannable text; otherwise it will do nothing.
10018      *
10019      * @return True if the cursor was actually moved, false otherwise.
10020      */
moveCursorToVisibleOffset()10021     public boolean moveCursorToVisibleOffset() {
10022         if (!(mText instanceof Spannable)) {
10023             return false;
10024         }
10025         int start = getSelectionStart();
10026         int end = getSelectionEnd();
10027         if (start != end) {
10028             return false;
10029         }
10030 
10031         // First: make sure the line is visible on screen:
10032 
10033         int line = mLayout.getLineForOffset(start);
10034 
10035         final int top = mLayout.getLineTop(line);
10036         final int bottom = mLayout.getLineTop(line + 1);
10037         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10038         int vslack = (bottom - top) / 2;
10039         if (vslack > vspace / 4) {
10040             vslack = vspace / 4;
10041         }
10042         final int vs = mScrollY;
10043 
10044         if (top < (vs + vslack)) {
10045             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
10046         } else if (bottom > (vspace + vs - vslack)) {
10047             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
10048         }
10049 
10050         // Next: make sure the character is visible on screen:
10051 
10052         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10053         final int hs = mScrollX;
10054         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
10055         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
10056 
10057         // line might contain bidirectional text
10058         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
10059         final int highChar = leftChar > rightChar ? leftChar : rightChar;
10060 
10061         int newStart = start;
10062         if (newStart < lowChar) {
10063             newStart = lowChar;
10064         } else if (newStart > highChar) {
10065             newStart = highChar;
10066         }
10067 
10068         if (newStart != start) {
10069             Selection.setSelection(mSpannable, newStart);
10070             return true;
10071         }
10072 
10073         return false;
10074     }
10075 
10076     @Override
computeScroll()10077     public void computeScroll() {
10078         if (mScroller != null) {
10079             if (mScroller.computeScrollOffset()) {
10080                 mScrollX = mScroller.getCurrX();
10081                 mScrollY = mScroller.getCurrY();
10082                 invalidateParentCaches();
10083                 postInvalidate();  // So we draw again
10084             }
10085         }
10086     }
10087 
getInterestingRect(Rect r, int line)10088     private void getInterestingRect(Rect r, int line) {
10089         convertFromViewportToContentCoordinates(r);
10090 
10091         // Rectangle can can be expanded on first and last line to take
10092         // padding into account.
10093         // TODO Take left/right padding into account too?
10094         if (line == 0) r.top -= getExtendedPaddingTop();
10095         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
10096     }
10097 
convertFromViewportToContentCoordinates(Rect r)10098     private void convertFromViewportToContentCoordinates(Rect r) {
10099         final int horizontalOffset = viewportToContentHorizontalOffset();
10100         r.left += horizontalOffset;
10101         r.right += horizontalOffset;
10102 
10103         final int verticalOffset = viewportToContentVerticalOffset();
10104         r.top += verticalOffset;
10105         r.bottom += verticalOffset;
10106     }
10107 
viewportToContentHorizontalOffset()10108     int viewportToContentHorizontalOffset() {
10109         return getCompoundPaddingLeft() - mScrollX;
10110     }
10111 
10112     @UnsupportedAppUsage
viewportToContentVerticalOffset()10113     int viewportToContentVerticalOffset() {
10114         int offset = getExtendedPaddingTop() - mScrollY;
10115         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
10116             offset += getVerticalOffset(false);
10117         }
10118         return offset;
10119     }
10120 
10121     @Override
debug(int depth)10122     public void debug(int depth) {
10123         super.debug(depth);
10124 
10125         String output = debugIndent(depth);
10126         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
10127                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
10128                 + "} ";
10129 
10130         if (mText != null) {
10131 
10132             output += "mText=\"" + mText + "\" ";
10133             if (mLayout != null) {
10134                 output += "mLayout width=" + mLayout.getWidth()
10135                         + " height=" + mLayout.getHeight();
10136             }
10137         } else {
10138             output += "mText=NULL";
10139         }
10140         Log.d(VIEW_LOG_TAG, output);
10141     }
10142 
10143     /**
10144      * Convenience for {@link Selection#getSelectionStart}.
10145      */
10146     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()10147     public int getSelectionStart() {
10148         return Selection.getSelectionStart(getText());
10149     }
10150 
10151     /**
10152      * Convenience for {@link Selection#getSelectionEnd}.
10153      */
10154     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()10155     public int getSelectionEnd() {
10156         return Selection.getSelectionEnd(getText());
10157     }
10158 
10159     /**
10160      * Return true iff there is a selection of nonzero length inside this text view.
10161      */
hasSelection()10162     public boolean hasSelection() {
10163         final int selectionStart = getSelectionStart();
10164         final int selectionEnd = getSelectionEnd();
10165 
10166         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
10167     }
10168 
getSelectedText()10169     String getSelectedText() {
10170         if (!hasSelection()) {
10171             return null;
10172         }
10173 
10174         final int start = getSelectionStart();
10175         final int end = getSelectionEnd();
10176         return String.valueOf(
10177                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
10178     }
10179 
10180     /**
10181      * Sets the properties of this field (lines, horizontally scrolling,
10182      * transformation method) to be for a single-line input.
10183      *
10184      * @attr ref android.R.styleable#TextView_singleLine
10185      */
setSingleLine()10186     public void setSingleLine() {
10187         setSingleLine(true);
10188     }
10189 
10190     /**
10191      * Sets the properties of this field to transform input to ALL CAPS
10192      * display. This may use a "small caps" formatting if available.
10193      * This setting will be ignored if this field is editable or selectable.
10194      *
10195      * This call replaces the current transformation method. Disabling this
10196      * will not necessarily restore the previous behavior from before this
10197      * was enabled.
10198      *
10199      * @see #setTransformationMethod(TransformationMethod)
10200      * @attr ref android.R.styleable#TextView_textAllCaps
10201      */
setAllCaps(boolean allCaps)10202     public void setAllCaps(boolean allCaps) {
10203         if (allCaps) {
10204             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
10205         } else {
10206             setTransformationMethod(null);
10207         }
10208     }
10209 
10210     /**
10211      *
10212      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
10213      * @return Whether the current transformation method is for ALL CAPS.
10214      *
10215      * @see #setAllCaps(boolean)
10216      * @see #setTransformationMethod(TransformationMethod)
10217      */
10218     @InspectableProperty(name = "textAllCaps")
isAllCaps()10219     public boolean isAllCaps() {
10220         final TransformationMethod method = getTransformationMethod();
10221         return method != null && method instanceof AllCapsTransformationMethod;
10222     }
10223 
10224     /**
10225      * If true, sets the properties of this field (number of lines, horizontally scrolling,
10226      * transformation method) to be for a single-line input; if false, restores these to the default
10227      * conditions.
10228      *
10229      * Note that the default conditions are not necessarily those that were in effect prior this
10230      * method, and you may want to reset these properties to your custom values.
10231      *
10232      * @attr ref android.R.styleable#TextView_singleLine
10233      */
10234     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)10235     public void setSingleLine(boolean singleLine) {
10236         // Could be used, but may break backward compatibility.
10237         // if (mSingleLine == singleLine) return;
10238         setInputTypeSingleLine(singleLine);
10239         applySingleLine(singleLine, true, true);
10240     }
10241 
10242     /**
10243      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
10244      * @param singleLine
10245      */
setInputTypeSingleLine(boolean singleLine)10246     private void setInputTypeSingleLine(boolean singleLine) {
10247         if (mEditor != null
10248                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
10249                         == EditorInfo.TYPE_CLASS_TEXT) {
10250             if (singleLine) {
10251                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10252             } else {
10253                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10254             }
10255         }
10256     }
10257 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)10258     private void applySingleLine(boolean singleLine, boolean applyTransformation,
10259             boolean changeMaxLines) {
10260         mSingleLine = singleLine;
10261         if (singleLine) {
10262             setLines(1);
10263             setHorizontallyScrolling(true);
10264             if (applyTransformation) {
10265                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
10266             }
10267         } else {
10268             if (changeMaxLines) {
10269                 setMaxLines(Integer.MAX_VALUE);
10270             }
10271             setHorizontallyScrolling(false);
10272             if (applyTransformation) {
10273                 setTransformationMethod(null);
10274             }
10275         }
10276     }
10277 
10278     /**
10279      * Causes words in the text that are longer than the view's width
10280      * to be ellipsized instead of broken in the middle.  You may also
10281      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
10282      * to constrain the text to a single line.  Use <code>null</code>
10283      * to turn off ellipsizing.
10284      *
10285      * If {@link #setMaxLines} has been used to set two or more lines,
10286      * only {@link android.text.TextUtils.TruncateAt#END} and
10287      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
10288      * (other ellipsizing types will not do anything).
10289      *
10290      * @attr ref android.R.styleable#TextView_ellipsize
10291      */
setEllipsize(TextUtils.TruncateAt where)10292     public void setEllipsize(TextUtils.TruncateAt where) {
10293         // TruncateAt is an enum. != comparison is ok between these singleton objects.
10294         if (mEllipsize != where) {
10295             mEllipsize = where;
10296 
10297             if (mLayout != null) {
10298                 nullLayouts();
10299                 requestLayout();
10300                 invalidate();
10301             }
10302         }
10303     }
10304 
10305     /**
10306      * Sets how many times to repeat the marquee animation. Only applied if the
10307      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
10308      *
10309      * @see #getMarqueeRepeatLimit()
10310      *
10311      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10312      */
setMarqueeRepeatLimit(int marqueeLimit)10313     public void setMarqueeRepeatLimit(int marqueeLimit) {
10314         mMarqueeRepeatLimit = marqueeLimit;
10315     }
10316 
10317     /**
10318      * Gets the number of times the marquee animation is repeated. Only meaningful if the
10319      * TextView has marquee enabled.
10320      *
10321      * @return the number of times the marquee animation is repeated. -1 if the animation
10322      * repeats indefinitely
10323      *
10324      * @see #setMarqueeRepeatLimit(int)
10325      *
10326      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10327      */
10328     @InspectableProperty
getMarqueeRepeatLimit()10329     public int getMarqueeRepeatLimit() {
10330         return mMarqueeRepeatLimit;
10331     }
10332 
10333     /**
10334      * Returns where, if anywhere, words that are longer than the view
10335      * is wide should be ellipsized.
10336      */
10337     @InspectableProperty
10338     @ViewDebug.ExportedProperty
getEllipsize()10339     public TextUtils.TruncateAt getEllipsize() {
10340         return mEllipsize;
10341     }
10342 
10343     /**
10344      * Set the TextView so that when it takes focus, all the text is
10345      * selected.
10346      *
10347      * @attr ref android.R.styleable#TextView_selectAllOnFocus
10348      */
10349     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)10350     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
10351         createEditorIfNeeded();
10352         mEditor.mSelectAllOnFocus = selectAllOnFocus;
10353 
10354         if (selectAllOnFocus && !(mText instanceof Spannable)) {
10355             setText(mText, BufferType.SPANNABLE);
10356         }
10357     }
10358 
10359     /**
10360      * Set whether the cursor is visible. The default is true. Note that this property only
10361      * makes sense for editable TextView.
10362      *
10363      * @see #isCursorVisible()
10364      *
10365      * @attr ref android.R.styleable#TextView_cursorVisible
10366      */
10367     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)10368     public void setCursorVisible(boolean visible) {
10369         if (visible && mEditor == null) return; // visible is the default value with no edit data
10370         createEditorIfNeeded();
10371         if (mEditor.mCursorVisible != visible) {
10372             mEditor.mCursorVisible = visible;
10373             invalidate();
10374 
10375             mEditor.makeBlink();
10376 
10377             // InsertionPointCursorController depends on mCursorVisible
10378             mEditor.prepareCursorControllers();
10379         }
10380     }
10381 
10382     /**
10383      * @return whether or not the cursor is visible (assuming this TextView is editable)
10384      *
10385      * @see #setCursorVisible(boolean)
10386      *
10387      * @attr ref android.R.styleable#TextView_cursorVisible
10388      */
10389     @InspectableProperty
isCursorVisible()10390     public boolean isCursorVisible() {
10391         // true is the default value
10392         return mEditor == null ? true : mEditor.mCursorVisible;
10393     }
10394 
canMarquee()10395     private boolean canMarquee() {
10396         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10397         return width > 0 && (mLayout.getLineWidth(0) > width
10398                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
10399                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
10400     }
10401 
10402     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()10403     private void startMarquee() {
10404         // Do not ellipsize EditText
10405         if (getKeyListener() != null) return;
10406 
10407         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
10408             return;
10409         }
10410 
10411         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
10412                 && getLineCount() == 1 && canMarquee()) {
10413 
10414             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10415                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
10416                 final Layout tmp = mLayout;
10417                 mLayout = mSavedMarqueeModeLayout;
10418                 mSavedMarqueeModeLayout = tmp;
10419                 setHorizontalFadingEdgeEnabled(true);
10420                 requestLayout();
10421                 invalidate();
10422             }
10423 
10424             if (mMarquee == null) mMarquee = new Marquee(this);
10425             mMarquee.start(mMarqueeRepeatLimit);
10426         }
10427     }
10428 
stopMarquee()10429     private void stopMarquee() {
10430         if (mMarquee != null && !mMarquee.isStopped()) {
10431             mMarquee.stop();
10432         }
10433 
10434         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
10435             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10436             final Layout tmp = mSavedMarqueeModeLayout;
10437             mSavedMarqueeModeLayout = mLayout;
10438             mLayout = tmp;
10439             setHorizontalFadingEdgeEnabled(false);
10440             requestLayout();
10441             invalidate();
10442         }
10443     }
10444 
10445     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)10446     private void startStopMarquee(boolean start) {
10447         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10448             if (start) {
10449                 startMarquee();
10450             } else {
10451                 stopMarquee();
10452             }
10453         }
10454     }
10455 
10456     /**
10457      * This method is called when the text is changed, in case any subclasses
10458      * would like to know.
10459      *
10460      * Within <code>text</code>, the <code>lengthAfter</code> characters
10461      * beginning at <code>start</code> have just replaced old text that had
10462      * length <code>lengthBefore</code>. It is an error to attempt to make
10463      * changes to <code>text</code> from this callback.
10464      *
10465      * @param text The text the TextView is displaying
10466      * @param start The offset of the start of the range of the text that was
10467      * modified
10468      * @param lengthBefore The length of the former text that has been replaced
10469      * @param lengthAfter The length of the replacement modified text
10470      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10471     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
10472         // intentionally empty, template pattern method can be overridden by subclasses
10473     }
10474 
10475     /**
10476      * This method is called when the selection has changed, in case any
10477      * subclasses would like to know.
10478      * </p>
10479      * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs
10480      * the accessibility subsystem about the selection change.
10481      * </p>
10482      *
10483      * @param selStart The new selection start location.
10484      * @param selEnd The new selection end location.
10485      */
10486     @CallSuper
onSelectionChanged(int selStart, int selEnd)10487     protected void onSelectionChanged(int selStart, int selEnd) {
10488         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
10489     }
10490 
10491     /**
10492      * Adds a TextWatcher to the list of those whose methods are called
10493      * whenever this TextView's text changes.
10494      * <p>
10495      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
10496      * not called after {@link #setText} calls.  Now, doing {@link #setText}
10497      * if there are any text changed listeners forces the buffer type to
10498      * Editable if it would not otherwise be and does call this method.
10499      */
addTextChangedListener(TextWatcher watcher)10500     public void addTextChangedListener(TextWatcher watcher) {
10501         if (mListeners == null) {
10502             mListeners = new ArrayList<TextWatcher>();
10503         }
10504 
10505         mListeners.add(watcher);
10506     }
10507 
10508     /**
10509      * Removes the specified TextWatcher from the list of those whose
10510      * methods are called
10511      * whenever this TextView's text changes.
10512      */
removeTextChangedListener(TextWatcher watcher)10513     public void removeTextChangedListener(TextWatcher watcher) {
10514         if (mListeners != null) {
10515             int i = mListeners.indexOf(watcher);
10516 
10517             if (i >= 0) {
10518                 mListeners.remove(i);
10519             }
10520         }
10521     }
10522 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)10523     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
10524         if (mListeners != null) {
10525             final ArrayList<TextWatcher> list = mListeners;
10526             final int count = list.size();
10527             for (int i = 0; i < count; i++) {
10528                 list.get(i).beforeTextChanged(text, start, before, after);
10529             }
10530         }
10531 
10532         // The spans that are inside or intersect the modified region no longer make sense
10533         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
10534         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
10535     }
10536 
10537     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10538     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
10539         if (!(mText instanceof Editable)) return;
10540         Editable text = (Editable) mText;
10541 
10542         T[] spans = text.getSpans(start, end, type);
10543         final int length = spans.length;
10544         for (int i = 0; i < length; i++) {
10545             final int spanStart = text.getSpanStart(spans[i]);
10546             final int spanEnd = text.getSpanEnd(spans[i]);
10547             if (spanEnd == start || spanStart == end) break;
10548             text.removeSpan(spans[i]);
10549         }
10550     }
10551 
removeAdjacentSuggestionSpans(final int pos)10552     void removeAdjacentSuggestionSpans(final int pos) {
10553         if (!(mText instanceof Editable)) return;
10554         final Editable text = (Editable) mText;
10555 
10556         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
10557         final int length = spans.length;
10558         for (int i = 0; i < length; i++) {
10559             final int spanStart = text.getSpanStart(spans[i]);
10560             final int spanEnd = text.getSpanEnd(spans[i]);
10561             if (spanEnd == pos || spanStart == pos) {
10562                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
10563                     text.removeSpan(spans[i]);
10564                 }
10565             }
10566         }
10567     }
10568 
10569     /**
10570      * Not private so it can be called from an inner class without going
10571      * through a thunk.
10572      */
sendOnTextChanged(CharSequence text, int start, int before, int after)10573     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
10574         if (mListeners != null) {
10575             final ArrayList<TextWatcher> list = mListeners;
10576             final int count = list.size();
10577             for (int i = 0; i < count; i++) {
10578                 list.get(i).onTextChanged(text, start, before, after);
10579             }
10580         }
10581 
10582         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
10583     }
10584 
10585     /**
10586      * Not private so it can be called from an inner class without going
10587      * through a thunk.
10588      */
sendAfterTextChanged(Editable text)10589     void sendAfterTextChanged(Editable text) {
10590         if (mListeners != null) {
10591             final ArrayList<TextWatcher> list = mListeners;
10592             final int count = list.size();
10593             for (int i = 0; i < count; i++) {
10594                 list.get(i).afterTextChanged(text);
10595             }
10596         }
10597 
10598         notifyListeningManagersAfterTextChanged();
10599 
10600         hideErrorIfUnchanged();
10601     }
10602 
10603     /**
10604      * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
10605      * interested on text changes.
10606      */
notifyListeningManagersAfterTextChanged()10607     private void notifyListeningManagersAfterTextChanged() {
10608 
10609         // Autofill
10610         if (isAutofillable()) {
10611             // It is important to not check whether the view is important for autofill
10612             // since the user can trigger autofill manually on not important views.
10613             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10614             if (afm != null) {
10615                 if (android.view.autofill.Helper.sVerbose) {
10616                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
10617                 }
10618                 afm.notifyValueChanged(TextView.this);
10619             }
10620         }
10621 
10622         // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
10623         // of using isLaidout(), so it's not called in cases where it's laid out but a
10624         // notifyAppeared was not sent.
10625 
10626         // ContentCapture
10627         if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) {
10628             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
10629             if (cm != null && cm.isContentCaptureEnabled()) {
10630                 final ContentCaptureSession session = getContentCaptureSession();
10631                 if (session != null) {
10632                     // TODO(b/111276913): pass flags when edited by user / add CTS test
10633                     session.notifyViewTextChanged(getAutofillId(), getText());
10634                 }
10635             }
10636         }
10637     }
10638 
isAutofillable()10639     private boolean isAutofillable() {
10640         // It is important to not check whether the view is important for autofill
10641         // since the user can trigger autofill manually on not important views.
10642         return getAutofillType() != AUTOFILL_TYPE_NONE;
10643     }
10644 
updateAfterEdit()10645     void updateAfterEdit() {
10646         invalidate();
10647         int curs = getSelectionStart();
10648 
10649         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
10650             registerForPreDraw();
10651         }
10652 
10653         checkForResize();
10654 
10655         if (curs >= 0) {
10656             mHighlightPathBogus = true;
10657             if (mEditor != null) mEditor.makeBlink();
10658             bringPointIntoView(curs);
10659         }
10660     }
10661 
10662     /**
10663      * Not private so it can be called from an inner class without going
10664      * through a thunk.
10665      */
handleTextChanged(CharSequence buffer, int start, int before, int after)10666     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
10667         sLastCutCopyOrTextChangedTime = 0;
10668 
10669         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10670         if (ims == null || ims.mBatchEditNesting == 0) {
10671             updateAfterEdit();
10672         }
10673         if (ims != null) {
10674             ims.mContentChanged = true;
10675             if (ims.mChangedStart < 0) {
10676                 ims.mChangedStart = start;
10677                 ims.mChangedEnd = start + before;
10678             } else {
10679                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
10680                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
10681             }
10682             ims.mChangedDelta += after - before;
10683         }
10684         resetErrorChangedFlag();
10685         sendOnTextChanged(buffer, start, before, after);
10686         onTextChanged(buffer, start, before, after);
10687     }
10688 
10689     /**
10690      * Not private so it can be called from an inner class without going
10691      * through a thunk.
10692      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10693     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
10694         // XXX Make the start and end move together if this ends up
10695         // spending too much time invalidating.
10696 
10697         boolean selChanged = false;
10698         int newSelStart = -1, newSelEnd = -1;
10699 
10700         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10701 
10702         if (what == Selection.SELECTION_END) {
10703             selChanged = true;
10704             newSelEnd = newStart;
10705 
10706             if (oldStart >= 0 || newStart >= 0) {
10707                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
10708                 checkForResize();
10709                 registerForPreDraw();
10710                 if (mEditor != null) mEditor.makeBlink();
10711             }
10712         }
10713 
10714         if (what == Selection.SELECTION_START) {
10715             selChanged = true;
10716             newSelStart = newStart;
10717 
10718             if (oldStart >= 0 || newStart >= 0) {
10719                 int end = Selection.getSelectionEnd(buf);
10720                 invalidateCursor(end, oldStart, newStart);
10721             }
10722         }
10723 
10724         if (selChanged) {
10725             mHighlightPathBogus = true;
10726             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
10727 
10728             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
10729                 if (newSelStart < 0) {
10730                     newSelStart = Selection.getSelectionStart(buf);
10731                 }
10732                 if (newSelEnd < 0) {
10733                     newSelEnd = Selection.getSelectionEnd(buf);
10734                 }
10735 
10736                 if (mEditor != null) {
10737                     mEditor.refreshTextActionMode();
10738                     if (!hasSelection()
10739                             && mEditor.getTextActionMode() == null && hasTransientState()) {
10740                         // User generated selection has been removed.
10741                         setHasTransientState(false);
10742                     }
10743                 }
10744                 onSelectionChanged(newSelStart, newSelEnd);
10745             }
10746         }
10747 
10748         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
10749                 || what instanceof CharacterStyle) {
10750             if (ims == null || ims.mBatchEditNesting == 0) {
10751                 invalidate();
10752                 mHighlightPathBogus = true;
10753                 checkForResize();
10754             } else {
10755                 ims.mContentChanged = true;
10756             }
10757             if (mEditor != null) {
10758                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
10759                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
10760                 mEditor.invalidateHandlesAndActionMode();
10761             }
10762         }
10763 
10764         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
10765             mHighlightPathBogus = true;
10766             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
10767                 ims.mSelectionModeChanged = true;
10768             }
10769 
10770             if (Selection.getSelectionStart(buf) >= 0) {
10771                 if (ims == null || ims.mBatchEditNesting == 0) {
10772                     invalidateCursor();
10773                 } else {
10774                     ims.mCursorChanged = true;
10775                 }
10776             }
10777         }
10778 
10779         if (what instanceof ParcelableSpan) {
10780             // If this is a span that can be sent to a remote process,
10781             // the current extract editor would be interested in it.
10782             if (ims != null && ims.mExtractedTextRequest != null) {
10783                 if (ims.mBatchEditNesting != 0) {
10784                     if (oldStart >= 0) {
10785                         if (ims.mChangedStart > oldStart) {
10786                             ims.mChangedStart = oldStart;
10787                         }
10788                         if (ims.mChangedStart > oldEnd) {
10789                             ims.mChangedStart = oldEnd;
10790                         }
10791                     }
10792                     if (newStart >= 0) {
10793                         if (ims.mChangedStart > newStart) {
10794                             ims.mChangedStart = newStart;
10795                         }
10796                         if (ims.mChangedStart > newEnd) {
10797                             ims.mChangedStart = newEnd;
10798                         }
10799                     }
10800                 } else {
10801                     if (DEBUG_EXTRACT) {
10802                         Log.v(LOG_TAG, "Span change outside of batch: "
10803                                 + oldStart + "-" + oldEnd + ","
10804                                 + newStart + "-" + newEnd + " " + what);
10805                     }
10806                     ims.mContentChanged = true;
10807                 }
10808             }
10809         }
10810 
10811         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
10812                 && what instanceof SpellCheckSpan) {
10813             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
10814         }
10815     }
10816 
10817     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10818     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
10819         if (isTemporarilyDetached()) {
10820             // If we are temporarily in the detach state, then do nothing.
10821             super.onFocusChanged(focused, direction, previouslyFocusedRect);
10822             return;
10823         }
10824 
10825         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
10826 
10827         if (focused) {
10828             if (mSpannable != null) {
10829                 MetaKeyKeyListener.resetMetaState(mSpannable);
10830             }
10831         }
10832 
10833         startStopMarquee(focused);
10834 
10835         if (mTransformation != null) {
10836             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
10837         }
10838 
10839         super.onFocusChanged(focused, direction, previouslyFocusedRect);
10840     }
10841 
10842     @Override
onWindowFocusChanged(boolean hasWindowFocus)10843     public void onWindowFocusChanged(boolean hasWindowFocus) {
10844         super.onWindowFocusChanged(hasWindowFocus);
10845 
10846         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
10847 
10848         startStopMarquee(hasWindowFocus);
10849     }
10850 
10851     @Override
onVisibilityChanged(View changedView, int visibility)10852     protected void onVisibilityChanged(View changedView, int visibility) {
10853         super.onVisibilityChanged(changedView, visibility);
10854         if (mEditor != null && visibility != VISIBLE) {
10855             mEditor.hideCursorAndSpanControllers();
10856             stopTextActionMode();
10857         }
10858     }
10859 
10860     /**
10861      * Use {@link BaseInputConnection#removeComposingSpans
10862      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
10863      * state from this text view.
10864      */
clearComposingText()10865     public void clearComposingText() {
10866         if (mText instanceof Spannable) {
10867             BaseInputConnection.removeComposingSpans(mSpannable);
10868         }
10869     }
10870 
10871     @Override
setSelected(boolean selected)10872     public void setSelected(boolean selected) {
10873         boolean wasSelected = isSelected();
10874 
10875         super.setSelected(selected);
10876 
10877         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10878             if (selected) {
10879                 startMarquee();
10880             } else {
10881                 stopMarquee();
10882             }
10883         }
10884     }
10885 
10886     /**
10887      * Called from onTouchEvent() to prevent the touches by secondary fingers.
10888      * Dragging on handles can revise cursor/selection, so can dragging on the text view.
10889      * This method is a lock to avoid processing multiple fingers on both text view and handles.
10890      * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work.
10891      *
10892      * @param event The motion event that is being handled and carries the pointer info.
10893      * @param fromHandleView true if the event is delivered to selection handle or insertion
10894      * handle; false if this event is delivered to TextView.
10895      * @return Returns true to indicate that onTouchEvent() can continue processing the motion
10896      * event, otherwise false.
10897      *  - Always returns true for the first finger.
10898      *  - For secondary fingers, if the first or current finger is from TextView, returns false.
10899      *    This is to make touch mutually exclusive between the TextView and the handles, but
10900      *    not among the handles.
10901      */
isFromPrimePointer(MotionEvent event, boolean fromHandleView)10902     boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) {
10903         boolean res = true;
10904         if (mPrimePointerId == NO_POINTER_ID)  {
10905             mPrimePointerId = event.getPointerId(0);
10906             mIsPrimePointerFromHandleView = fromHandleView;
10907         } else if (mPrimePointerId != event.getPointerId(0)) {
10908             res = mIsPrimePointerFromHandleView && fromHandleView;
10909         }
10910         if (event.getActionMasked() == MotionEvent.ACTION_UP
10911             || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
10912             mPrimePointerId = -1;
10913         }
10914         return res;
10915     }
10916 
10917     @Override
onTouchEvent(MotionEvent event)10918     public boolean onTouchEvent(MotionEvent event) {
10919         if (DEBUG_CURSOR) {
10920             logCursor("onTouchEvent", "%d: %s (%f,%f)",
10921                     event.getSequenceNumber(),
10922                     MotionEvent.actionToString(event.getActionMasked()),
10923                     event.getX(), event.getY());
10924         }
10925         if (!isFromPrimePointer(event, false)) {
10926             return true;
10927         }
10928 
10929         final int action = event.getActionMasked();
10930         if (mEditor != null) {
10931             mEditor.onTouchEvent(event);
10932 
10933             if (mEditor.mInsertionPointCursorController != null
10934                     && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
10935                 return true;
10936             }
10937             if (mEditor.mSelectionModifierCursorController != null
10938                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
10939                 return true;
10940             }
10941         }
10942 
10943         final boolean superResult = super.onTouchEvent(event);
10944         if (DEBUG_CURSOR) {
10945             logCursor("onTouchEvent", "superResult=%s", superResult);
10946         }
10947 
10948         /*
10949          * Don't handle the release after a long press, because it will move the selection away from
10950          * whatever the menu action was trying to affect. If the long press should have triggered an
10951          * insertion action mode, we can now actually show it.
10952          */
10953         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
10954             mEditor.mDiscardNextActionUp = false;
10955             if (DEBUG_CURSOR) {
10956                 logCursor("onTouchEvent", "release after long press detected");
10957             }
10958             if (mEditor.mIsInsertionActionModeStartPending) {
10959                 mEditor.startInsertionActionMode();
10960                 mEditor.mIsInsertionActionModeStartPending = false;
10961             }
10962             return superResult;
10963         }
10964 
10965         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
10966                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
10967 
10968         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
10969                 && mText instanceof Spannable && mLayout != null) {
10970             boolean handled = false;
10971 
10972             if (mMovement != null) {
10973                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
10974             }
10975 
10976             final boolean textIsSelectable = isTextSelectable();
10977             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
10978                 // The LinkMovementMethod which should handle taps on links has not been installed
10979                 // on non editable text that support text selection.
10980                 // We reproduce its behavior here to open links for these.
10981                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
10982                     getSelectionEnd(), ClickableSpan.class);
10983 
10984                 if (links.length > 0) {
10985                     links[0].onClick(this);
10986                     handled = true;
10987                 }
10988             }
10989 
10990             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
10991                 // Show the IME, except when selecting in read-only text.
10992                 final InputMethodManager imm = getInputMethodManager();
10993                 viewClicked(imm);
10994                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10995                     imm.showSoftInput(this, 0);
10996                 }
10997 
10998                 // The above condition ensures that the mEditor is not null
10999                 mEditor.onTouchUpEvent(event);
11000 
11001                 handled = true;
11002             }
11003 
11004             if (handled) {
11005                 return true;
11006             }
11007         }
11008 
11009         return superResult;
11010     }
11011 
11012     @Override
onGenericMotionEvent(MotionEvent event)11013     public boolean onGenericMotionEvent(MotionEvent event) {
11014         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
11015             try {
11016                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
11017                     return true;
11018                 }
11019             } catch (AbstractMethodError ex) {
11020                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
11021                 // Ignore its absence in case third party applications implemented the
11022                 // interface directly.
11023             }
11024         }
11025         return super.onGenericMotionEvent(event);
11026     }
11027 
11028     @Override
onCreateContextMenu(ContextMenu menu)11029     protected void onCreateContextMenu(ContextMenu menu) {
11030         if (mEditor != null) {
11031             mEditor.onCreateContextMenu(menu);
11032         }
11033     }
11034 
11035     @Override
showContextMenu()11036     public boolean showContextMenu() {
11037         if (mEditor != null) {
11038             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
11039         }
11040         return super.showContextMenu();
11041     }
11042 
11043     @Override
showContextMenu(float x, float y)11044     public boolean showContextMenu(float x, float y) {
11045         if (mEditor != null) {
11046             mEditor.setContextMenuAnchor(x, y);
11047         }
11048         return super.showContextMenu(x, y);
11049     }
11050 
11051     /**
11052      * @return True iff this TextView contains a text that can be edited, or if this is
11053      * a selectable TextView.
11054      */
11055     @UnsupportedAppUsage
isTextEditable()11056     boolean isTextEditable() {
11057         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
11058     }
11059 
11060     /**
11061      * Returns true, only while processing a touch gesture, if the initial
11062      * touch down event caused focus to move to the text view and as a result
11063      * its selection changed.  Only valid while processing the touch gesture
11064      * of interest, in an editable text view.
11065      */
didTouchFocusSelect()11066     public boolean didTouchFocusSelect() {
11067         return mEditor != null && mEditor.mTouchFocusSelected;
11068     }
11069 
11070     @Override
cancelLongPress()11071     public void cancelLongPress() {
11072         super.cancelLongPress();
11073         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
11074     }
11075 
11076     @Override
onTrackballEvent(MotionEvent event)11077     public boolean onTrackballEvent(MotionEvent event) {
11078         if (mMovement != null && mSpannable != null && mLayout != null) {
11079             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
11080                 return true;
11081             }
11082         }
11083 
11084         return super.onTrackballEvent(event);
11085     }
11086 
11087     /**
11088      * Sets the Scroller used for producing a scrolling animation
11089      *
11090      * @param s A Scroller instance
11091      */
setScroller(Scroller s)11092     public void setScroller(Scroller s) {
11093         mScroller = s;
11094     }
11095 
11096     @Override
getLeftFadingEdgeStrength()11097     protected float getLeftFadingEdgeStrength() {
11098         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11099             final Marquee marquee = mMarquee;
11100             if (marquee.shouldDrawLeftFade()) {
11101                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
11102             } else {
11103                 return 0.0f;
11104             }
11105         } else if (getLineCount() == 1) {
11106             final float lineLeft = getLayout().getLineLeft(0);
11107             if (lineLeft > mScrollX) return 0.0f;
11108             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
11109         }
11110         return super.getLeftFadingEdgeStrength();
11111     }
11112 
11113     @Override
getRightFadingEdgeStrength()11114     protected float getRightFadingEdgeStrength() {
11115         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11116             final Marquee marquee = mMarquee;
11117             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
11118         } else if (getLineCount() == 1) {
11119             final float rightEdge = mScrollX +
11120                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
11121             final float lineRight = getLayout().getLineRight(0);
11122             if (lineRight < rightEdge) return 0.0f;
11123             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
11124         }
11125         return super.getRightFadingEdgeStrength();
11126     }
11127 
11128     /**
11129      * Calculates the fading edge strength as the ratio of the distance between two
11130      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
11131      * value for the distance calculation.
11132      *
11133      * @param position1 A horizontal position.
11134      * @param position2 A horizontal position.
11135      * @return Fading edge strength between [0.0f, 1.0f].
11136      */
11137     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)11138     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
11139         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
11140         if (horizontalFadingEdgeLength == 0) return 0.0f;
11141         final float diff = Math.abs(position1 - position2);
11142         if (diff > horizontalFadingEdgeLength) return 1.0f;
11143         return diff / horizontalFadingEdgeLength;
11144     }
11145 
isMarqueeFadeEnabled()11146     private boolean isMarqueeFadeEnabled() {
11147         return mEllipsize == TextUtils.TruncateAt.MARQUEE
11148                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
11149     }
11150 
11151     @Override
computeHorizontalScrollRange()11152     protected int computeHorizontalScrollRange() {
11153         if (mLayout != null) {
11154             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
11155                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
11156         }
11157 
11158         return super.computeHorizontalScrollRange();
11159     }
11160 
11161     @Override
computeVerticalScrollRange()11162     protected int computeVerticalScrollRange() {
11163         if (mLayout != null) {
11164             return mLayout.getHeight();
11165         }
11166         return super.computeVerticalScrollRange();
11167     }
11168 
11169     @Override
computeVerticalScrollExtent()11170     protected int computeVerticalScrollExtent() {
11171         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
11172     }
11173 
11174     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11175     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
11176         super.findViewsWithText(outViews, searched, flags);
11177         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
11178                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
11179             String searchedLowerCase = searched.toString().toLowerCase();
11180             String textLowerCase = mText.toString().toLowerCase();
11181             if (textLowerCase.contains(searchedLowerCase)) {
11182                 outViews.add(this);
11183             }
11184         }
11185     }
11186 
11187     /**
11188      * Type of the text buffer that defines the characteristics of the text such as static,
11189      * styleable, or editable.
11190      */
11191     public enum BufferType {
11192         NORMAL, SPANNABLE, EDITABLE
11193     }
11194 
11195     /**
11196      * Returns the TextView_textColor attribute from the TypedArray, if set, or
11197      * the TextAppearance_textColor from the TextView_textAppearance attribute,
11198      * if TextView_textColor was not set directly.
11199      *
11200      * @removed
11201      */
getTextColors(Context context, TypedArray attrs)11202     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
11203         if (attrs == null) {
11204             // Preserve behavior prior to removal of this API.
11205             throw new NullPointerException();
11206         }
11207 
11208         // It's not safe to use this method from apps. The parameter 'attrs'
11209         // must have been obtained using the TextView filter array which is not
11210         // available to the SDK. As such, we grab a default TypedArray with the
11211         // right filter instead here.
11212         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
11213         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
11214         if (colors == null) {
11215             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
11216             if (ap != 0) {
11217                 final TypedArray appearance = context.obtainStyledAttributes(
11218                         ap, R.styleable.TextAppearance);
11219                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
11220                 appearance.recycle();
11221             }
11222         }
11223         a.recycle();
11224 
11225         return colors;
11226     }
11227 
11228     /**
11229      * Returns the default color from the TextView_textColor attribute from the
11230      * AttributeSet, if set, or the default color from the
11231      * TextAppearance_textColor from the TextView_textAppearance attribute, if
11232      * TextView_textColor was not set directly.
11233      *
11234      * @removed
11235      */
getTextColor(Context context, TypedArray attrs, int def)11236     public static int getTextColor(Context context, TypedArray attrs, int def) {
11237         final ColorStateList colors = getTextColors(context, attrs);
11238         if (colors == null) {
11239             return def;
11240         } else {
11241             return colors.getDefaultColor();
11242         }
11243     }
11244 
11245     @Override
onKeyShortcut(int keyCode, KeyEvent event)11246     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
11247         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
11248             // Handle Ctrl-only shortcuts.
11249             switch (keyCode) {
11250                 case KeyEvent.KEYCODE_A:
11251                     if (canSelectText()) {
11252                         return onTextContextMenuItem(ID_SELECT_ALL);
11253                     }
11254                     break;
11255                 case KeyEvent.KEYCODE_Z:
11256                     if (canUndo()) {
11257                         return onTextContextMenuItem(ID_UNDO);
11258                     }
11259                     break;
11260                 case KeyEvent.KEYCODE_X:
11261                     if (canCut()) {
11262                         return onTextContextMenuItem(ID_CUT);
11263                     }
11264                     break;
11265                 case KeyEvent.KEYCODE_C:
11266                     if (canCopy()) {
11267                         return onTextContextMenuItem(ID_COPY);
11268                     }
11269                     break;
11270                 case KeyEvent.KEYCODE_V:
11271                     if (canPaste()) {
11272                         return onTextContextMenuItem(ID_PASTE);
11273                     }
11274                     break;
11275             }
11276         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
11277             // Handle Ctrl-Shift shortcuts.
11278             switch (keyCode) {
11279                 case KeyEvent.KEYCODE_Z:
11280                     if (canRedo()) {
11281                         return onTextContextMenuItem(ID_REDO);
11282                     }
11283                     break;
11284                 case KeyEvent.KEYCODE_V:
11285                     if (canPaste()) {
11286                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
11287                     }
11288             }
11289         }
11290         return super.onKeyShortcut(keyCode, event);
11291     }
11292 
11293     /**
11294      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
11295      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
11296      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
11297      * sufficient.
11298      */
canSelectText()11299     boolean canSelectText() {
11300         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
11301     }
11302 
11303     /**
11304      * Test based on the <i>intrinsic</i> charateristics of the TextView.
11305      * The text must be spannable and the movement method must allow for arbitary selection.
11306      *
11307      * See also {@link #canSelectText()}.
11308      */
textCanBeSelected()11309     boolean textCanBeSelected() {
11310         // prepareCursorController() relies on this method.
11311         // If you change this condition, make sure prepareCursorController is called anywhere
11312         // the value of this condition might be changed.
11313         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
11314         return isTextEditable()
11315                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
11316     }
11317 
11318     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)11319     private Locale getTextServicesLocale(boolean allowNullLocale) {
11320         // Start fetching the text services locale asynchronously.
11321         updateTextServicesLocaleAsync();
11322         // If !allowNullLocale and there is no cached text services locale, just return the default
11323         // locale.
11324         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
11325                 : mCurrentSpellCheckerLocaleCache;
11326     }
11327 
11328     /**
11329      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
11330      * this {@link TextView}.
11331      *
11332      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
11333      * other apps may need to set this so that the system can user right user's resources and
11334      * services such as input methods and spell checkers.</p>
11335      *
11336      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
11337      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
11338      * @hide
11339      */
11340     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)11341     public final void setTextOperationUser(@Nullable UserHandle user) {
11342         if (Objects.equals(mTextOperationUser, user)) {
11343             return;
11344         }
11345         if (user != null && !Process.myUserHandle().equals(user)) {
11346             // Just for preventing people from accidentally using this hidden API without
11347             // the required permission.  The same permission is also checked in the system server.
11348             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
11349                     != PackageManager.PERMISSION_GRANTED) {
11350                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
11351                         + " userId=" + user.getIdentifier()
11352                         + " callingUserId" + UserHandle.myUserId());
11353             }
11354         }
11355         mTextOperationUser = user;
11356         // Invalidate some resources
11357         mCurrentSpellCheckerLocaleCache = null;
11358         if (mEditor != null) {
11359             mEditor.onTextOperationUserChanged();
11360         }
11361     }
11362 
11363     @Nullable
getTextServicesManagerForUser()11364     final TextServicesManager getTextServicesManagerForUser() {
11365         return getServiceManagerForUser("android", TextServicesManager.class);
11366     }
11367 
11368     @Nullable
getClipboardManagerForUser()11369     final ClipboardManager getClipboardManagerForUser() {
11370         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
11371     }
11372 
11373     @Nullable
getTextClassificationManagerForUser()11374     final TextClassificationManager getTextClassificationManagerForUser() {
11375         return getServiceManagerForUser(
11376                 getContext().getPackageName(), TextClassificationManager.class);
11377     }
11378 
11379     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)11380     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
11381         if (mTextOperationUser == null) {
11382             return getContext().getSystemService(managerClazz);
11383         }
11384         try {
11385             Context context = getContext().createPackageContextAsUser(
11386                     packageName, 0 /* flags */, mTextOperationUser);
11387             return context.getSystemService(managerClazz);
11388         } catch (PackageManager.NameNotFoundException e) {
11389             return null;
11390         }
11391     }
11392 
11393     /**
11394      * Starts {@link Activity} as a text-operation user if it is specified with
11395      * {@link #setTextOperationUser(UserHandle)}.
11396      *
11397      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
11398      *
11399      * @param intent The description of the activity to start.
11400      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11401     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
11402         if (mTextOperationUser != null) {
11403             getContext().startActivityAsUser(intent, mTextOperationUser);
11404         } else {
11405             getContext().startActivity(intent);
11406         }
11407     }
11408 
11409     /**
11410      * This is a temporary method. Future versions may support multi-locale text.
11411      * Caveat: This method may not return the latest text services locale, but this should be
11412      * acceptable and it's more important to make this method asynchronous.
11413      *
11414      * @return The locale that should be used for a word iterator
11415      * in this TextView, based on the current spell checker settings,
11416      * the current IME's locale, or the system default locale.
11417      * Please note that a word iterator in this TextView is different from another word iterator
11418      * used by SpellChecker.java of TextView. This method should be used for the former.
11419      * @hide
11420      */
11421     // TODO: Support multi-locale
11422     // TODO: Update the text services locale immediately after the keyboard locale is switched
11423     // by catching intent of keyboard switch event
getTextServicesLocale()11424     public Locale getTextServicesLocale() {
11425         return getTextServicesLocale(false /* allowNullLocale */);
11426     }
11427 
11428     /**
11429      * @return {@code true} if this TextView is specialized for showing and interacting with the
11430      * extracted text in a full-screen input method.
11431      * @hide
11432      */
isInExtractedMode()11433     public boolean isInExtractedMode() {
11434         return false;
11435     }
11436 
11437     /**
11438      * @return {@code true} if this widget supports auto-sizing text and has been configured to
11439      * auto-size.
11440      */
isAutoSizeEnabled()11441     private boolean isAutoSizeEnabled() {
11442         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
11443     }
11444 
11445     /**
11446      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
11447      * @hide
11448      */
supportsAutoSizeText()11449     protected boolean supportsAutoSizeText() {
11450         return true;
11451     }
11452 
11453     /**
11454      * This is a temporary method. Future versions may support multi-locale text.
11455      * Caveat: This method may not return the latest spell checker locale, but this should be
11456      * acceptable and it's more important to make this method asynchronous.
11457      *
11458      * @return The locale that should be used for a spell checker in this TextView,
11459      * based on the current spell checker settings, the current IME's locale, or the system default
11460      * locale.
11461      * @hide
11462      */
getSpellCheckerLocale()11463     public Locale getSpellCheckerLocale() {
11464         return getTextServicesLocale(true /* allowNullLocale */);
11465     }
11466 
updateTextServicesLocaleAsync()11467     private void updateTextServicesLocaleAsync() {
11468         // AsyncTask.execute() uses a serial executor which means we don't have
11469         // to lock around updateTextServicesLocaleLocked() to prevent it from
11470         // being executed n times in parallel.
11471         AsyncTask.execute(new Runnable() {
11472             @Override
11473             public void run() {
11474                 updateTextServicesLocaleLocked();
11475             }
11476         });
11477     }
11478 
11479     @UnsupportedAppUsage
updateTextServicesLocaleLocked()11480     private void updateTextServicesLocaleLocked() {
11481         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
11482         if (textServicesManager == null) {
11483             return;
11484         }
11485         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
11486         final Locale locale;
11487         if (subtype != null) {
11488             locale = subtype.getLocaleObject();
11489         } else {
11490             locale = null;
11491         }
11492         mCurrentSpellCheckerLocaleCache = locale;
11493     }
11494 
onLocaleChanged()11495     void onLocaleChanged() {
11496         mEditor.onLocaleChanged();
11497     }
11498 
11499     /**
11500      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
11501      * Made available to achieve a consistent behavior.
11502      * @hide
11503      */
getWordIterator()11504     public WordIterator getWordIterator() {
11505         if (mEditor != null) {
11506             return mEditor.getWordIterator();
11507         } else {
11508             return null;
11509         }
11510     }
11511 
11512     /** @hide */
11513     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)11514     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
11515         super.onPopulateAccessibilityEventInternal(event);
11516 
11517         final CharSequence text = getTextForAccessibility();
11518         if (!TextUtils.isEmpty(text)) {
11519             event.getText().add(text);
11520         }
11521     }
11522 
11523     @Override
getAccessibilityClassName()11524     public CharSequence getAccessibilityClassName() {
11525         return TextView.class.getName();
11526     }
11527 
11528     /** @hide */
11529     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11530     protected void onProvideStructure(@NonNull ViewStructure structure,
11531             @ViewStructureType int viewFor, int flags) {
11532         super.onProvideStructure(structure, viewFor, flags);
11533 
11534         final boolean isPassword = hasPasswordTransformationMethod()
11535                 || isPasswordInputType(getInputType());
11536         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11537                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11538             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11539                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
11540             }
11541             if (mTextId != Resources.ID_NULL) {
11542                 try {
11543                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
11544                 } catch (Resources.NotFoundException e) {
11545                     if (android.view.autofill.Helper.sVerbose) {
11546                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
11547                                 + mTextId + ": " + e.getMessage());
11548                     }
11549                 }
11550             }
11551         }
11552 
11553         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11554                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11555             if (mLayout == null) {
11556                 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11557                     Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
11558                 }
11559                 assumeLayout();
11560             }
11561             Layout layout = mLayout;
11562             final int lineCount = layout.getLineCount();
11563             if (lineCount <= 1) {
11564                 // Simple case: this is a single line.
11565                 final CharSequence text = getText();
11566                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11567                     structure.setText(text);
11568                 } else {
11569                     structure.setText(text, getSelectionStart(), getSelectionEnd());
11570                 }
11571             } else {
11572                 // Complex case: multi-line, could be scrolled or within a scroll container
11573                 // so some lines are not visible.
11574                 final int[] tmpCords = new int[2];
11575                 getLocationInWindow(tmpCords);
11576                 final int topWindowLocation = tmpCords[1];
11577                 View root = this;
11578                 ViewParent viewParent = getParent();
11579                 while (viewParent instanceof View) {
11580                     root = (View) viewParent;
11581                     viewParent = root.getParent();
11582                 }
11583                 final int windowHeight = root.getHeight();
11584                 final int topLine;
11585                 final int bottomLine;
11586                 if (topWindowLocation >= 0) {
11587                     // The top of the view is fully within its window; start text at line 0.
11588                     topLine = getLineAtCoordinateUnclamped(0);
11589                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
11590                 } else {
11591                     // The top of hte window has scrolled off the top of the window; figure out
11592                     // the starting line for this.
11593                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
11594                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
11595                 }
11596                 // We want to return some contextual lines above/below the lines that are
11597                 // actually visible.
11598                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
11599                 if (expandedTopLine < 0) {
11600                     expandedTopLine = 0;
11601                 }
11602                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
11603                 if (expandedBottomLine >= lineCount) {
11604                     expandedBottomLine = lineCount - 1;
11605                 }
11606 
11607                 // Convert lines into character offsets.
11608                 int expandedTopChar = layout.getLineStart(expandedTopLine);
11609                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
11610 
11611                 // Take into account selection -- if there is a selection, we need to expand
11612                 // the text we are returning to include that selection.
11613                 final int selStart = getSelectionStart();
11614                 final int selEnd = getSelectionEnd();
11615                 if (selStart < selEnd) {
11616                     if (selStart < expandedTopChar) {
11617                         expandedTopChar = selStart;
11618                     }
11619                     if (selEnd > expandedBottomChar) {
11620                         expandedBottomChar = selEnd;
11621                     }
11622                 }
11623 
11624                 // Get the text and trim it to the range we are reporting.
11625                 CharSequence text = getText();
11626                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
11627                     text = text.subSequence(expandedTopChar, expandedBottomChar);
11628                 }
11629 
11630                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11631                     structure.setText(text);
11632                 } else {
11633                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
11634 
11635                     final int[] lineOffsets = new int[bottomLine - topLine + 1];
11636                     final int[] lineBaselines = new int[bottomLine - topLine + 1];
11637                     final int baselineOffset = getBaselineOffset();
11638                     for (int i = topLine; i <= bottomLine; i++) {
11639                         lineOffsets[i - topLine] = layout.getLineStart(i);
11640                         lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
11641                     }
11642                     structure.setTextLines(lineOffsets, lineBaselines);
11643                 }
11644             }
11645 
11646             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
11647                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11648                 // Extract style information that applies to the TextView as a whole.
11649                 int style = 0;
11650                 int typefaceStyle = getTypefaceStyle();
11651                 if ((typefaceStyle & Typeface.BOLD) != 0) {
11652                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11653                 }
11654                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
11655                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
11656                 }
11657 
11658                 // Global styles can also be set via TextView.setPaintFlags().
11659                 int paintFlags = mTextPaint.getFlags();
11660                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
11661                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11662                 }
11663                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
11664                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
11665                 }
11666                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
11667                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
11668                 }
11669 
11670                 // TextView does not have its own text background color. A background is either part
11671                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
11672                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
11673                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
11674             }
11675             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11676                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11677                 structure.setMinTextEms(getMinEms());
11678                 structure.setMaxTextEms(getMaxEms());
11679                 int maxLength = -1;
11680                 for (InputFilter filter: getFilters()) {
11681                     if (filter instanceof InputFilter.LengthFilter) {
11682                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
11683                         break;
11684                     }
11685                 }
11686                 structure.setMaxTextLength(maxLength);
11687             }
11688         }
11689         if (mHintId != Resources.ID_NULL) {
11690             try {
11691                 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId));
11692             } catch (Resources.NotFoundException e) {
11693                 if (android.view.autofill.Helper.sVerbose) {
11694                     Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id "
11695                             + mHintId + ": " + e.getMessage());
11696                 }
11697             }
11698         }
11699         structure.setHint(getHint());
11700         structure.setInputType(getInputType());
11701     }
11702 
canRequestAutofill()11703     boolean canRequestAutofill() {
11704         if (!isAutofillable()) {
11705             return false;
11706         }
11707         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11708         if (afm != null) {
11709             return afm.isEnabled();
11710         }
11711         return false;
11712     }
11713 
requestAutofill()11714     private void requestAutofill() {
11715         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11716         if (afm != null) {
11717             afm.requestAutofill(this);
11718         }
11719     }
11720 
11721     @Override
autofill(AutofillValue value)11722     public void autofill(AutofillValue value) {
11723         if (!value.isText() || !isTextEditable()) {
11724             Log.w(LOG_TAG, value + " could not be autofilled into " + this);
11725             return;
11726         }
11727 
11728         final CharSequence autofilledValue = value.getTextValue();
11729 
11730         // First autofill it...
11731         setText(autofilledValue, mBufferType, true, 0);
11732 
11733         // ...then move cursor to the end.
11734         final CharSequence text = getText();
11735         if ((text instanceof Spannable)) {
11736             Selection.setSelection((Spannable) text, text.length());
11737         }
11738     }
11739 
11740     @Override
getAutofillType()11741     public @AutofillType int getAutofillType() {
11742         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
11743     }
11744 
11745     /**
11746      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
11747      * {@code char}s if longer.
11748      *
11749      * @return current text, {@code null} if the text is not editable
11750      *
11751      * @see View#getAutofillValue()
11752      */
11753     @Override
11754     @Nullable
getAutofillValue()11755     public AutofillValue getAutofillValue() {
11756         if (isTextEditable()) {
11757             final CharSequence text = TextUtils.trimToParcelableSize(getText());
11758             return AutofillValue.forText(text);
11759         }
11760         return null;
11761     }
11762 
11763     /** @hide */
11764     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)11765     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
11766         super.onInitializeAccessibilityEventInternal(event);
11767 
11768         final boolean isPassword = hasPasswordTransformationMethod();
11769         event.setPassword(isPassword);
11770 
11771         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
11772             event.setFromIndex(Selection.getSelectionStart(mText));
11773             event.setToIndex(Selection.getSelectionEnd(mText));
11774             event.setItemCount(mText.length());
11775         }
11776     }
11777 
11778     /** @hide */
11779     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11780     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
11781         super.onInitializeAccessibilityNodeInfoInternal(info);
11782 
11783         final boolean isPassword = hasPasswordTransformationMethod();
11784         info.setPassword(isPassword);
11785         info.setText(getTextForAccessibility());
11786         info.setHintText(mHint);
11787         info.setShowingHintText(isShowingHint());
11788 
11789         if (mBufferType == BufferType.EDITABLE) {
11790             info.setEditable(true);
11791             if (isEnabled()) {
11792                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
11793             }
11794         }
11795 
11796         if (mEditor != null) {
11797             info.setInputType(mEditor.mInputType);
11798 
11799             if (mEditor.mError != null) {
11800                 info.setContentInvalid(true);
11801                 info.setError(mEditor.mError);
11802             }
11803             // TextView will expose this action if it is editable and has focus.
11804             if (isTextEditable() && isFocused()) {
11805                 CharSequence imeActionLabel = mContext.getResources().getString(
11806                         com.android.internal.R.string.keyboardview_keycode_enter);
11807                 if (getImeActionLabel() != null) {
11808                     imeActionLabel = getImeActionLabel();
11809                 }
11810                 AccessibilityNodeInfo.AccessibilityAction action =
11811                         new AccessibilityNodeInfo.AccessibilityAction(
11812                                 R.id.accessibilityActionImeEnter, imeActionLabel);
11813                 info.addAction(action);
11814             }
11815         }
11816 
11817         if (!TextUtils.isEmpty(mText)) {
11818             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
11819             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
11820             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
11821                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
11822                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
11823                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
11824                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
11825             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
11826             info.setAvailableExtraData(Arrays.asList(
11827                     EXTRA_DATA_RENDERING_INFO_KEY,
11828                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
11829             ));
11830         } else {
11831             info.setAvailableExtraData(Arrays.asList(
11832                     EXTRA_DATA_RENDERING_INFO_KEY
11833             ));
11834         }
11835 
11836         if (isFocused()) {
11837             if (canCopy()) {
11838                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
11839             }
11840             if (canPaste()) {
11841                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
11842             }
11843             if (canCut()) {
11844                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
11845             }
11846             if (canShare()) {
11847                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
11848                         ACCESSIBILITY_ACTION_SHARE,
11849                         getResources().getString(com.android.internal.R.string.share)));
11850             }
11851             if (canProcessText()) {  // also implies mEditor is not null.
11852                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
11853             }
11854         }
11855 
11856         // Check for known input filter types.
11857         final int numFilters = mFilters.length;
11858         for (int i = 0; i < numFilters; i++) {
11859             final InputFilter filter = mFilters[i];
11860             if (filter instanceof InputFilter.LengthFilter) {
11861                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
11862             }
11863         }
11864 
11865         if (!isSingleLine()) {
11866             info.setMultiLine(true);
11867         }
11868 
11869         // A view should not be exposed as clickable/long-clickable to a service because of a
11870         // LinkMovementMethod.
11871         if ((info.isClickable() || info.isLongClickable())
11872                 && mMovement instanceof LinkMovementMethod) {
11873             if (!hasOnClickListeners()) {
11874                 info.setClickable(false);
11875                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
11876             }
11877             if (!hasOnLongClickListeners()) {
11878                 info.setLongClickable(false);
11879                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
11880             }
11881         }
11882     }
11883 
11884     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11885     public void addExtraDataToAccessibilityNodeInfo(
11886             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
11887         if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
11888             int positionInfoStartIndex = arguments.getInt(
11889                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
11890             int positionInfoLength = arguments.getInt(
11891                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
11892             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
11893                     || (positionInfoStartIndex >= mText.length())) {
11894                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
11895                 return;
11896             }
11897             RectF[] boundingRects = new RectF[positionInfoLength];
11898             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
11899             populateCharacterBounds(builder, positionInfoStartIndex,
11900                     positionInfoStartIndex + positionInfoLength,
11901                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
11902             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
11903             for (int i = 0; i < positionInfoLength; i++) {
11904                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
11905                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
11906                     RectF bounds = cursorAnchorInfo
11907                             .getCharacterBounds(positionInfoStartIndex + i);
11908                     if (bounds != null) {
11909                         mapRectFromViewToScreenCoords(bounds, true);
11910                         boundingRects[i] = bounds;
11911                     }
11912                 }
11913             }
11914             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
11915             return;
11916         }
11917         if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) {
11918             final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo =
11919                     AccessibilityNodeInfo.ExtraRenderingInfo.obtain();
11920             extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height);
11921             extraRenderingInfo.setTextSizeInPx(getTextSize());
11922             extraRenderingInfo.setTextSizeUnit(getTextSizeUnit());
11923             info.setExtraRenderingInfo(extraRenderingInfo);
11924         }
11925     }
11926 
11927     /**
11928      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
11929      *
11930      * @param builder The builder to populate
11931      * @param startIndex The starting character index to populate
11932      * @param endIndex The ending character index to populate
11933      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
11934      * content
11935      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
11936      * @hide
11937      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)11938     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
11939             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
11940             float viewportToContentVerticalOffset) {
11941         final int minLine = mLayout.getLineForOffset(startIndex);
11942         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
11943         for (int line = minLine; line <= maxLine; ++line) {
11944             final int lineStart = mLayout.getLineStart(line);
11945             final int lineEnd = mLayout.getLineEnd(line);
11946             final int offsetStart = Math.max(lineStart, startIndex);
11947             final int offsetEnd = Math.min(lineEnd, endIndex);
11948             final boolean ltrLine =
11949                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
11950             final float[] widths = new float[offsetEnd - offsetStart];
11951             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
11952             final float top = mLayout.getLineTop(line);
11953             final float bottom = mLayout.getLineBottom(line);
11954             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
11955                 final float charWidth = widths[offset - offsetStart];
11956                 final boolean isRtl = mLayout.isRtlCharAt(offset);
11957                 final float primary = mLayout.getPrimaryHorizontal(offset);
11958                 final float secondary = mLayout.getSecondaryHorizontal(offset);
11959                 // TODO: This doesn't work perfectly for text with custom styles and
11960                 // TAB chars.
11961                 final float left;
11962                 final float right;
11963                 if (ltrLine) {
11964                     if (isRtl) {
11965                         left = secondary - charWidth;
11966                         right = secondary;
11967                     } else {
11968                         left = primary;
11969                         right = primary + charWidth;
11970                     }
11971                 } else {
11972                     if (!isRtl) {
11973                         left = secondary;
11974                         right = secondary + charWidth;
11975                     } else {
11976                         left = primary - charWidth;
11977                         right = primary;
11978                     }
11979                 }
11980                 // TODO: Check top-right and bottom-left as well.
11981                 final float localLeft = left + viewportToContentHorizontalOffset;
11982                 final float localRight = right + viewportToContentHorizontalOffset;
11983                 final float localTop = top + viewportToContentVerticalOffset;
11984                 final float localBottom = bottom + viewportToContentVerticalOffset;
11985                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
11986                 final boolean isBottomRightVisible =
11987                         isPositionVisible(localRight, localBottom);
11988                 int characterBoundsFlags = 0;
11989                 if (isTopLeftVisible || isBottomRightVisible) {
11990                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
11991                 }
11992                 if (!isTopLeftVisible || !isBottomRightVisible) {
11993                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
11994                 }
11995                 if (isRtl) {
11996                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
11997                 }
11998                 // Here offset is the index in Java chars.
11999                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
12000                         localBottom, characterBoundsFlags);
12001             }
12002         }
12003     }
12004 
12005     /**
12006      * @hide
12007      */
isPositionVisible(final float positionX, final float positionY)12008     public boolean isPositionVisible(final float positionX, final float positionY) {
12009         synchronized (TEMP_POSITION) {
12010             final float[] position = TEMP_POSITION;
12011             position[0] = positionX;
12012             position[1] = positionY;
12013             View view = this;
12014 
12015             while (view != null) {
12016                 if (view != this) {
12017                     // Local scroll is already taken into account in positionX/Y
12018                     position[0] -= view.getScrollX();
12019                     position[1] -= view.getScrollY();
12020                 }
12021 
12022                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
12023                         || position[1] > view.getHeight()) {
12024                     return false;
12025                 }
12026 
12027                 if (!view.getMatrix().isIdentity()) {
12028                     view.getMatrix().mapPoints(position);
12029                 }
12030 
12031                 position[0] += view.getLeft();
12032                 position[1] += view.getTop();
12033 
12034                 final ViewParent parent = view.getParent();
12035                 if (parent instanceof View) {
12036                     view = (View) parent;
12037                 } else {
12038                     // We've reached the ViewRoot, stop iterating
12039                     view = null;
12040                 }
12041             }
12042         }
12043 
12044         // We've been able to walk up the view hierarchy and the position was never clipped
12045         return true;
12046     }
12047 
12048     /**
12049      * Performs an accessibility action after it has been offered to the
12050      * delegate.
12051      *
12052      * @hide
12053      */
12054     @Override
performAccessibilityActionInternal(int action, Bundle arguments)12055     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
12056         if (mEditor != null
12057                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
12058             return true;
12059         }
12060         switch (action) {
12061             case AccessibilityNodeInfo.ACTION_CLICK: {
12062                 return performAccessibilityActionClick(arguments);
12063             }
12064             case AccessibilityNodeInfo.ACTION_COPY: {
12065                 if (isFocused() && canCopy()) {
12066                     if (onTextContextMenuItem(ID_COPY)) {
12067                         return true;
12068                     }
12069                 }
12070             } return false;
12071             case AccessibilityNodeInfo.ACTION_PASTE: {
12072                 if (isFocused() && canPaste()) {
12073                     if (onTextContextMenuItem(ID_PASTE)) {
12074                         return true;
12075                     }
12076                 }
12077             } return false;
12078             case AccessibilityNodeInfo.ACTION_CUT: {
12079                 if (isFocused() && canCut()) {
12080                     if (onTextContextMenuItem(ID_CUT)) {
12081                         return true;
12082                     }
12083                 }
12084             } return false;
12085             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
12086                 ensureIterableTextForAccessibilitySelectable();
12087                 CharSequence text = getIterableTextForAccessibility();
12088                 if (text == null) {
12089                     return false;
12090                 }
12091                 final int start = (arguments != null) ? arguments.getInt(
12092                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
12093                 final int end = (arguments != null) ? arguments.getInt(
12094                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
12095                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
12096                     // No arguments clears the selection.
12097                     if (start == end && end == -1) {
12098                         Selection.removeSelection((Spannable) text);
12099                         return true;
12100                     }
12101                     if (start >= 0 && start <= end && end <= text.length()) {
12102                         Selection.setSelection((Spannable) text, start, end);
12103                         // Make sure selection mode is engaged.
12104                         if (mEditor != null) {
12105                             mEditor.startSelectionActionModeAsync(false);
12106                         }
12107                         return true;
12108                     }
12109                 }
12110             } return false;
12111             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
12112             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
12113                 ensureIterableTextForAccessibilitySelectable();
12114                 return super.performAccessibilityActionInternal(action, arguments);
12115             }
12116             case ACCESSIBILITY_ACTION_SHARE: {
12117                 if (isFocused() && canShare()) {
12118                     if (onTextContextMenuItem(ID_SHARE)) {
12119                         return true;
12120                     }
12121                 }
12122             } return false;
12123             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
12124                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
12125                     return false;
12126                 }
12127                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
12128                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
12129                 setText(text);
12130                 if (mText != null) {
12131                     int updatedTextLength = mText.length();
12132                     if (updatedTextLength > 0) {
12133                         Selection.setSelection(mSpannable, updatedTextLength);
12134                     }
12135                 }
12136             } return true;
12137             case R.id.accessibilityActionImeEnter: {
12138                 if (isFocused() && isTextEditable()) {
12139                     onEditorAction(getImeActionId());
12140                 }
12141             } return true;
12142             case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
12143                 if (isLongClickable()) {
12144                     boolean handled;
12145                     if (isEnabled() && (mBufferType == BufferType.EDITABLE)) {
12146                         mEditor.mIsBeingLongClickedByAccessibility = true;
12147                         try {
12148                             handled = performLongClick();
12149                         } finally {
12150                             mEditor.mIsBeingLongClickedByAccessibility = false;
12151                         }
12152                     } else {
12153                         handled = performLongClick();
12154                     }
12155                     return handled;
12156                 }
12157             }
12158             return false;
12159             default: {
12160                 return super.performAccessibilityActionInternal(action, arguments);
12161             }
12162         }
12163     }
12164 
performAccessibilityActionClick(Bundle arguments)12165     private boolean performAccessibilityActionClick(Bundle arguments) {
12166         boolean handled = false;
12167 
12168         if (!isEnabled()) {
12169             return false;
12170         }
12171 
12172         if (isClickable() || isLongClickable()) {
12173             // Simulate View.onTouchEvent for an ACTION_UP event
12174             if (isFocusable() && !isFocused()) {
12175                 requestFocus();
12176             }
12177 
12178             performClick();
12179             handled = true;
12180         }
12181 
12182         // Show the IME, except when selecting in read-only text.
12183         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
12184                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
12185             final InputMethodManager imm = getInputMethodManager();
12186             viewClicked(imm);
12187             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
12188                 handled |= imm.showSoftInput(this, 0);
12189             }
12190         }
12191 
12192         return handled;
12193     }
12194 
hasSpannableText()12195     private boolean hasSpannableText() {
12196         return mText != null && mText instanceof Spannable;
12197     }
12198 
12199     /** @hide */
12200     @Override
sendAccessibilityEventInternal(int eventType)12201     public void sendAccessibilityEventInternal(int eventType) {
12202         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
12203             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
12204         }
12205 
12206         super.sendAccessibilityEventInternal(eventType);
12207     }
12208 
12209     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)12210     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
12211         // Do not send scroll events since first they are not interesting for
12212         // accessibility and second such events a generated too frequently.
12213         // For details see the implementation of bringTextIntoView().
12214         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
12215             return;
12216         }
12217         super.sendAccessibilityEventUnchecked(event);
12218     }
12219 
12220     /**
12221      * Returns the text that should be exposed to accessibility services.
12222      * <p>
12223      * This approximates what is displayed visually. If the user has specified
12224      * that accessibility services should speak passwords, this method will
12225      * bypass any password transformation method and return unobscured text.
12226      *
12227      * @return the text that should be exposed to accessibility services, may
12228      *         be {@code null} if no text is set
12229      */
12230     @Nullable
12231     @UnsupportedAppUsage
getTextForAccessibility()12232     private CharSequence getTextForAccessibility() {
12233         // If the text is empty, we must be showing the hint text.
12234         if (TextUtils.isEmpty(mText)) {
12235             return mHint;
12236         }
12237 
12238         // Otherwise, return whatever text is being displayed.
12239         return TextUtils.trimToParcelableSize(mTransformed);
12240     }
12241 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12242     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
12243             int fromIndex, int removedCount, int addedCount) {
12244         AccessibilityEvent event =
12245                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
12246         event.setFromIndex(fromIndex);
12247         event.setRemovedCount(removedCount);
12248         event.setAddedCount(addedCount);
12249         event.setBeforeText(beforeText);
12250         sendAccessibilityEventUnchecked(event);
12251     }
12252 
getInputMethodManager()12253     private InputMethodManager getInputMethodManager() {
12254         return getContext().getSystemService(InputMethodManager.class);
12255     }
12256 
12257     /**
12258      * Returns whether this text view is a current input method target.  The
12259      * default implementation just checks with {@link InputMethodManager}.
12260      * @return True if the TextView is a current input method target; false otherwise.
12261      */
isInputMethodTarget()12262     public boolean isInputMethodTarget() {
12263         InputMethodManager imm = getInputMethodManager();
12264         return imm != null && imm.isActive(this);
12265     }
12266 
12267     static final int ID_SELECT_ALL = android.R.id.selectAll;
12268     static final int ID_UNDO = android.R.id.undo;
12269     static final int ID_REDO = android.R.id.redo;
12270     static final int ID_CUT = android.R.id.cut;
12271     static final int ID_COPY = android.R.id.copy;
12272     static final int ID_PASTE = android.R.id.paste;
12273     static final int ID_SHARE = android.R.id.shareText;
12274     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
12275     static final int ID_REPLACE = android.R.id.replaceText;
12276     static final int ID_ASSIST = android.R.id.textAssist;
12277     static final int ID_AUTOFILL = android.R.id.autofill;
12278 
12279     /**
12280      * Called when a context menu option for the text view is selected.  Currently
12281      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
12282      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
12283      *
12284      * @return true if the context menu item action was performed.
12285      */
onTextContextMenuItem(int id)12286     public boolean onTextContextMenuItem(int id) {
12287         int min = 0;
12288         int max = mText.length();
12289 
12290         if (isFocused()) {
12291             final int selStart = getSelectionStart();
12292             final int selEnd = getSelectionEnd();
12293 
12294             min = Math.max(0, Math.min(selStart, selEnd));
12295             max = Math.max(0, Math.max(selStart, selEnd));
12296         }
12297 
12298         switch (id) {
12299             case ID_SELECT_ALL:
12300                 final boolean hadSelection = hasSelection();
12301                 selectAllText();
12302                 if (mEditor != null && hadSelection) {
12303                     mEditor.invalidateActionModeAsync();
12304                 }
12305                 return true;
12306 
12307             case ID_UNDO:
12308                 if (mEditor != null) {
12309                     mEditor.undo();
12310                 }
12311                 return true;  // Returns true even if nothing was undone.
12312 
12313             case ID_REDO:
12314                 if (mEditor != null) {
12315                     mEditor.redo();
12316                 }
12317                 return true;  // Returns true even if nothing was undone.
12318 
12319             case ID_PASTE:
12320                 paste(min, max, true /* withFormatting */);
12321                 return true;
12322 
12323             case ID_PASTE_AS_PLAIN_TEXT:
12324                 paste(min, max, false /* withFormatting */);
12325                 return true;
12326 
12327             case ID_CUT:
12328                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
12329                 if (setPrimaryClip(cutData)) {
12330                     deleteText_internal(min, max);
12331                 } else {
12332                     Toast.makeText(getContext(),
12333                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12334                             Toast.LENGTH_SHORT).show();
12335                 }
12336                 return true;
12337 
12338             case ID_COPY:
12339                 // For link action mode in a non-selectable/non-focusable TextView,
12340                 // make sure that we set the appropriate min/max.
12341                 final int selStart = getSelectionStart();
12342                 final int selEnd = getSelectionEnd();
12343                 min = Math.max(0, Math.min(selStart, selEnd));
12344                 max = Math.max(0, Math.max(selStart, selEnd));
12345                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
12346                 if (setPrimaryClip(copyData)) {
12347                     stopTextActionMode();
12348                 } else {
12349                     Toast.makeText(getContext(),
12350                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12351                             Toast.LENGTH_SHORT).show();
12352                 }
12353                 return true;
12354 
12355             case ID_REPLACE:
12356                 if (mEditor != null) {
12357                     mEditor.replace();
12358                 }
12359                 return true;
12360 
12361             case ID_SHARE:
12362                 shareSelectedText();
12363                 return true;
12364 
12365             case ID_AUTOFILL:
12366                 requestAutofill();
12367                 stopTextActionMode();
12368                 return true;
12369         }
12370         return false;
12371     }
12372 
12373     @UnsupportedAppUsage
getTransformedText(int start, int end)12374     CharSequence getTransformedText(int start, int end) {
12375         return removeSuggestionSpans(mTransformed.subSequence(start, end));
12376     }
12377 
12378     @Override
performLongClick()12379     public boolean performLongClick() {
12380         if (DEBUG_CURSOR) {
12381             logCursor("performLongClick", null);
12382         }
12383 
12384         boolean handled = false;
12385         boolean performedHapticFeedback = false;
12386 
12387         if (mEditor != null) {
12388             mEditor.mIsBeingLongClicked = true;
12389         }
12390 
12391         if (super.performLongClick()) {
12392             handled = true;
12393             performedHapticFeedback = true;
12394         }
12395 
12396         if (mEditor != null) {
12397             handled |= mEditor.performLongClick(handled);
12398             mEditor.mIsBeingLongClicked = false;
12399         }
12400 
12401         if (handled) {
12402             if (!performedHapticFeedback) {
12403               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
12404             }
12405             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
12406         } else {
12407             MetricsLogger.action(
12408                     mContext,
12409                     MetricsEvent.TEXT_LONGPRESS,
12410                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
12411         }
12412 
12413         return handled;
12414     }
12415 
12416     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12417     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
12418         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
12419         if (mEditor != null) {
12420             mEditor.onScrollChanged();
12421         }
12422     }
12423 
12424     /**
12425      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
12426      * by the IME or by the spell checker as the user types. This is done by adding
12427      * {@link SuggestionSpan}s to the text.
12428      *
12429      * When suggestions are enabled (default), this list of suggestions will be displayed when the
12430      * user asks for them on these parts of the text. This value depends on the inputType of this
12431      * TextView.
12432      *
12433      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
12434      *
12435      * In addition, the type variation must be one of
12436      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
12437      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
12438      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
12439      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
12440      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
12441      *
12442      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
12443      *
12444      * @return true if the suggestions popup window is enabled, based on the inputType.
12445      */
isSuggestionsEnabled()12446     public boolean isSuggestionsEnabled() {
12447         if (mEditor == null) return false;
12448         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
12449             return false;
12450         }
12451         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
12452 
12453         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
12454         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
12455                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
12456                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
12457                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
12458                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
12459     }
12460 
12461     /**
12462      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12463      * selection is initiated in this View.
12464      *
12465      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
12466      * Paste, Replace and Share actions, depending on what this View supports.
12467      *
12468      * <p>A custom implementation can add new entries in the default menu in its
12469      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
12470      * method. The default actions can also be removed from the menu using
12471      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12472      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
12473      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
12474      *
12475      * <p>Returning false from
12476      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
12477      * will prevent the action mode from being started.
12478      *
12479      * <p>Action click events should be handled by the custom implementation of
12480      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
12481      * android.view.MenuItem)}.
12482      *
12483      * <p>Note that text selection mode is not started when a TextView receives focus and the
12484      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
12485      * that case, to allow for quick replacement.
12486      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12487     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
12488         createEditorIfNeeded();
12489         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
12490     }
12491 
12492     /**
12493      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
12494      *
12495      * @return The current custom selection callback.
12496      */
getCustomSelectionActionModeCallback()12497     public ActionMode.Callback getCustomSelectionActionModeCallback() {
12498         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
12499     }
12500 
12501     /**
12502      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12503      * insertion is initiated in this View.
12504      * The standard implementation populates the menu with a subset of Select All,
12505      * Paste and Replace actions, depending on what this View supports.
12506      *
12507      * <p>A custom implementation can add new entries in the default menu in its
12508      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
12509      * android.view.Menu)} method. The default actions can also be removed from the menu using
12510      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12511      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
12512      *
12513      * <p>Returning false from
12514      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
12515      * android.view.Menu)} will prevent the action mode from being started.</p>
12516      *
12517      * <p>Action click events should be handled by the custom implementation of
12518      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
12519      * android.view.MenuItem)}.</p>
12520      *
12521      * <p>Note that text insertion mode is not started when a TextView receives focus and the
12522      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
12523      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12524     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
12525         createEditorIfNeeded();
12526         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
12527     }
12528 
12529     /**
12530      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
12531      *
12532      * @return The current custom insertion callback.
12533      */
getCustomInsertionActionModeCallback()12534     public ActionMode.Callback getCustomInsertionActionModeCallback() {
12535         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
12536     }
12537 
12538     /**
12539      * Sets the {@link TextClassifier} for this TextView.
12540      */
setTextClassifier(@ullable TextClassifier textClassifier)12541     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
12542         mTextClassifier = textClassifier;
12543     }
12544 
12545     /**
12546      * Returns the {@link TextClassifier} used by this TextView.
12547      * If no TextClassifier has been set, this TextView uses the default set by the
12548      * {@link TextClassificationManager}.
12549      */
12550     @NonNull
getTextClassifier()12551     public TextClassifier getTextClassifier() {
12552         if (mTextClassifier == null) {
12553             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12554             if (tcm != null) {
12555                 return tcm.getTextClassifier();
12556             }
12557             return TextClassifier.NO_OP;
12558         }
12559         return mTextClassifier;
12560     }
12561 
12562     /**
12563      * Returns a session-aware text classifier.
12564      * This method creates one if none already exists or the current one is destroyed.
12565      */
12566     @NonNull
getTextClassificationSession()12567     TextClassifier getTextClassificationSession() {
12568         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
12569             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12570             if (tcm != null) {
12571                 final String widgetType;
12572                 if (isTextEditable()) {
12573                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
12574                 } else if (isTextSelectable()) {
12575                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
12576                 } else {
12577                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
12578                 }
12579                 mTextClassificationContext = new TextClassificationContext.Builder(
12580                         mContext.getPackageName(), widgetType)
12581                         .build();
12582                 if (mTextClassifier != null) {
12583                     mTextClassificationSession = tcm.createTextClassificationSession(
12584                             mTextClassificationContext, mTextClassifier);
12585                 } else {
12586                     mTextClassificationSession = tcm.createTextClassificationSession(
12587                             mTextClassificationContext);
12588                 }
12589             } else {
12590                 mTextClassificationSession = TextClassifier.NO_OP;
12591             }
12592         }
12593         return mTextClassificationSession;
12594     }
12595 
12596     /**
12597      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
12598      * @see #getTextClassificationSession()
12599      */
12600     @Nullable
getTextClassificationContext()12601     TextClassificationContext getTextClassificationContext() {
12602         return mTextClassificationContext;
12603     }
12604 
12605     /**
12606      * Returns true if this TextView uses a no-op TextClassifier.
12607      */
usesNoOpTextClassifier()12608     boolean usesNoOpTextClassifier() {
12609         return getTextClassifier() == TextClassifier.NO_OP;
12610     }
12611 
12612     /**
12613      * Starts an ActionMode for the specified TextLinkSpan.
12614      *
12615      * @return Whether or not we're attempting to start the action mode.
12616      * @hide
12617      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12618     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12619         Preconditions.checkNotNull(clickedSpan);
12620 
12621         if (!(mText instanceof Spanned)) {
12622             return false;
12623         }
12624 
12625         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
12626         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
12627 
12628         if (start < 0 || end > mText.length() || start >= end) {
12629             return false;
12630         }
12631 
12632         createEditorIfNeeded();
12633         mEditor.startLinkActionModeAsync(start, end);
12634         return true;
12635     }
12636 
12637     /**
12638      * Handles a click on the specified TextLinkSpan.
12639      *
12640      * @return Whether or not the click is being handled.
12641      * @hide
12642      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12643     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12644         Preconditions.checkNotNull(clickedSpan);
12645         if (mText instanceof Spanned) {
12646             final Spanned spanned = (Spanned) mText;
12647             final int start = spanned.getSpanStart(clickedSpan);
12648             final int end = spanned.getSpanEnd(clickedSpan);
12649             if (start >= 0 && end <= mText.length() && start < end) {
12650                 final TextClassification.Request request = new TextClassification.Request.Builder(
12651                         mText, start, end)
12652                         .setDefaultLocales(getTextLocales())
12653                         .build();
12654                 final Supplier<TextClassification> supplier = () ->
12655                         getTextClassificationSession().classifyText(request);
12656                 final Consumer<TextClassification> consumer = classification -> {
12657                     if (classification != null) {
12658                         if (!classification.getActions().isEmpty()) {
12659                             try {
12660                                 classification.getActions().get(0).getActionIntent().send();
12661                             } catch (PendingIntent.CanceledException e) {
12662                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
12663                             }
12664                         } else {
12665                             Log.d(LOG_TAG, "No link action to perform");
12666                         }
12667                     } else {
12668                         // classification == null
12669                         Log.d(LOG_TAG, "Timeout while classifying text");
12670                     }
12671                 };
12672                 CompletableFuture.supplyAsync(supplier)
12673                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
12674                         .thenAccept(consumer);
12675                 return true;
12676             }
12677         }
12678         return false;
12679     }
12680 
12681     /**
12682      * @hide
12683      */
12684     @UnsupportedAppUsage
stopTextActionMode()12685     protected void stopTextActionMode() {
12686         if (mEditor != null) {
12687             mEditor.stopTextActionMode();
12688         }
12689     }
12690 
12691     /** @hide */
hideFloatingToolbar(int durationMs)12692     public void hideFloatingToolbar(int durationMs) {
12693         if (mEditor != null) {
12694             mEditor.hideFloatingToolbar(durationMs);
12695         }
12696     }
12697 
canUndo()12698     boolean canUndo() {
12699         return mEditor != null && mEditor.canUndo();
12700     }
12701 
canRedo()12702     boolean canRedo() {
12703         return mEditor != null && mEditor.canRedo();
12704     }
12705 
canCut()12706     boolean canCut() {
12707         if (hasPasswordTransformationMethod()) {
12708             return false;
12709         }
12710 
12711         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
12712                 && mEditor.mKeyListener != null) {
12713             return true;
12714         }
12715 
12716         return false;
12717     }
12718 
canCopy()12719     boolean canCopy() {
12720         if (hasPasswordTransformationMethod()) {
12721             return false;
12722         }
12723 
12724         if (mText.length() > 0 && hasSelection() && mEditor != null) {
12725             return true;
12726         }
12727 
12728         return false;
12729     }
12730 
canShare()12731     boolean canShare() {
12732         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
12733             return false;
12734         }
12735         return canCopy();
12736     }
12737 
isDeviceProvisioned()12738     boolean isDeviceProvisioned() {
12739         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
12740             mDeviceProvisionedState = Settings.Global.getInt(
12741                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
12742                     ? DEVICE_PROVISIONED_YES
12743                     : DEVICE_PROVISIONED_NO;
12744         }
12745         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
12746     }
12747 
12748     @UnsupportedAppUsage
canPaste()12749     boolean canPaste() {
12750         return (mText instanceof Editable
12751                 && mEditor != null && mEditor.mKeyListener != null
12752                 && getSelectionStart() >= 0
12753                 && getSelectionEnd() >= 0
12754                 && getClipboardManagerForUser().hasPrimaryClip());
12755     }
12756 
canPasteAsPlainText()12757     boolean canPasteAsPlainText() {
12758         if (!canPaste()) {
12759             return false;
12760         }
12761 
12762         final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
12763         final ClipDescription description = clipData.getDescription();
12764         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
12765         final CharSequence text = clipData.getItemAt(0).getText();
12766         if (isPlainType && (text instanceof Spanned)) {
12767             Spanned spanned = (Spanned) text;
12768             if (TextUtils.hasStyleSpan(spanned)) {
12769                 return true;
12770             }
12771         }
12772         return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
12773     }
12774 
canProcessText()12775     boolean canProcessText() {
12776         if (getId() == View.NO_ID) {
12777             return false;
12778         }
12779         return canShare();
12780     }
12781 
canSelectAllText()12782     boolean canSelectAllText() {
12783         return canSelectText() && !hasPasswordTransformationMethod()
12784                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
12785     }
12786 
selectAllText()12787     boolean selectAllText() {
12788         if (mEditor != null) {
12789             // Hide the toolbar before changing the selection to avoid flickering.
12790             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
12791         }
12792         final int length = mText.length();
12793         Selection.setSelection(mSpannable, 0, length);
12794         return length > 0;
12795     }
12796 
replaceSelectionWithText(CharSequence text)12797     void replaceSelectionWithText(CharSequence text) {
12798         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
12799     }
12800 
12801     /**
12802      * Paste clipboard content between min and max positions.
12803      */
paste(int min, int max, boolean withFormatting)12804     private void paste(int min, int max, boolean withFormatting) {
12805         ClipboardManager clipboard = getClipboardManagerForUser();
12806         ClipData clip = clipboard.getPrimaryClip();
12807         if (clip != null) {
12808             boolean didFirst = false;
12809             for (int i = 0; i < clip.getItemCount(); i++) {
12810                 final CharSequence paste;
12811                 if (withFormatting) {
12812                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
12813                 } else {
12814                     // Get an item as text and remove all spans by toString().
12815                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
12816                     paste = (text instanceof Spanned) ? text.toString() : text;
12817                 }
12818                 if (paste != null) {
12819                     if (!didFirst) {
12820                         Selection.setSelection(mSpannable, max);
12821                         ((Editable) mText).replace(min, max, paste);
12822                         didFirst = true;
12823                     } else {
12824                         ((Editable) mText).insert(getSelectionEnd(), "\n");
12825                         ((Editable) mText).insert(getSelectionEnd(), paste);
12826                     }
12827                 }
12828             }
12829             sLastCutCopyOrTextChangedTime = 0;
12830         }
12831     }
12832 
shareSelectedText()12833     private void shareSelectedText() {
12834         String selectedText = getSelectedText();
12835         if (selectedText != null && !selectedText.isEmpty()) {
12836             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
12837             sharingIntent.setType("text/plain");
12838             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
12839             selectedText = TextUtils.trimToParcelableSize(selectedText);
12840             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
12841             getContext().startActivity(Intent.createChooser(sharingIntent, null));
12842             Selection.setSelection(mSpannable, getSelectionEnd());
12843         }
12844     }
12845 
12846     @CheckResult
setPrimaryClip(ClipData clip)12847     private boolean setPrimaryClip(ClipData clip) {
12848         ClipboardManager clipboard = getClipboardManagerForUser();
12849         try {
12850             clipboard.setPrimaryClip(clip);
12851         } catch (Throwable t) {
12852             return false;
12853         }
12854         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
12855         return true;
12856     }
12857 
12858     /**
12859      * Get the character offset closest to the specified absolute position. A typical use case is to
12860      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
12861      *
12862      * @param x The horizontal absolute position of a point on screen
12863      * @param y The vertical absolute position of a point on screen
12864      * @return the character offset for the character whose position is closest to the specified
12865      *  position. Returns -1 if there is no layout.
12866      */
getOffsetForPosition(float x, float y)12867     public int getOffsetForPosition(float x, float y) {
12868         if (getLayout() == null) return -1;
12869         final int line = getLineAtCoordinate(y);
12870         final int offset = getOffsetAtCoordinate(line, x);
12871         return offset;
12872     }
12873 
convertToLocalHorizontalCoordinate(float x)12874     float convertToLocalHorizontalCoordinate(float x) {
12875         x -= getTotalPaddingLeft();
12876         // Clamp the position to inside of the view.
12877         x = Math.max(0.0f, x);
12878         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
12879         x += getScrollX();
12880         return x;
12881     }
12882 
12883     @UnsupportedAppUsage
getLineAtCoordinate(float y)12884     int getLineAtCoordinate(float y) {
12885         y -= getTotalPaddingTop();
12886         // Clamp the position to inside of the view.
12887         y = Math.max(0.0f, y);
12888         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
12889         y += getScrollY();
12890         return getLayout().getLineForVertical((int) y);
12891     }
12892 
getLineAtCoordinateUnclamped(float y)12893     int getLineAtCoordinateUnclamped(float y) {
12894         y -= getTotalPaddingTop();
12895         y += getScrollY();
12896         return getLayout().getLineForVertical((int) y);
12897     }
12898 
getOffsetAtCoordinate(int line, float x)12899     int getOffsetAtCoordinate(int line, float x) {
12900         x = convertToLocalHorizontalCoordinate(x);
12901         return getLayout().getOffsetForHorizontal(line, x);
12902     }
12903 
12904     @Override
onDragEvent(DragEvent event)12905     public boolean onDragEvent(DragEvent event) {
12906         switch (event.getAction()) {
12907             case DragEvent.ACTION_DRAG_STARTED:
12908                 return mEditor != null && mEditor.hasInsertionController();
12909 
12910             case DragEvent.ACTION_DRAG_ENTERED:
12911                 TextView.this.requestFocus();
12912                 return true;
12913 
12914             case DragEvent.ACTION_DRAG_LOCATION:
12915                 if (mText instanceof Spannable) {
12916                     final int offset = getOffsetForPosition(event.getX(), event.getY());
12917                     Selection.setSelection(mSpannable, offset);
12918                 }
12919                 return true;
12920 
12921             case DragEvent.ACTION_DROP:
12922                 if (mEditor != null) mEditor.onDrop(event);
12923                 return true;
12924 
12925             case DragEvent.ACTION_DRAG_ENDED:
12926             case DragEvent.ACTION_DRAG_EXITED:
12927             default:
12928                 return true;
12929         }
12930     }
12931 
isInBatchEditMode()12932     boolean isInBatchEditMode() {
12933         if (mEditor == null) return false;
12934         final Editor.InputMethodState ims = mEditor.mInputMethodState;
12935         if (ims != null) {
12936             return ims.mBatchEditNesting > 0;
12937         }
12938         return mEditor.mInBatchEditControllers;
12939     }
12940 
12941     @Override
onRtlPropertiesChanged(int layoutDirection)12942     public void onRtlPropertiesChanged(int layoutDirection) {
12943         super.onRtlPropertiesChanged(layoutDirection);
12944 
12945         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
12946         if (mTextDir != newTextDir) {
12947             mTextDir = newTextDir;
12948             if (mLayout != null) {
12949                 checkForRelayout();
12950             }
12951         }
12952     }
12953 
12954     /**
12955      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
12956      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
12957      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
12958      * return value may not be the same as the one TextView uses if the View's layout direction is
12959      * not resolved or detached from parent root view.
12960      */
getTextDirectionHeuristic()12961     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
12962         if (hasPasswordTransformationMethod()) {
12963             // passwords fields should be LTR
12964             return TextDirectionHeuristics.LTR;
12965         }
12966 
12967         if (mEditor != null
12968                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
12969                     == EditorInfo.TYPE_CLASS_PHONE) {
12970             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
12971             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
12972             // RTL digits.
12973             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
12974             final String zero = symbols.getDigitStrings()[0];
12975             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
12976             // direction.
12977             final int firstCodepoint = zero.codePointAt(0);
12978             final byte digitDirection = Character.getDirectionality(firstCodepoint);
12979             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
12980                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
12981                 return TextDirectionHeuristics.RTL;
12982             } else {
12983                 return TextDirectionHeuristics.LTR;
12984             }
12985         }
12986 
12987         // Always need to resolve layout direction first
12988         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
12989 
12990         // Now, we can select the heuristic
12991         switch (getTextDirection()) {
12992             default:
12993             case TEXT_DIRECTION_FIRST_STRONG:
12994                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
12995                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
12996             case TEXT_DIRECTION_ANY_RTL:
12997                 return TextDirectionHeuristics.ANYRTL_LTR;
12998             case TEXT_DIRECTION_LTR:
12999                 return TextDirectionHeuristics.LTR;
13000             case TEXT_DIRECTION_RTL:
13001                 return TextDirectionHeuristics.RTL;
13002             case TEXT_DIRECTION_LOCALE:
13003                 return TextDirectionHeuristics.LOCALE;
13004             case TEXT_DIRECTION_FIRST_STRONG_LTR:
13005                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
13006             case TEXT_DIRECTION_FIRST_STRONG_RTL:
13007                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
13008         }
13009     }
13010 
13011     /**
13012      * @hide
13013      */
13014     @Override
onResolveDrawables(int layoutDirection)13015     public void onResolveDrawables(int layoutDirection) {
13016         // No need to resolve twice
13017         if (mLastLayoutDirection == layoutDirection) {
13018             return;
13019         }
13020         mLastLayoutDirection = layoutDirection;
13021 
13022         // Resolve drawables
13023         if (mDrawables != null) {
13024             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
13025                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
13026                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
13027                 applyCompoundDrawableTint();
13028             }
13029         }
13030     }
13031 
13032     /**
13033      * Prepares a drawable for display by propagating layout direction and
13034      * drawable state.
13035      *
13036      * @param dr the drawable to prepare
13037      */
prepareDrawableForDisplay(@ullable Drawable dr)13038     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
13039         if (dr == null) {
13040             return;
13041         }
13042 
13043         dr.setLayoutDirection(getLayoutDirection());
13044 
13045         if (dr.isStateful()) {
13046             dr.setState(getDrawableState());
13047             dr.jumpToCurrentState();
13048         }
13049     }
13050 
13051     /**
13052      * @hide
13053      */
resetResolvedDrawables()13054     protected void resetResolvedDrawables() {
13055         super.resetResolvedDrawables();
13056         mLastLayoutDirection = -1;
13057     }
13058 
13059     /**
13060      * @hide
13061      */
viewClicked(InputMethodManager imm)13062     protected void viewClicked(InputMethodManager imm) {
13063         if (imm != null) {
13064             imm.viewClicked(this);
13065         }
13066     }
13067 
13068     /**
13069      * Deletes the range of text [start, end[.
13070      * @hide
13071      */
13072     @UnsupportedAppUsage
deleteText_internal(int start, int end)13073     protected void deleteText_internal(int start, int end) {
13074         ((Editable) mText).delete(start, end);
13075     }
13076 
13077     /**
13078      * Replaces the range of text [start, end[ by replacement text
13079      * @hide
13080      */
replaceText_internal(int start, int end, CharSequence text)13081     protected void replaceText_internal(int start, int end, CharSequence text) {
13082         ((Editable) mText).replace(start, end, text);
13083     }
13084 
13085     /**
13086      * Sets a span on the specified range of text
13087      * @hide
13088      */
setSpan_internal(Object span, int start, int end, int flags)13089     protected void setSpan_internal(Object span, int start, int end, int flags) {
13090         ((Editable) mText).setSpan(span, start, end, flags);
13091     }
13092 
13093     /**
13094      * Moves the cursor to the specified offset position in text
13095      * @hide
13096      */
setCursorPosition_internal(int start, int end)13097     protected void setCursorPosition_internal(int start, int end) {
13098         Selection.setSelection(((Editable) mText), start, end);
13099     }
13100 
13101     /**
13102      * An Editor should be created as soon as any of the editable-specific fields (grouped
13103      * inside the Editor object) is assigned to a non-default value.
13104      * This method will create the Editor if needed.
13105      *
13106      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
13107      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
13108      * Editor for backward compatibility, as soon as one of these fields is assigned.
13109      *
13110      * Also note that for performance reasons, the mEditor is created when needed, but not
13111      * reset when no more edit-specific fields are needed.
13112      */
13113     @UnsupportedAppUsage
createEditorIfNeeded()13114     private void createEditorIfNeeded() {
13115         if (mEditor == null) {
13116             mEditor = new Editor(this);
13117         }
13118     }
13119 
13120     /**
13121      * @hide
13122      */
13123     @Override
13124     @UnsupportedAppUsage
getIterableTextForAccessibility()13125     public CharSequence getIterableTextForAccessibility() {
13126         return mText;
13127     }
13128 
ensureIterableTextForAccessibilitySelectable()13129     private void ensureIterableTextForAccessibilitySelectable() {
13130         if (!(mText instanceof Spannable)) {
13131             setText(mText, BufferType.SPANNABLE);
13132         }
13133     }
13134 
13135     /**
13136      * @hide
13137      */
13138     @Override
getIteratorForGranularity(int granularity)13139     public TextSegmentIterator getIteratorForGranularity(int granularity) {
13140         switch (granularity) {
13141             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
13142                 Spannable text = (Spannable) getIterableTextForAccessibility();
13143                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
13144                     AccessibilityIterators.LineTextSegmentIterator iterator =
13145                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
13146                     iterator.initialize(text, getLayout());
13147                     return iterator;
13148                 }
13149             } break;
13150             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
13151                 Spannable text = (Spannable) getIterableTextForAccessibility();
13152                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
13153                     AccessibilityIterators.PageTextSegmentIterator iterator =
13154                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
13155                     iterator.initialize(this);
13156                     return iterator;
13157                 }
13158             } break;
13159         }
13160         return super.getIteratorForGranularity(granularity);
13161     }
13162 
13163     /**
13164      * @hide
13165      */
13166     @Override
getAccessibilitySelectionStart()13167     public int getAccessibilitySelectionStart() {
13168         return getSelectionStart();
13169     }
13170 
13171     /**
13172      * @hide
13173      */
isAccessibilitySelectionExtendable()13174     public boolean isAccessibilitySelectionExtendable() {
13175         return true;
13176     }
13177 
13178     /**
13179      * @hide
13180      */
13181     @Override
getAccessibilitySelectionEnd()13182     public int getAccessibilitySelectionEnd() {
13183         return getSelectionEnd();
13184     }
13185 
13186     /**
13187      * @hide
13188      */
13189     @Override
setAccessibilitySelection(int start, int end)13190     public void setAccessibilitySelection(int start, int end) {
13191         if (getAccessibilitySelectionStart() == start
13192                 && getAccessibilitySelectionEnd() == end) {
13193             return;
13194         }
13195         CharSequence text = getIterableTextForAccessibility();
13196         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
13197             Selection.setSelection((Spannable) text, start, end);
13198         } else {
13199             Selection.removeSelection((Spannable) text);
13200         }
13201         // Hide all selection controllers used for adjusting selection
13202         // since we are doing so explicitlty by other means and these
13203         // controllers interact with how selection behaves.
13204         if (mEditor != null) {
13205             mEditor.hideCursorAndSpanControllers();
13206             mEditor.stopTextActionMode();
13207         }
13208     }
13209 
13210     /** @hide */
13211     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)13212     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
13213         super.encodeProperties(stream);
13214 
13215         TruncateAt ellipsize = getEllipsize();
13216         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
13217         stream.addProperty("text:textSize", getTextSize());
13218         stream.addProperty("text:scaledTextSize", getScaledTextSize());
13219         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
13220         stream.addProperty("text:selectionStart", getSelectionStart());
13221         stream.addProperty("text:selectionEnd", getSelectionEnd());
13222         stream.addProperty("text:curTextColor", mCurTextColor);
13223         stream.addUserProperty("text:text", mText == null ? null : mText.toString());
13224         stream.addProperty("text:gravity", mGravity);
13225     }
13226 
13227     /**
13228      * User interface state that is stored by TextView for implementing
13229      * {@link View#onSaveInstanceState}.
13230      */
13231     public static class SavedState extends BaseSavedState {
13232         int selStart = -1;
13233         int selEnd = -1;
13234         @UnsupportedAppUsage
13235         CharSequence text;
13236         boolean frozenWithFocus;
13237         CharSequence error;
13238         ParcelableParcel editorState;  // Optional state from Editor.
13239 
SavedState(Parcelable superState)13240         SavedState(Parcelable superState) {
13241             super(superState);
13242         }
13243 
13244         @Override
writeToParcel(Parcel out, int flags)13245         public void writeToParcel(Parcel out, int flags) {
13246             super.writeToParcel(out, flags);
13247             out.writeInt(selStart);
13248             out.writeInt(selEnd);
13249             out.writeInt(frozenWithFocus ? 1 : 0);
13250             TextUtils.writeToParcel(text, out, flags);
13251 
13252             if (error == null) {
13253                 out.writeInt(0);
13254             } else {
13255                 out.writeInt(1);
13256                 TextUtils.writeToParcel(error, out, flags);
13257             }
13258 
13259             if (editorState == null) {
13260                 out.writeInt(0);
13261             } else {
13262                 out.writeInt(1);
13263                 editorState.writeToParcel(out, flags);
13264             }
13265         }
13266 
13267         @Override
toString()13268         public String toString() {
13269             String str = "TextView.SavedState{"
13270                     + Integer.toHexString(System.identityHashCode(this))
13271                     + " start=" + selStart + " end=" + selEnd;
13272             if (text != null) {
13273                 str += " text=" + text;
13274             }
13275             return str + "}";
13276         }
13277 
13278         @SuppressWarnings("hiding")
13279         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
13280                 new Parcelable.Creator<SavedState>() {
13281                     public SavedState createFromParcel(Parcel in) {
13282                         return new SavedState(in);
13283                     }
13284 
13285                     public SavedState[] newArray(int size) {
13286                         return new SavedState[size];
13287                     }
13288                 };
13289 
SavedState(Parcel in)13290         private SavedState(Parcel in) {
13291             super(in);
13292             selStart = in.readInt();
13293             selEnd = in.readInt();
13294             frozenWithFocus = (in.readInt() != 0);
13295             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13296 
13297             if (in.readInt() != 0) {
13298                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13299             }
13300 
13301             if (in.readInt() != 0) {
13302                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
13303             }
13304         }
13305     }
13306 
13307     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
13308         private char[] mChars;
13309         private int mStart, mLength;
13310 
CharWrapper(char[] chars, int start, int len)13311         public CharWrapper(char[] chars, int start, int len) {
13312             mChars = chars;
13313             mStart = start;
13314             mLength = len;
13315         }
13316 
set(char[] chars, int start, int len)13317         /* package */ void set(char[] chars, int start, int len) {
13318             mChars = chars;
13319             mStart = start;
13320             mLength = len;
13321         }
13322 
length()13323         public int length() {
13324             return mLength;
13325         }
13326 
charAt(int off)13327         public char charAt(int off) {
13328             return mChars[off + mStart];
13329         }
13330 
13331         @Override
toString()13332         public String toString() {
13333             return new String(mChars, mStart, mLength);
13334         }
13335 
subSequence(int start, int end)13336         public CharSequence subSequence(int start, int end) {
13337             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13338                 throw new IndexOutOfBoundsException(start + ", " + end);
13339             }
13340 
13341             return new String(mChars, start + mStart, end - start);
13342         }
13343 
getChars(int start, int end, char[] buf, int off)13344         public void getChars(int start, int end, char[] buf, int off) {
13345             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13346                 throw new IndexOutOfBoundsException(start + ", " + end);
13347             }
13348 
13349             System.arraycopy(mChars, start + mStart, buf, off, end - start);
13350         }
13351 
13352         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13353         public void drawText(BaseCanvas c, int start, int end,
13354                              float x, float y, Paint p) {
13355             c.drawText(mChars, start + mStart, end - start, x, y, p);
13356         }
13357 
13358         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13359         public void drawTextRun(BaseCanvas c, int start, int end,
13360                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
13361             int count = end - start;
13362             int contextCount = contextEnd - contextStart;
13363             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
13364                     contextCount, x, y, isRtl, p);
13365         }
13366 
measureText(int start, int end, Paint p)13367         public float measureText(int start, int end, Paint p) {
13368             return p.measureText(mChars, start + mStart, end - start);
13369         }
13370 
getTextWidths(int start, int end, float[] widths, Paint p)13371         public int getTextWidths(int start, int end, float[] widths, Paint p) {
13372             return p.getTextWidths(mChars, start + mStart, end - start, widths);
13373         }
13374 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13375         public float getTextRunAdvances(int start, int end, int contextStart,
13376                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
13377                 Paint p) {
13378             int count = end - start;
13379             int contextCount = contextEnd - contextStart;
13380             return p.getTextRunAdvances(mChars, start + mStart, count,
13381                     contextStart + mStart, contextCount, isRtl, advances,
13382                     advancesIndex);
13383         }
13384 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13385         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
13386                 int offset, int cursorOpt, Paint p) {
13387             int contextCount = contextEnd - contextStart;
13388             return p.getTextRunCursor(mChars, contextStart + mStart,
13389                     contextCount, isRtl, offset + mStart, cursorOpt);
13390         }
13391     }
13392 
13393     private static final class Marquee {
13394         // TODO: Add an option to configure this
13395         private static final float MARQUEE_DELTA_MAX = 0.07f;
13396         private static final int MARQUEE_DELAY = 1200;
13397         private static final int MARQUEE_DP_PER_SECOND = 30;
13398 
13399         private static final byte MARQUEE_STOPPED = 0x0;
13400         private static final byte MARQUEE_STARTING = 0x1;
13401         private static final byte MARQUEE_RUNNING = 0x2;
13402 
13403         private final WeakReference<TextView> mView;
13404         private final Choreographer mChoreographer;
13405 
13406         private byte mStatus = MARQUEE_STOPPED;
13407         private final float mPixelsPerMs;
13408         private float mMaxScroll;
13409         private float mMaxFadeScroll;
13410         private float mGhostStart;
13411         private float mGhostOffset;
13412         private float mFadeStop;
13413         private int mRepeatLimit;
13414 
13415         private float mScroll;
13416         private long mLastAnimationMs;
13417 
Marquee(TextView v)13418         Marquee(TextView v) {
13419             final float density = v.getContext().getResources().getDisplayMetrics().density;
13420             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
13421             mView = new WeakReference<TextView>(v);
13422             mChoreographer = Choreographer.getInstance();
13423         }
13424 
13425         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
13426             @Override
13427             public void doFrame(long frameTimeNanos) {
13428                 tick();
13429             }
13430         };
13431 
13432         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
13433             @Override
13434             public void doFrame(long frameTimeNanos) {
13435                 mStatus = MARQUEE_RUNNING;
13436                 mLastAnimationMs = mChoreographer.getFrameTime();
13437                 tick();
13438             }
13439         };
13440 
13441         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
13442             @Override
13443             public void doFrame(long frameTimeNanos) {
13444                 if (mStatus == MARQUEE_RUNNING) {
13445                     if (mRepeatLimit >= 0) {
13446                         mRepeatLimit--;
13447                     }
13448                     start(mRepeatLimit);
13449                 }
13450             }
13451         };
13452 
tick()13453         void tick() {
13454             if (mStatus != MARQUEE_RUNNING) {
13455                 return;
13456             }
13457 
13458             mChoreographer.removeFrameCallback(mTickCallback);
13459 
13460             final TextView textView = mView.get();
13461             if (textView != null && (textView.isFocused() || textView.isSelected())) {
13462                 long currentMs = mChoreographer.getFrameTime();
13463                 long deltaMs = currentMs - mLastAnimationMs;
13464                 mLastAnimationMs = currentMs;
13465                 float deltaPx = deltaMs * mPixelsPerMs;
13466                 mScroll += deltaPx;
13467                 if (mScroll > mMaxScroll) {
13468                     mScroll = mMaxScroll;
13469                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
13470                 } else {
13471                     mChoreographer.postFrameCallback(mTickCallback);
13472                 }
13473                 textView.invalidate();
13474             }
13475         }
13476 
stop()13477         void stop() {
13478             mStatus = MARQUEE_STOPPED;
13479             mChoreographer.removeFrameCallback(mStartCallback);
13480             mChoreographer.removeFrameCallback(mRestartCallback);
13481             mChoreographer.removeFrameCallback(mTickCallback);
13482             resetScroll();
13483         }
13484 
resetScroll()13485         private void resetScroll() {
13486             mScroll = 0.0f;
13487             final TextView textView = mView.get();
13488             if (textView != null) textView.invalidate();
13489         }
13490 
start(int repeatLimit)13491         void start(int repeatLimit) {
13492             if (repeatLimit == 0) {
13493                 stop();
13494                 return;
13495             }
13496             mRepeatLimit = repeatLimit;
13497             final TextView textView = mView.get();
13498             if (textView != null && textView.mLayout != null) {
13499                 mStatus = MARQUEE_STARTING;
13500                 mScroll = 0.0f;
13501                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
13502                         - textView.getCompoundPaddingRight();
13503                 final float lineWidth = textView.mLayout.getLineWidth(0);
13504                 final float gap = textWidth / 3.0f;
13505                 mGhostStart = lineWidth - textWidth + gap;
13506                 mMaxScroll = mGhostStart + textWidth;
13507                 mGhostOffset = lineWidth + gap;
13508                 mFadeStop = lineWidth + textWidth / 6.0f;
13509                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
13510 
13511                 textView.invalidate();
13512                 mChoreographer.postFrameCallback(mStartCallback);
13513             }
13514         }
13515 
getGhostOffset()13516         float getGhostOffset() {
13517             return mGhostOffset;
13518         }
13519 
getScroll()13520         float getScroll() {
13521             return mScroll;
13522         }
13523 
getMaxFadeScroll()13524         float getMaxFadeScroll() {
13525             return mMaxFadeScroll;
13526         }
13527 
shouldDrawLeftFade()13528         boolean shouldDrawLeftFade() {
13529             return mScroll <= mFadeStop;
13530         }
13531 
shouldDrawGhost()13532         boolean shouldDrawGhost() {
13533             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
13534         }
13535 
isRunning()13536         boolean isRunning() {
13537             return mStatus == MARQUEE_RUNNING;
13538         }
13539 
isStopped()13540         boolean isStopped() {
13541             return mStatus == MARQUEE_STOPPED;
13542         }
13543     }
13544 
13545     private class ChangeWatcher implements TextWatcher, SpanWatcher {
13546 
13547         private CharSequence mBeforeText;
13548 
beforeTextChanged(CharSequence buffer, int start, int before, int after)13549         public void beforeTextChanged(CharSequence buffer, int start,
13550                                       int before, int after) {
13551             if (DEBUG_EXTRACT) {
13552                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
13553                         + " before=" + before + " after=" + after + ": " + buffer);
13554             }
13555 
13556             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
13557                 mBeforeText = mTransformed.toString();
13558             }
13559 
13560             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
13561         }
13562 
onTextChanged(CharSequence buffer, int start, int before, int after)13563         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
13564             if (DEBUG_EXTRACT) {
13565                 Log.v(LOG_TAG, "onTextChanged start=" + start
13566                         + " before=" + before + " after=" + after + ": " + buffer);
13567             }
13568             TextView.this.handleTextChanged(buffer, start, before, after);
13569 
13570             if (AccessibilityManager.getInstance(mContext).isEnabled()
13571                     && (isFocused() || isSelected() && isShown())) {
13572                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
13573                 mBeforeText = null;
13574             }
13575         }
13576 
afterTextChanged(Editable buffer)13577         public void afterTextChanged(Editable buffer) {
13578             if (DEBUG_EXTRACT) {
13579                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
13580             }
13581             TextView.this.sendAfterTextChanged(buffer);
13582 
13583             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
13584                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
13585             }
13586         }
13587 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13588         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
13589             if (DEBUG_EXTRACT) {
13590                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
13591                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
13592             }
13593             TextView.this.spanChange(buf, what, s, st, e, en);
13594         }
13595 
onSpanAdded(Spannable buf, Object what, int s, int e)13596         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
13597             if (DEBUG_EXTRACT) {
13598                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
13599             }
13600             TextView.this.spanChange(buf, what, -1, s, -1, e);
13601         }
13602 
onSpanRemoved(Spannable buf, Object what, int s, int e)13603         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
13604             if (DEBUG_EXTRACT) {
13605                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
13606             }
13607             TextView.this.spanChange(buf, what, s, -1, e, -1);
13608         }
13609     }
13610 
logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)13611     private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
13612         if (msgFormat == null) {
13613             Log.d(LOG_TAG, location);
13614         } else {
13615             Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs));
13616         }
13617     }
13618 }
13619