1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
23 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
24 
25 import android.R;
26 import android.annotation.CheckResult;
27 import android.annotation.ColorInt;
28 import android.annotation.DrawableRes;
29 import android.annotation.FloatRange;
30 import android.annotation.IntDef;
31 import android.annotation.IntRange;
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.annotation.Px;
35 import android.annotation.RequiresPermission;
36 import android.annotation.Size;
37 import android.annotation.StringRes;
38 import android.annotation.StyleRes;
39 import android.annotation.XmlRes;
40 import android.app.Activity;
41 import android.app.PendingIntent;
42 import android.app.assist.AssistStructure;
43 import android.compat.annotation.UnsupportedAppUsage;
44 import android.content.ClipData;
45 import android.content.ClipDescription;
46 import android.content.ClipboardManager;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.content.UndoManager;
50 import android.content.pm.PackageManager;
51 import android.content.res.ColorStateList;
52 import android.content.res.CompatibilityInfo;
53 import android.content.res.Configuration;
54 import android.content.res.Resources;
55 import android.content.res.TypedArray;
56 import android.content.res.XmlResourceParser;
57 import android.graphics.BaseCanvas;
58 import android.graphics.BlendMode;
59 import android.graphics.Canvas;
60 import android.graphics.Insets;
61 import android.graphics.Paint;
62 import android.graphics.Paint.FontMetricsInt;
63 import android.graphics.Path;
64 import android.graphics.PorterDuff;
65 import android.graphics.Rect;
66 import android.graphics.RectF;
67 import android.graphics.Typeface;
68 import android.graphics.drawable.Drawable;
69 import android.graphics.fonts.FontStyle;
70 import android.graphics.fonts.FontVariationAxis;
71 import android.icu.text.DecimalFormatSymbols;
72 import android.os.AsyncTask;
73 import android.os.Build;
74 import android.os.Build.VERSION_CODES;
75 import android.os.Bundle;
76 import android.os.LocaleList;
77 import android.os.Parcel;
78 import android.os.Parcelable;
79 import android.os.ParcelableParcel;
80 import android.os.Process;
81 import android.os.SystemClock;
82 import android.os.UserHandle;
83 import android.provider.Settings;
84 import android.text.BoringLayout;
85 import android.text.DynamicLayout;
86 import android.text.Editable;
87 import android.text.GetChars;
88 import android.text.GraphicsOperations;
89 import android.text.InputFilter;
90 import android.text.InputType;
91 import android.text.Layout;
92 import android.text.ParcelableSpan;
93 import android.text.PrecomputedText;
94 import android.text.Selection;
95 import android.text.SpanWatcher;
96 import android.text.Spannable;
97 import android.text.SpannableStringBuilder;
98 import android.text.Spanned;
99 import android.text.SpannedString;
100 import android.text.StaticLayout;
101 import android.text.TextDirectionHeuristic;
102 import android.text.TextDirectionHeuristics;
103 import android.text.TextPaint;
104 import android.text.TextUtils;
105 import android.text.TextUtils.TruncateAt;
106 import android.text.TextWatcher;
107 import android.text.method.AllCapsTransformationMethod;
108 import android.text.method.ArrowKeyMovementMethod;
109 import android.text.method.DateKeyListener;
110 import android.text.method.DateTimeKeyListener;
111 import android.text.method.DialerKeyListener;
112 import android.text.method.DigitsKeyListener;
113 import android.text.method.KeyListener;
114 import android.text.method.LinkMovementMethod;
115 import android.text.method.MetaKeyKeyListener;
116 import android.text.method.MovementMethod;
117 import android.text.method.PasswordTransformationMethod;
118 import android.text.method.SingleLineTransformationMethod;
119 import android.text.method.TextKeyListener;
120 import android.text.method.TimeKeyListener;
121 import android.text.method.TransformationMethod;
122 import android.text.method.TransformationMethod2;
123 import android.text.method.WordIterator;
124 import android.text.style.CharacterStyle;
125 import android.text.style.ClickableSpan;
126 import android.text.style.ParagraphStyle;
127 import android.text.style.SpellCheckSpan;
128 import android.text.style.SuggestionSpan;
129 import android.text.style.URLSpan;
130 import android.text.style.UpdateAppearance;
131 import android.text.util.Linkify;
132 import android.util.AttributeSet;
133 import android.util.DisplayMetrics;
134 import android.util.IntArray;
135 import android.util.Log;
136 import android.util.SparseIntArray;
137 import android.util.TypedValue;
138 import android.view.AccessibilityIterators.TextSegmentIterator;
139 import android.view.ActionMode;
140 import android.view.Choreographer;
141 import android.view.ContextMenu;
142 import android.view.DragEvent;
143 import android.view.Gravity;
144 import android.view.HapticFeedbackConstants;
145 import android.view.InputDevice;
146 import android.view.KeyCharacterMap;
147 import android.view.KeyEvent;
148 import android.view.MotionEvent;
149 import android.view.PointerIcon;
150 import android.view.View;
151 import android.view.ViewConfiguration;
152 import android.view.ViewDebug;
153 import android.view.ViewGroup.LayoutParams;
154 import android.view.ViewHierarchyEncoder;
155 import android.view.ViewParent;
156 import android.view.ViewRootImpl;
157 import android.view.ViewStructure;
158 import android.view.ViewTreeObserver;
159 import android.view.accessibility.AccessibilityEvent;
160 import android.view.accessibility.AccessibilityManager;
161 import android.view.accessibility.AccessibilityNodeInfo;
162 import android.view.animation.AnimationUtils;
163 import android.view.autofill.AutofillManager;
164 import android.view.autofill.AutofillValue;
165 import android.view.contentcapture.ContentCaptureManager;
166 import android.view.contentcapture.ContentCaptureSession;
167 import android.view.inputmethod.BaseInputConnection;
168 import android.view.inputmethod.CompletionInfo;
169 import android.view.inputmethod.CorrectionInfo;
170 import android.view.inputmethod.CursorAnchorInfo;
171 import android.view.inputmethod.EditorInfo;
172 import android.view.inputmethod.ExtractedText;
173 import android.view.inputmethod.ExtractedTextRequest;
174 import android.view.inputmethod.InputConnection;
175 import android.view.inputmethod.InputMethodManager;
176 import android.view.inspector.InspectableProperty;
177 import android.view.inspector.InspectableProperty.EnumEntry;
178 import android.view.inspector.InspectableProperty.FlagEntry;
179 import android.view.textclassifier.TextClassification;
180 import android.view.textclassifier.TextClassificationContext;
181 import android.view.textclassifier.TextClassificationManager;
182 import android.view.textclassifier.TextClassifier;
183 import android.view.textclassifier.TextLinks;
184 import android.view.textservice.SpellCheckerSubtype;
185 import android.view.textservice.TextServicesManager;
186 import android.widget.RemoteViews.RemoteView;
187 
188 import com.android.internal.annotations.VisibleForTesting;
189 import com.android.internal.logging.MetricsLogger;
190 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
191 import com.android.internal.util.FastMath;
192 import com.android.internal.util.Preconditions;
193 import com.android.internal.widget.EditableInputConnection;
194 
195 import libcore.util.EmptyArray;
196 
197 import org.xmlpull.v1.XmlPullParserException;
198 
199 import java.io.IOException;
200 import java.lang.annotation.Retention;
201 import java.lang.annotation.RetentionPolicy;
202 import java.lang.ref.WeakReference;
203 import java.util.ArrayList;
204 import java.util.Arrays;
205 import java.util.Locale;
206 import java.util.Objects;
207 import java.util.concurrent.CompletableFuture;
208 import java.util.concurrent.TimeUnit;
209 import java.util.function.Consumer;
210 import java.util.function.Supplier;
211 
212 /**
213  * A user interface element that displays text to the user.
214  * To provide user-editable text, see {@link EditText}.
215  * <p>
216  * The following code sample shows a typical use, with an XML layout
217  * and code to modify the contents of the text view:
218  * </p>
219 
220  * <pre>
221  * &lt;LinearLayout
222        xmlns:android="http://schemas.android.com/apk/res/android"
223        android:layout_width="match_parent"
224        android:layout_height="match_parent"&gt;
225  *    &lt;TextView
226  *        android:id="@+id/text_view_id"
227  *        android:layout_height="wrap_content"
228  *        android:layout_width="wrap_content"
229  *        android:text="@string/hello" /&gt;
230  * &lt;/LinearLayout&gt;
231  * </pre>
232  * <p>
233  * This code sample demonstrates how to modify the contents of the text view
234  * defined in the previous XML layout:
235  * </p>
236  * <pre>
237  * public class MainActivity extends Activity {
238  *
239  *    protected void onCreate(Bundle savedInstanceState) {
240  *         super.onCreate(savedInstanceState);
241  *         setContentView(R.layout.activity_main);
242  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
243  *         helloTextView.setText(R.string.user_greeting);
244  *     }
245  * }
246  * </pre>
247  * <p>
248  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
249  * </p>
250  * <p>
251  * <b>XML attributes</b>
252  * <p>
253  * See {@link android.R.styleable#TextView TextView Attributes},
254  * {@link android.R.styleable#View View Attributes}
255  *
256  * @attr ref android.R.styleable#TextView_text
257  * @attr ref android.R.styleable#TextView_bufferType
258  * @attr ref android.R.styleable#TextView_hint
259  * @attr ref android.R.styleable#TextView_textColor
260  * @attr ref android.R.styleable#TextView_textColorHighlight
261  * @attr ref android.R.styleable#TextView_textColorHint
262  * @attr ref android.R.styleable#TextView_textAppearance
263  * @attr ref android.R.styleable#TextView_textColorLink
264  * @attr ref android.R.styleable#TextView_textFontWeight
265  * @attr ref android.R.styleable#TextView_textSize
266  * @attr ref android.R.styleable#TextView_textScaleX
267  * @attr ref android.R.styleable#TextView_fontFamily
268  * @attr ref android.R.styleable#TextView_typeface
269  * @attr ref android.R.styleable#TextView_textStyle
270  * @attr ref android.R.styleable#TextView_cursorVisible
271  * @attr ref android.R.styleable#TextView_maxLines
272  * @attr ref android.R.styleable#TextView_maxHeight
273  * @attr ref android.R.styleable#TextView_lines
274  * @attr ref android.R.styleable#TextView_height
275  * @attr ref android.R.styleable#TextView_minLines
276  * @attr ref android.R.styleable#TextView_minHeight
277  * @attr ref android.R.styleable#TextView_maxEms
278  * @attr ref android.R.styleable#TextView_maxWidth
279  * @attr ref android.R.styleable#TextView_ems
280  * @attr ref android.R.styleable#TextView_width
281  * @attr ref android.R.styleable#TextView_minEms
282  * @attr ref android.R.styleable#TextView_minWidth
283  * @attr ref android.R.styleable#TextView_gravity
284  * @attr ref android.R.styleable#TextView_scrollHorizontally
285  * @attr ref android.R.styleable#TextView_password
286  * @attr ref android.R.styleable#TextView_singleLine
287  * @attr ref android.R.styleable#TextView_selectAllOnFocus
288  * @attr ref android.R.styleable#TextView_includeFontPadding
289  * @attr ref android.R.styleable#TextView_maxLength
290  * @attr ref android.R.styleable#TextView_shadowColor
291  * @attr ref android.R.styleable#TextView_shadowDx
292  * @attr ref android.R.styleable#TextView_shadowDy
293  * @attr ref android.R.styleable#TextView_shadowRadius
294  * @attr ref android.R.styleable#TextView_autoLink
295  * @attr ref android.R.styleable#TextView_linksClickable
296  * @attr ref android.R.styleable#TextView_numeric
297  * @attr ref android.R.styleable#TextView_digits
298  * @attr ref android.R.styleable#TextView_phoneNumber
299  * @attr ref android.R.styleable#TextView_inputMethod
300  * @attr ref android.R.styleable#TextView_capitalize
301  * @attr ref android.R.styleable#TextView_autoText
302  * @attr ref android.R.styleable#TextView_editable
303  * @attr ref android.R.styleable#TextView_freezesText
304  * @attr ref android.R.styleable#TextView_ellipsize
305  * @attr ref android.R.styleable#TextView_drawableTop
306  * @attr ref android.R.styleable#TextView_drawableBottom
307  * @attr ref android.R.styleable#TextView_drawableRight
308  * @attr ref android.R.styleable#TextView_drawableLeft
309  * @attr ref android.R.styleable#TextView_drawableStart
310  * @attr ref android.R.styleable#TextView_drawableEnd
311  * @attr ref android.R.styleable#TextView_drawablePadding
312  * @attr ref android.R.styleable#TextView_drawableTint
313  * @attr ref android.R.styleable#TextView_drawableTintMode
314  * @attr ref android.R.styleable#TextView_lineSpacingExtra
315  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
316  * @attr ref android.R.styleable#TextView_justificationMode
317  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
318  * @attr ref android.R.styleable#TextView_inputType
319  * @attr ref android.R.styleable#TextView_imeOptions
320  * @attr ref android.R.styleable#TextView_privateImeOptions
321  * @attr ref android.R.styleable#TextView_imeActionLabel
322  * @attr ref android.R.styleable#TextView_imeActionId
323  * @attr ref android.R.styleable#TextView_editorExtras
324  * @attr ref android.R.styleable#TextView_elegantTextHeight
325  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
326  * @attr ref android.R.styleable#TextView_letterSpacing
327  * @attr ref android.R.styleable#TextView_fontFeatureSettings
328  * @attr ref android.R.styleable#TextView_fontVariationSettings
329  * @attr ref android.R.styleable#TextView_breakStrategy
330  * @attr ref android.R.styleable#TextView_hyphenationFrequency
331  * @attr ref android.R.styleable#TextView_autoSizeTextType
332  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
333  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
334  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
335  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
336  * @attr ref android.R.styleable#TextView_textCursorDrawable
337  * @attr ref android.R.styleable#TextView_textSelectHandle
338  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
339  * @attr ref android.R.styleable#TextView_textSelectHandleRight
340  * @attr ref android.R.styleable#TextView_allowUndo
341  * @attr ref android.R.styleable#TextView_enabled
342  */
343 @RemoteView
344 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
345     static final String LOG_TAG = "TextView";
346     static final boolean DEBUG_EXTRACT = false;
347     private static final float[] TEMP_POSITION = new float[2];
348 
349     // Enum for the "typeface" XML parameter.
350     // TODO: How can we get this from the XML instead of hardcoding it here?
351     /** @hide */
352     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
353     @Retention(RetentionPolicy.SOURCE)
354     public @interface XMLTypefaceAttr{}
355     private static final int DEFAULT_TYPEFACE = -1;
356     private static final int SANS = 1;
357     private static final int SERIF = 2;
358     private static final int MONOSPACE = 3;
359 
360     // Enum for the "ellipsize" XML parameter.
361     private static final int ELLIPSIZE_NOT_SET = -1;
362     private static final int ELLIPSIZE_NONE = 0;
363     private static final int ELLIPSIZE_START = 1;
364     private static final int ELLIPSIZE_MIDDLE = 2;
365     private static final int ELLIPSIZE_END = 3;
366     private static final int ELLIPSIZE_MARQUEE = 4;
367 
368     // Bitfield for the "numeric" XML parameter.
369     // TODO: How can we get this from the XML instead of hardcoding it here?
370     private static final int SIGNED = 2;
371     private static final int DECIMAL = 4;
372 
373     /**
374      * Draw marquee text with fading edges as usual
375      */
376     private static final int MARQUEE_FADE_NORMAL = 0;
377 
378     /**
379      * Draw marquee text as ellipsize end while inactive instead of with the fade.
380      * (Useful for devices where the fade can be expensive if overdone)
381      */
382     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
383 
384     /**
385      * Draw marquee text with fading edges because it is currently active/animating.
386      */
387     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
388 
389     @UnsupportedAppUsage
390     private static final int LINES = 1;
391     private static final int EMS = LINES;
392     private static final int PIXELS = 2;
393 
394     private static final RectF TEMP_RECTF = new RectF();
395 
396     /** @hide */
397     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
398     private static final int ANIMATED_SCROLL_GAP = 250;
399 
400     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
401     private static final Spanned EMPTY_SPANNED = new SpannedString("");
402 
403     private static final int CHANGE_WATCHER_PRIORITY = 100;
404 
405     // New state used to change background based on whether this TextView is multiline.
406     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
407 
408     // Accessibility action to share selected text.
409     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
410 
411     /**
412      * @hide
413      */
414     // Accessibility action start id for "process text" actions.
415     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
416 
417     /**
418      * @hide
419      */
420     static final int PROCESS_TEXT_REQUEST_CODE = 100;
421 
422     /**
423      *  Return code of {@link #doKeyDown}.
424      */
425     private static final int KEY_EVENT_NOT_HANDLED = 0;
426     private static final int KEY_EVENT_HANDLED = -1;
427     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
428     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
429 
430     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
431 
432     // System wide time for last cut, copy or text changed action.
433     static long sLastCutCopyOrTextChangedTime;
434 
435     private ColorStateList mTextColor;
436     private ColorStateList mHintTextColor;
437     private ColorStateList mLinkTextColor;
438     @ViewDebug.ExportedProperty(category = "text")
439 
440     /**
441      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
442      */
443     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
444     private int mCurTextColor;
445 
446     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
447     private int mCurHintTextColor;
448     private boolean mFreezesText;
449 
450     @UnsupportedAppUsage
451     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
452     @UnsupportedAppUsage
453     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
454 
455     @UnsupportedAppUsage
456     private float mShadowRadius;
457     @UnsupportedAppUsage
458     private float mShadowDx;
459     @UnsupportedAppUsage
460     private float mShadowDy;
461     private int mShadowColor;
462 
463     private boolean mPreDrawRegistered;
464     private boolean mPreDrawListenerDetached;
465 
466     private TextClassifier mTextClassifier;
467     private TextClassifier mTextClassificationSession;
468     private TextClassificationContext mTextClassificationContext;
469 
470     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
471     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
472     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
473     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
474     // into by the user holding the movement key down) then we shouldn't prevent the focus from
475     // changing.
476     private boolean mPreventDefaultMovement;
477 
478     private TextUtils.TruncateAt mEllipsize;
479 
480     static class Drawables {
481         static final int LEFT = 0;
482         static final int TOP = 1;
483         static final int RIGHT = 2;
484         static final int BOTTOM = 3;
485 
486         static final int DRAWABLE_NONE = -1;
487         static final int DRAWABLE_RIGHT = 0;
488         static final int DRAWABLE_LEFT = 1;
489 
490         final Rect mCompoundRect = new Rect();
491 
492         final Drawable[] mShowing = new Drawable[4];
493 
494         ColorStateList mTintList;
495         BlendMode mBlendMode;
496         boolean mHasTint;
497         boolean mHasTintMode;
498 
499         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
500         Drawable mDrawableLeftInitial, mDrawableRightInitial;
501 
502         boolean mIsRtlCompatibilityMode;
503         boolean mOverride;
504 
505         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
506                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
507 
508         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
509                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
510 
511         int mDrawablePadding;
512 
513         int mDrawableSaved = DRAWABLE_NONE;
514 
Drawables(Context context)515         public Drawables(Context context) {
516             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
517             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
518                     || !context.getApplicationInfo().hasRtlSupport();
519             mOverride = false;
520         }
521 
522         /**
523          * @return {@code true} if this object contains metadata that needs to
524          *         be retained, {@code false} otherwise
525          */
526         public boolean hasMetadata() {
527             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
528         }
529 
530         /**
531          * Updates the list of displayed drawables to account for the current
532          * layout direction.
533          *
534          * @param layoutDirection the current layout direction
535          * @return {@code true} if the displayed drawables changed
536          */
537         public boolean resolveWithLayoutDirection(int layoutDirection) {
538             final Drawable previousLeft = mShowing[Drawables.LEFT];
539             final Drawable previousRight = mShowing[Drawables.RIGHT];
540 
541             // First reset "left" and "right" drawables to their initial values
542             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
543             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
544 
545             if (mIsRtlCompatibilityMode) {
546                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
547                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
548                     mShowing[Drawables.LEFT] = mDrawableStart;
549                     mDrawableSizeLeft = mDrawableSizeStart;
550                     mDrawableHeightLeft = mDrawableHeightStart;
551                 }
552                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
553                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
554                     mShowing[Drawables.RIGHT] = mDrawableEnd;
555                     mDrawableSizeRight = mDrawableSizeEnd;
556                     mDrawableHeightRight = mDrawableHeightEnd;
557                 }
558             } else {
559                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
560                 // drawable if and only if they have been defined
561                 switch(layoutDirection) {
562                     case LAYOUT_DIRECTION_RTL:
563                         if (mOverride) {
564                             mShowing[Drawables.RIGHT] = mDrawableStart;
565                             mDrawableSizeRight = mDrawableSizeStart;
566                             mDrawableHeightRight = mDrawableHeightStart;
567 
568                             mShowing[Drawables.LEFT] = mDrawableEnd;
569                             mDrawableSizeLeft = mDrawableSizeEnd;
570                             mDrawableHeightLeft = mDrawableHeightEnd;
571                         }
572                         break;
573 
574                     case LAYOUT_DIRECTION_LTR:
575                     default:
576                         if (mOverride) {
577                             mShowing[Drawables.LEFT] = mDrawableStart;
578                             mDrawableSizeLeft = mDrawableSizeStart;
579                             mDrawableHeightLeft = mDrawableHeightStart;
580 
581                             mShowing[Drawables.RIGHT] = mDrawableEnd;
582                             mDrawableSizeRight = mDrawableSizeEnd;
583                             mDrawableHeightRight = mDrawableHeightEnd;
584                         }
585                         break;
586                 }
587             }
588 
589             applyErrorDrawableIfNeeded(layoutDirection);
590 
591             return mShowing[Drawables.LEFT] != previousLeft
592                     || mShowing[Drawables.RIGHT] != previousRight;
593         }
594 
595         public void setErrorDrawable(Drawable dr, TextView tv) {
596             if (mDrawableError != dr && mDrawableError != null) {
597                 mDrawableError.setCallback(null);
598             }
599             mDrawableError = dr;
600 
601             if (mDrawableError != null) {
602                 final Rect compoundRect = mCompoundRect;
603                 final int[] state = tv.getDrawableState();
604 
605                 mDrawableError.setState(state);
606                 mDrawableError.copyBounds(compoundRect);
607                 mDrawableError.setCallback(tv);
608                 mDrawableSizeError = compoundRect.width();
609                 mDrawableHeightError = compoundRect.height();
610             } else {
611                 mDrawableSizeError = mDrawableHeightError = 0;
612             }
613         }
614 
615         private void applyErrorDrawableIfNeeded(int layoutDirection) {
616             // first restore the initial state if needed
617             switch (mDrawableSaved) {
618                 case DRAWABLE_LEFT:
619                     mShowing[Drawables.LEFT] = mDrawableTemp;
620                     mDrawableSizeLeft = mDrawableSizeTemp;
621                     mDrawableHeightLeft = mDrawableHeightTemp;
622                     break;
623                 case DRAWABLE_RIGHT:
624                     mShowing[Drawables.RIGHT] = mDrawableTemp;
625                     mDrawableSizeRight = mDrawableSizeTemp;
626                     mDrawableHeightRight = mDrawableHeightTemp;
627                     break;
628                 case DRAWABLE_NONE:
629                 default:
630             }
631             // then, if needed, assign the Error drawable to the correct location
632             if (mDrawableError != null) {
633                 switch(layoutDirection) {
634                     case LAYOUT_DIRECTION_RTL:
635                         mDrawableSaved = DRAWABLE_LEFT;
636 
637                         mDrawableTemp = mShowing[Drawables.LEFT];
638                         mDrawableSizeTemp = mDrawableSizeLeft;
639                         mDrawableHeightTemp = mDrawableHeightLeft;
640 
641                         mShowing[Drawables.LEFT] = mDrawableError;
642                         mDrawableSizeLeft = mDrawableSizeError;
643                         mDrawableHeightLeft = mDrawableHeightError;
644                         break;
645                     case LAYOUT_DIRECTION_LTR:
646                     default:
647                         mDrawableSaved = DRAWABLE_RIGHT;
648 
649                         mDrawableTemp = mShowing[Drawables.RIGHT];
650                         mDrawableSizeTemp = mDrawableSizeRight;
651                         mDrawableHeightTemp = mDrawableHeightRight;
652 
653                         mShowing[Drawables.RIGHT] = mDrawableError;
654                         mDrawableSizeRight = mDrawableSizeError;
655                         mDrawableHeightRight = mDrawableHeightError;
656                         break;
657                 }
658             }
659         }
660     }
661 
662     @UnsupportedAppUsage
663     Drawables mDrawables;
664 
665     @UnsupportedAppUsage
666     private CharWrapper mCharWrapper;
667 
668     @UnsupportedAppUsage(trackingBug = 124050217)
669     private Marquee mMarquee;
670     @UnsupportedAppUsage
671     private boolean mRestartMarquee;
672 
673     private int mMarqueeRepeatLimit = 3;
674 
675     private int mLastLayoutDirection = -1;
676 
677     /**
678      * On some devices the fading edges add a performance penalty if used
679      * extensively in the same layout. This mode indicates how the marquee
680      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
681      */
682     @UnsupportedAppUsage
683     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
684 
685     /**
686      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
687      * the layout that should be used when the mode switches.
688      */
689     @UnsupportedAppUsage
690     private Layout mSavedMarqueeModeLayout;
691 
692     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
693     @ViewDebug.ExportedProperty(category = "text")
694     @UnsupportedAppUsage
695     private @Nullable CharSequence mText;
696     private @Nullable Spannable mSpannable;
697     private @Nullable PrecomputedText mPrecomputed;
698 
699     @UnsupportedAppUsage
700     private CharSequence mTransformed;
701     @UnsupportedAppUsage
702     private BufferType mBufferType = BufferType.NORMAL;
703 
704     private CharSequence mHint;
705     @UnsupportedAppUsage
706     private Layout mHintLayout;
707 
708     private MovementMethod mMovement;
709 
710     private TransformationMethod mTransformation;
711     @UnsupportedAppUsage
712     private boolean mAllowTransformationLengthChange;
713     @UnsupportedAppUsage
714     private ChangeWatcher mChangeWatcher;
715 
716     @UnsupportedAppUsage(trackingBug = 123769451)
717     private ArrayList<TextWatcher> mListeners;
718 
719     // display attributes
720     @UnsupportedAppUsage
721     private final TextPaint mTextPaint;
722     @UnsupportedAppUsage
723     private boolean mUserSetTextScaleX;
724     @UnsupportedAppUsage
725     private Layout mLayout;
726     private boolean mLocalesChanged = false;
727 
728     // True if setKeyListener() has been explicitly called
729     private boolean mListenerChanged = false;
730     // True if internationalized input should be used for numbers and date and time.
731     private final boolean mUseInternationalizedInput;
732     // True if fallback fonts that end up getting used should be allowed to affect line spacing.
733     /* package */ boolean mUseFallbackLineSpacing;
734 
735     @ViewDebug.ExportedProperty(category = "text")
736     @UnsupportedAppUsage
737     private int mGravity = Gravity.TOP | Gravity.START;
738     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
739     private boolean mHorizontallyScrolling;
740 
741     private int mAutoLinkMask;
742     private boolean mLinksClickable = true;
743 
744     @UnsupportedAppUsage
745     private float mSpacingMult = 1.0f;
746     @UnsupportedAppUsage
747     private float mSpacingAdd = 0.0f;
748 
749     private int mBreakStrategy;
750     private int mHyphenationFrequency;
751     private int mJustificationMode;
752 
753     @UnsupportedAppUsage
754     private int mMaximum = Integer.MAX_VALUE;
755     @UnsupportedAppUsage
756     private int mMaxMode = LINES;
757     @UnsupportedAppUsage
758     private int mMinimum = 0;
759     @UnsupportedAppUsage
760     private int mMinMode = LINES;
761 
762     @UnsupportedAppUsage
763     private int mOldMaximum = mMaximum;
764     @UnsupportedAppUsage
765     private int mOldMaxMode = mMaxMode;
766 
767     @UnsupportedAppUsage
768     private int mMaxWidth = Integer.MAX_VALUE;
769     @UnsupportedAppUsage
770     private int mMaxWidthMode = PIXELS;
771     @UnsupportedAppUsage
772     private int mMinWidth = 0;
773     @UnsupportedAppUsage
774     private int mMinWidthMode = PIXELS;
775 
776     @UnsupportedAppUsage
777     private boolean mSingleLine;
778     @UnsupportedAppUsage
779     private int mDesiredHeightAtMeasure = -1;
780     @UnsupportedAppUsage
781     private boolean mIncludePad = true;
782     private int mDeferScroll = -1;
783 
784     // tmp primitives, so we don't alloc them on each draw
785     private Rect mTempRect;
786     private long mLastScroll;
787     private Scroller mScroller;
788     private TextPaint mTempTextPaint;
789 
790     @UnsupportedAppUsage
791     private BoringLayout.Metrics mBoring;
792     @UnsupportedAppUsage
793     private BoringLayout.Metrics mHintBoring;
794     @UnsupportedAppUsage
795     private BoringLayout mSavedLayout;
796     @UnsupportedAppUsage
797     private BoringLayout mSavedHintLayout;
798 
799     @UnsupportedAppUsage
800     private TextDirectionHeuristic mTextDir;
801 
802     private InputFilter[] mFilters = NO_FILTERS;
803 
804     /**
805      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
806      * the same as {@link Process#myUserHandle()}.
807      *
808      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
809      * other apps may need to set this so that the system can use right user's resources and
810      * services such as input methods and spell checkers.</p>
811      *
812      * @see #setTextOperationUser(UserHandle)
813      */
814     @Nullable
815     private UserHandle mTextOperationUser;
816 
817     private volatile Locale mCurrentSpellCheckerLocaleCache;
818 
819     // It is possible to have a selection even when mEditor is null (programmatically set, like when
820     // a link is pressed). These highlight-related fields do not go in mEditor.
821     @UnsupportedAppUsage
822     int mHighlightColor = 0x6633B5E5;
823     private Path mHighlightPath;
824     @UnsupportedAppUsage
825     private final Paint mHighlightPaint;
826     @UnsupportedAppUsage
827     private boolean mHighlightPathBogus = true;
828 
829     // Although these fields are specific to editable text, they are not added to Editor because
830     // they are defined by the TextView's style and are theme-dependent.
831     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
832     int mCursorDrawableRes;
833     private Drawable mCursorDrawable;
834     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
835     // by removing it, but we would break apps targeting <= P that use it by reflection.
836     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
837     int mTextSelectHandleLeftRes;
838     private Drawable mTextSelectHandleLeft;
839     // Note: this might be stale if setTextSelectHandleRight 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 mTextSelectHandleRightRes;
843     private Drawable mTextSelectHandleRight;
844     // Note: this might be stale if setTextSelectHandle 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 mTextSelectHandleRes;
848     private Drawable mTextSelectHandle;
849     int mTextEditSuggestionItemLayout;
850     int mTextEditSuggestionContainerLayout;
851     int mTextEditSuggestionHighlightStyle;
852 
853     /**
854      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
855      * See {@link #createEditorIfNeeded()}.
856      */
857     @UnsupportedAppUsage
858     private Editor mEditor;
859 
860     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
861     private static final int DEVICE_PROVISIONED_NO = 1;
862     private static final int DEVICE_PROVISIONED_YES = 2;
863 
864     /**
865      * Some special options such as sharing selected text should only be shown if the device
866      * is provisioned. Only check the provisioned state once for a given view instance.
867      */
868     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
869 
870     /**
871      * The TextView does not auto-size text (default).
872      */
873     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
874 
875     /**
876      * The TextView scales text size both horizontally and vertically to fit within the
877      * container.
878      */
879     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
880 
881     /** @hide */
882     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
883             AUTO_SIZE_TEXT_TYPE_NONE,
884             AUTO_SIZE_TEXT_TYPE_UNIFORM
885     })
886     @Retention(RetentionPolicy.SOURCE)
887     public @interface AutoSizeTextType {}
888     // Default minimum size for auto-sizing text in scaled pixels.
889     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
890     // Default maximum size for auto-sizing text in scaled pixels.
891     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
892     // Default value for the step size in pixels.
893     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
894     // Use this to specify that any of the auto-size configuration int values have not been set.
895     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
896     // Auto-size text type.
897     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
898     // Specify if auto-size text is needed.
899     private boolean mNeedsAutoSizeText = false;
900     // Step size for auto-sizing in pixels.
901     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
902     // Minimum text size for auto-sizing in pixels.
903     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
904     // Maximum text size for auto-sizing in pixels.
905     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
906     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
907     // when auto-sizing text.
908     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
909     // Specifies whether auto-size should use the provided auto size steps set or if it should
910     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
911     // mAutoSizeStepGranularityInPx.
912     private boolean mHasPresetAutoSizeValues = false;
913 
914     // Autofill-related attributes
915     //
916     // Indicates whether the text was set statically or dynamically, so it can be used to
917     // sanitize autofill requests.
918     private boolean mTextSetFromXmlOrResourceId = false;
919     // Resource id used to set the text.
920     private @StringRes int mTextId = Resources.ID_NULL;
921     //
922     // End of autofill-related attributes
923 
924     /**
925      * Kick-start the font cache for the zygote process (to pay the cost of
926      * initializing freetype for our default font only once).
927      * @hide
928      */
929     public static void preloadFontCache() {
930         Paint p = new Paint();
931         p.setAntiAlias(true);
932         // Ensure that the Typeface is loaded here.
933         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
934         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
935         // since Paint.measureText can not be called without Typeface static initializer.
936         p.setTypeface(Typeface.DEFAULT);
937         // We don't care about the result, just the side-effect of measuring.
938         p.measureText("H");
939     }
940 
941     /**
942      * Interface definition for a callback to be invoked when an action is
943      * performed on the editor.
944      */
945     public interface OnEditorActionListener {
946         /**
947          * Called when an action is being performed.
948          *
949          * @param v The view that was clicked.
950          * @param actionId Identifier of the action.  This will be either the
951          * identifier you supplied, or {@link EditorInfo#IME_NULL
952          * EditorInfo.IME_NULL} if being called due to the enter key
953          * being pressed.
954          * @param event If triggered by an enter key, this is the event;
955          * otherwise, this is null.
956          * @return Return true if you have consumed the action, else false.
957          */
958         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
959     }
960 
961     public TextView(Context context) {
962         this(context, null);
963     }
964 
965     public TextView(Context context, @Nullable AttributeSet attrs) {
966         this(context, attrs, com.android.internal.R.attr.textViewStyle);
967     }
968 
969     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
970         this(context, attrs, defStyleAttr, 0);
971     }
972 
973     @SuppressWarnings("deprecation")
974     public TextView(
975             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
976         super(context, attrs, defStyleAttr, defStyleRes);
977 
978         // TextView is important by default, unless app developer overrode attribute.
979         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
980             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
981         }
982         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
983             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
984         }
985 
986         setTextInternal("");
987 
988         final Resources res = getResources();
989         final CompatibilityInfo compat = res.getCompatibilityInfo();
990 
991         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
992         mTextPaint.density = res.getDisplayMetrics().density;
993         mTextPaint.setCompatibilityScaling(compat.applicationScale);
994 
995         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
996         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
997 
998         mMovement = getDefaultMovementMethod();
999 
1000         mTransformation = null;
1001 
1002         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
1003         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
1004         attributes.mTextSize = 15;
1005         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1006         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1007         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1008 
1009         final Resources.Theme theme = context.getTheme();
1010 
1011         /*
1012          * Look the appearance up without checking first if it exists because
1013          * almost every TextView has one and it greatly simplifies the logic
1014          * to be able to parse the appearance first and then let specific tags
1015          * for this View override it.
1016          */
1017         TypedArray a = theme.obtainStyledAttributes(attrs,
1018                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1019         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1020                 attrs, a, defStyleAttr, defStyleRes);
1021         TypedArray appearance = null;
1022         int ap = a.getResourceId(
1023                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1024         a.recycle();
1025         if (ap != -1) {
1026             appearance = theme.obtainStyledAttributes(
1027                     ap, com.android.internal.R.styleable.TextAppearance);
1028             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1029                     null, appearance, 0, ap);
1030         }
1031         if (appearance != null) {
1032             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1033             attributes.mFontFamilyExplicit = false;
1034             appearance.recycle();
1035         }
1036 
1037         boolean editable = getDefaultEditable();
1038         CharSequence inputMethod = null;
1039         int numeric = 0;
1040         CharSequence digits = null;
1041         boolean phone = false;
1042         boolean autotext = false;
1043         int autocap = -1;
1044         int buffertype = 0;
1045         boolean selectallonfocus = false;
1046         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1047                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1048         ColorStateList drawableTint = null;
1049         BlendMode drawableTintMode = null;
1050         int drawablePadding = 0;
1051         int ellipsize = ELLIPSIZE_NOT_SET;
1052         boolean singleLine = false;
1053         int maxlength = -1;
1054         CharSequence text = "";
1055         CharSequence hint = null;
1056         boolean password = false;
1057         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1058         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1059         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1060         int inputType = EditorInfo.TYPE_NULL;
1061         a = theme.obtainStyledAttributes(
1062                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1063         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1064                 defStyleAttr, defStyleRes);
1065         int firstBaselineToTopHeight = -1;
1066         int lastBaselineToBottomHeight = -1;
1067         int lineHeight = -1;
1068 
1069         readTextAppearance(context, a, attributes, true /* styleArray */);
1070 
1071         int n = a.getIndexCount();
1072 
1073         // Must set id in a temporary variable because it will be reset by setText()
1074         boolean textIsSetFromXml = false;
1075         for (int i = 0; i < n; i++) {
1076             int attr = a.getIndex(i);
1077 
1078             switch (attr) {
1079                 case com.android.internal.R.styleable.TextView_editable:
1080                     editable = a.getBoolean(attr, editable);
1081                     break;
1082 
1083                 case com.android.internal.R.styleable.TextView_inputMethod:
1084                     inputMethod = a.getText(attr);
1085                     break;
1086 
1087                 case com.android.internal.R.styleable.TextView_numeric:
1088                     numeric = a.getInt(attr, numeric);
1089                     break;
1090 
1091                 case com.android.internal.R.styleable.TextView_digits:
1092                     digits = a.getText(attr);
1093                     break;
1094 
1095                 case com.android.internal.R.styleable.TextView_phoneNumber:
1096                     phone = a.getBoolean(attr, phone);
1097                     break;
1098 
1099                 case com.android.internal.R.styleable.TextView_autoText:
1100                     autotext = a.getBoolean(attr, autotext);
1101                     break;
1102 
1103                 case com.android.internal.R.styleable.TextView_capitalize:
1104                     autocap = a.getInt(attr, autocap);
1105                     break;
1106 
1107                 case com.android.internal.R.styleable.TextView_bufferType:
1108                     buffertype = a.getInt(attr, buffertype);
1109                     break;
1110 
1111                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1112                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1113                     break;
1114 
1115                 case com.android.internal.R.styleable.TextView_autoLink:
1116                     mAutoLinkMask = a.getInt(attr, 0);
1117                     break;
1118 
1119                 case com.android.internal.R.styleable.TextView_linksClickable:
1120                     mLinksClickable = a.getBoolean(attr, true);
1121                     break;
1122 
1123                 case com.android.internal.R.styleable.TextView_drawableLeft:
1124                     drawableLeft = a.getDrawable(attr);
1125                     break;
1126 
1127                 case com.android.internal.R.styleable.TextView_drawableTop:
1128                     drawableTop = a.getDrawable(attr);
1129                     break;
1130 
1131                 case com.android.internal.R.styleable.TextView_drawableRight:
1132                     drawableRight = a.getDrawable(attr);
1133                     break;
1134 
1135                 case com.android.internal.R.styleable.TextView_drawableBottom:
1136                     drawableBottom = a.getDrawable(attr);
1137                     break;
1138 
1139                 case com.android.internal.R.styleable.TextView_drawableStart:
1140                     drawableStart = a.getDrawable(attr);
1141                     break;
1142 
1143                 case com.android.internal.R.styleable.TextView_drawableEnd:
1144                     drawableEnd = a.getDrawable(attr);
1145                     break;
1146 
1147                 case com.android.internal.R.styleable.TextView_drawableTint:
1148                     drawableTint = a.getColorStateList(attr);
1149                     break;
1150 
1151                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1152                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1153                             drawableTintMode);
1154                     break;
1155 
1156                 case com.android.internal.R.styleable.TextView_drawablePadding:
1157                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1158                     break;
1159 
1160                 case com.android.internal.R.styleable.TextView_maxLines:
1161                     setMaxLines(a.getInt(attr, -1));
1162                     break;
1163 
1164                 case com.android.internal.R.styleable.TextView_maxHeight:
1165                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1166                     break;
1167 
1168                 case com.android.internal.R.styleable.TextView_lines:
1169                     setLines(a.getInt(attr, -1));
1170                     break;
1171 
1172                 case com.android.internal.R.styleable.TextView_height:
1173                     setHeight(a.getDimensionPixelSize(attr, -1));
1174                     break;
1175 
1176                 case com.android.internal.R.styleable.TextView_minLines:
1177                     setMinLines(a.getInt(attr, -1));
1178                     break;
1179 
1180                 case com.android.internal.R.styleable.TextView_minHeight:
1181                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1182                     break;
1183 
1184                 case com.android.internal.R.styleable.TextView_maxEms:
1185                     setMaxEms(a.getInt(attr, -1));
1186                     break;
1187 
1188                 case com.android.internal.R.styleable.TextView_maxWidth:
1189                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1190                     break;
1191 
1192                 case com.android.internal.R.styleable.TextView_ems:
1193                     setEms(a.getInt(attr, -1));
1194                     break;
1195 
1196                 case com.android.internal.R.styleable.TextView_width:
1197                     setWidth(a.getDimensionPixelSize(attr, -1));
1198                     break;
1199 
1200                 case com.android.internal.R.styleable.TextView_minEms:
1201                     setMinEms(a.getInt(attr, -1));
1202                     break;
1203 
1204                 case com.android.internal.R.styleable.TextView_minWidth:
1205                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1206                     break;
1207 
1208                 case com.android.internal.R.styleable.TextView_gravity:
1209                     setGravity(a.getInt(attr, -1));
1210                     break;
1211 
1212                 case com.android.internal.R.styleable.TextView_hint:
1213                     hint = a.getText(attr);
1214                     break;
1215 
1216                 case com.android.internal.R.styleable.TextView_text:
1217                     textIsSetFromXml = true;
1218                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1219                     text = a.getText(attr);
1220                     break;
1221 
1222                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1223                     if (a.getBoolean(attr, false)) {
1224                         setHorizontallyScrolling(true);
1225                     }
1226                     break;
1227 
1228                 case com.android.internal.R.styleable.TextView_singleLine:
1229                     singleLine = a.getBoolean(attr, singleLine);
1230                     break;
1231 
1232                 case com.android.internal.R.styleable.TextView_ellipsize:
1233                     ellipsize = a.getInt(attr, ellipsize);
1234                     break;
1235 
1236                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1237                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1238                     break;
1239 
1240                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1241                     if (!a.getBoolean(attr, true)) {
1242                         setIncludeFontPadding(false);
1243                     }
1244                     break;
1245 
1246                 case com.android.internal.R.styleable.TextView_cursorVisible:
1247                     if (!a.getBoolean(attr, true)) {
1248                         setCursorVisible(false);
1249                     }
1250                     break;
1251 
1252                 case com.android.internal.R.styleable.TextView_maxLength:
1253                     maxlength = a.getInt(attr, -1);
1254                     break;
1255 
1256                 case com.android.internal.R.styleable.TextView_textScaleX:
1257                     setTextScaleX(a.getFloat(attr, 1.0f));
1258                     break;
1259 
1260                 case com.android.internal.R.styleable.TextView_freezesText:
1261                     mFreezesText = a.getBoolean(attr, false);
1262                     break;
1263 
1264                 case com.android.internal.R.styleable.TextView_enabled:
1265                     setEnabled(a.getBoolean(attr, isEnabled()));
1266                     break;
1267 
1268                 case com.android.internal.R.styleable.TextView_password:
1269                     password = a.getBoolean(attr, password);
1270                     break;
1271 
1272                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1273                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1274                     break;
1275 
1276                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1277                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1278                     break;
1279 
1280                 case com.android.internal.R.styleable.TextView_inputType:
1281                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1282                     break;
1283 
1284                 case com.android.internal.R.styleable.TextView_allowUndo:
1285                     createEditorIfNeeded();
1286                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1287                     break;
1288 
1289                 case com.android.internal.R.styleable.TextView_imeOptions:
1290                     createEditorIfNeeded();
1291                     mEditor.createInputContentTypeIfNeeded();
1292                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1293                             mEditor.mInputContentType.imeOptions);
1294                     break;
1295 
1296                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1297                     createEditorIfNeeded();
1298                     mEditor.createInputContentTypeIfNeeded();
1299                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1300                     break;
1301 
1302                 case com.android.internal.R.styleable.TextView_imeActionId:
1303                     createEditorIfNeeded();
1304                     mEditor.createInputContentTypeIfNeeded();
1305                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1306                             mEditor.mInputContentType.imeActionId);
1307                     break;
1308 
1309                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1310                     setPrivateImeOptions(a.getString(attr));
1311                     break;
1312 
1313                 case com.android.internal.R.styleable.TextView_editorExtras:
1314                     try {
1315                         setInputExtras(a.getResourceId(attr, 0));
1316                     } catch (XmlPullParserException e) {
1317                         Log.w(LOG_TAG, "Failure reading input extras", e);
1318                     } catch (IOException e) {
1319                         Log.w(LOG_TAG, "Failure reading input extras", e);
1320                     }
1321                     break;
1322 
1323                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1324                     mCursorDrawableRes = a.getResourceId(attr, 0);
1325                     break;
1326 
1327                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1328                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1329                     break;
1330 
1331                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1332                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1333                     break;
1334 
1335                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1336                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1337                     break;
1338 
1339                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1340                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1341                     break;
1342 
1343                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1344                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1345                     break;
1346 
1347                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1348                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1349                     break;
1350 
1351                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1352                     setTextIsSelectable(a.getBoolean(attr, false));
1353                     break;
1354 
1355                 case com.android.internal.R.styleable.TextView_breakStrategy:
1356                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1357                     break;
1358 
1359                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1360                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1361                     break;
1362 
1363                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1364                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1365                     break;
1366 
1367                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1368                     autoSizeStepGranularityInPx = a.getDimension(attr,
1369                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1370                     break;
1371 
1372                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1373                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1374                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1375                     break;
1376 
1377                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1378                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1379                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1380                     break;
1381 
1382                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1383                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1384                     if (autoSizeStepSizeArrayResId > 0) {
1385                         final TypedArray autoSizePresetTextSizes = a.getResources()
1386                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1387                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1388                         autoSizePresetTextSizes.recycle();
1389                     }
1390                     break;
1391                 case com.android.internal.R.styleable.TextView_justificationMode:
1392                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1393                     break;
1394 
1395                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1396                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1397                     break;
1398 
1399                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1400                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1401                     break;
1402 
1403                 case com.android.internal.R.styleable.TextView_lineHeight:
1404                     lineHeight = a.getDimensionPixelSize(attr, -1);
1405                     break;
1406             }
1407         }
1408 
1409         a.recycle();
1410 
1411         BufferType bufferType = BufferType.EDITABLE;
1412 
1413         final int variation =
1414                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1415         final boolean passwordInputType = variation
1416                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1417         final boolean webPasswordInputType = variation
1418                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1419         final boolean numberPasswordInputType = variation
1420                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1421 
1422         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1423         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1424         mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
1425 
1426         if (inputMethod != null) {
1427             Class<?> c;
1428 
1429             try {
1430                 c = Class.forName(inputMethod.toString());
1431             } catch (ClassNotFoundException ex) {
1432                 throw new RuntimeException(ex);
1433             }
1434 
1435             try {
1436                 createEditorIfNeeded();
1437                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1438             } catch (InstantiationException ex) {
1439                 throw new RuntimeException(ex);
1440             } catch (IllegalAccessException ex) {
1441                 throw new RuntimeException(ex);
1442             }
1443             try {
1444                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1445                         ? inputType
1446                         : mEditor.mKeyListener.getInputType();
1447             } catch (IncompatibleClassChangeError e) {
1448                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1449             }
1450         } else if (digits != null) {
1451             createEditorIfNeeded();
1452             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1453             // If no input type was specified, we will default to generic
1454             // text, since we can't tell the IME about the set of digits
1455             // that was selected.
1456             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1457                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1458         } else if (inputType != EditorInfo.TYPE_NULL) {
1459             setInputType(inputType, true);
1460             // If set, the input type overrides what was set using the deprecated singleLine flag.
1461             singleLine = !isMultilineInputType(inputType);
1462         } else if (phone) {
1463             createEditorIfNeeded();
1464             mEditor.mKeyListener = DialerKeyListener.getInstance();
1465             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1466         } else if (numeric != 0) {
1467             createEditorIfNeeded();
1468             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1469                     null,  // locale
1470                     (numeric & SIGNED) != 0,
1471                     (numeric & DECIMAL) != 0);
1472             inputType = mEditor.mKeyListener.getInputType();
1473             mEditor.mInputType = inputType;
1474         } else if (autotext || autocap != -1) {
1475             TextKeyListener.Capitalize cap;
1476 
1477             inputType = EditorInfo.TYPE_CLASS_TEXT;
1478 
1479             switch (autocap) {
1480                 case 1:
1481                     cap = TextKeyListener.Capitalize.SENTENCES;
1482                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1483                     break;
1484 
1485                 case 2:
1486                     cap = TextKeyListener.Capitalize.WORDS;
1487                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1488                     break;
1489 
1490                 case 3:
1491                     cap = TextKeyListener.Capitalize.CHARACTERS;
1492                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1493                     break;
1494 
1495                 default:
1496                     cap = TextKeyListener.Capitalize.NONE;
1497                     break;
1498             }
1499 
1500             createEditorIfNeeded();
1501             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1502             mEditor.mInputType = inputType;
1503         } else if (editable) {
1504             createEditorIfNeeded();
1505             mEditor.mKeyListener = TextKeyListener.getInstance();
1506             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1507         } else if (isTextSelectable()) {
1508             // Prevent text changes from keyboard.
1509             if (mEditor != null) {
1510                 mEditor.mKeyListener = null;
1511                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1512             }
1513             bufferType = BufferType.SPANNABLE;
1514             // So that selection can be changed using arrow keys and touch is handled.
1515             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1516         } else {
1517             if (mEditor != null) mEditor.mKeyListener = null;
1518 
1519             switch (buffertype) {
1520                 case 0:
1521                     bufferType = BufferType.NORMAL;
1522                     break;
1523                 case 1:
1524                     bufferType = BufferType.SPANNABLE;
1525                     break;
1526                 case 2:
1527                     bufferType = BufferType.EDITABLE;
1528                     break;
1529             }
1530         }
1531 
1532         if (mEditor != null) {
1533             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1534                     numberPasswordInputType);
1535         }
1536 
1537         if (selectallonfocus) {
1538             createEditorIfNeeded();
1539             mEditor.mSelectAllOnFocus = true;
1540 
1541             if (bufferType == BufferType.NORMAL) {
1542                 bufferType = BufferType.SPANNABLE;
1543             }
1544         }
1545 
1546         // Set up the tint (if needed) before setting the drawables so that it
1547         // gets applied correctly.
1548         if (drawableTint != null || drawableTintMode != null) {
1549             if (mDrawables == null) {
1550                 mDrawables = new Drawables(context);
1551             }
1552             if (drawableTint != null) {
1553                 mDrawables.mTintList = drawableTint;
1554                 mDrawables.mHasTint = true;
1555             }
1556             if (drawableTintMode != null) {
1557                 mDrawables.mBlendMode = drawableTintMode;
1558                 mDrawables.mHasTintMode = true;
1559             }
1560         }
1561 
1562         // This call will save the initial left/right drawables
1563         setCompoundDrawablesWithIntrinsicBounds(
1564                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1565         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1566         setCompoundDrawablePadding(drawablePadding);
1567 
1568         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1569         // of lines of height are unchanged for multi-line TextViews.
1570         setInputTypeSingleLine(singleLine);
1571         applySingleLine(singleLine, singleLine, singleLine);
1572 
1573         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1574             ellipsize = ELLIPSIZE_END;
1575         }
1576 
1577         switch (ellipsize) {
1578             case ELLIPSIZE_START:
1579                 setEllipsize(TextUtils.TruncateAt.START);
1580                 break;
1581             case ELLIPSIZE_MIDDLE:
1582                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1583                 break;
1584             case ELLIPSIZE_END:
1585                 setEllipsize(TextUtils.TruncateAt.END);
1586                 break;
1587             case ELLIPSIZE_MARQUEE:
1588                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1589                     setHorizontalFadingEdgeEnabled(true);
1590                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1591                 } else {
1592                     setHorizontalFadingEdgeEnabled(false);
1593                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1594                 }
1595                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1596                 break;
1597         }
1598 
1599         final boolean isPassword = password || passwordInputType || webPasswordInputType
1600                 || numberPasswordInputType;
1601         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1602                 && (mEditor.mInputType
1603                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1604                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1605         if (isMonospaceEnforced) {
1606             attributes.mTypefaceIndex = MONOSPACE;
1607         }
1608 
1609         applyTextAppearance(attributes);
1610 
1611         if (isPassword) {
1612             setTransformationMethod(PasswordTransformationMethod.getInstance());
1613         }
1614 
1615         if (maxlength >= 0) {
1616             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1617         } else {
1618             setFilters(NO_FILTERS);
1619         }
1620 
1621         setText(text, bufferType);
1622         if (mText == null) {
1623             mText = "";
1624         }
1625         if (mTransformed == null) {
1626             mTransformed = "";
1627         }
1628 
1629         if (textIsSetFromXml) {
1630             mTextSetFromXmlOrResourceId = true;
1631         }
1632 
1633         if (hint != null) setHint(hint);
1634 
1635         /*
1636          * Views are not normally clickable unless specified to be.
1637          * However, TextViews that have input or movement methods *are*
1638          * clickable by default. By setting clickable here, we implicitly set focusable as well
1639          * if not overridden by the developer.
1640          */
1641         a = context.obtainStyledAttributes(
1642                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1643         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1644         boolean clickable = canInputOrMove || isClickable();
1645         boolean longClickable = canInputOrMove || isLongClickable();
1646         int focusable = getFocusable();
1647 
1648         n = a.getIndexCount();
1649         for (int i = 0; i < n; i++) {
1650             int attr = a.getIndex(i);
1651 
1652             switch (attr) {
1653                 case com.android.internal.R.styleable.View_focusable:
1654                     TypedValue val = new TypedValue();
1655                     if (a.getValue(attr, val)) {
1656                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1657                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1658                                 : val.data;
1659                     }
1660                     break;
1661 
1662                 case com.android.internal.R.styleable.View_clickable:
1663                     clickable = a.getBoolean(attr, clickable);
1664                     break;
1665 
1666                 case com.android.internal.R.styleable.View_longClickable:
1667                     longClickable = a.getBoolean(attr, longClickable);
1668                     break;
1669             }
1670         }
1671         a.recycle();
1672 
1673         // Some apps were relying on the undefined behavior of focusable winning over
1674         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1675         // when starting with EditText and setting only focusable=false). To keep those apps from
1676         // breaking, re-apply the focusable attribute here.
1677         if (focusable != getFocusable()) {
1678             setFocusable(focusable);
1679         }
1680         setClickable(clickable);
1681         setLongClickable(longClickable);
1682 
1683         if (mEditor != null) mEditor.prepareCursorControllers();
1684 
1685         // If not explicitly specified this view is important for accessibility.
1686         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1687             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1688         }
1689 
1690         if (supportsAutoSizeText()) {
1691             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1692                 // If uniform auto-size has been specified but preset values have not been set then
1693                 // replace the auto-size configuration values that have not been specified with the
1694                 // defaults.
1695                 if (!mHasPresetAutoSizeValues) {
1696                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1697 
1698                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1699                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1700                                 TypedValue.COMPLEX_UNIT_SP,
1701                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1702                                 displayMetrics);
1703                     }
1704 
1705                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1706                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1707                                 TypedValue.COMPLEX_UNIT_SP,
1708                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1709                                 displayMetrics);
1710                     }
1711 
1712                     if (autoSizeStepGranularityInPx
1713                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1714                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1715                     }
1716 
1717                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1718                             autoSizeMaxTextSizeInPx,
1719                             autoSizeStepGranularityInPx);
1720                 }
1721 
1722                 setupAutoSizeText();
1723             }
1724         } else {
1725             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1726         }
1727 
1728         if (firstBaselineToTopHeight >= 0) {
1729             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1730         }
1731         if (lastBaselineToBottomHeight >= 0) {
1732             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1733         }
1734         if (lineHeight >= 0) {
1735             setLineHeight(lineHeight);
1736         }
1737     }
1738 
1739     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)1740     private void setTextInternal(@Nullable CharSequence text) {
1741         mText = text;
1742         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
1743         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
1744     }
1745 
1746     /**
1747      * Specify whether this widget should automatically scale the text to try to perfectly fit
1748      * within the layout bounds by using the default auto-size configuration.
1749      *
1750      * @param autoSizeTextType the type of auto-size. Must be one of
1751      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1752      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1753      *
1754      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1755      *
1756      * @attr ref android.R.styleable#TextView_autoSizeTextType
1757      *
1758      * @see #getAutoSizeTextType()
1759      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1760     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1761         if (supportsAutoSizeText()) {
1762             switch (autoSizeTextType) {
1763                 case AUTO_SIZE_TEXT_TYPE_NONE:
1764                     clearAutoSizeConfiguration();
1765                     break;
1766                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1767                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1768                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1769                             TypedValue.COMPLEX_UNIT_SP,
1770                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1771                             displayMetrics);
1772                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1773                             TypedValue.COMPLEX_UNIT_SP,
1774                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1775                             displayMetrics);
1776 
1777                     validateAndSetAutoSizeTextTypeUniformConfiguration(
1778                             autoSizeMinTextSizeInPx,
1779                             autoSizeMaxTextSizeInPx,
1780                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1781                     if (setupAutoSizeText()) {
1782                         autoSizeText();
1783                         invalidate();
1784                     }
1785                     break;
1786                 default:
1787                     throw new IllegalArgumentException(
1788                             "Unknown auto-size text type: " + autoSizeTextType);
1789             }
1790         }
1791     }
1792 
1793     /**
1794      * Specify whether this widget should automatically scale the text to try to perfectly fit
1795      * within the layout bounds. If all the configuration params are valid the type of auto-size is
1796      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1797      *
1798      * @param autoSizeMinTextSize the minimum text size available for auto-size
1799      * @param autoSizeMaxTextSize the maximum text size available for auto-size
1800      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1801      *                                the minimum and maximum text size in order to build the set of
1802      *                                text sizes the system uses to choose from when auto-sizing
1803      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1804      *             possible dimension units
1805      *
1806      * @throws IllegalArgumentException if any of the configuration params are invalid.
1807      *
1808      * @attr ref android.R.styleable#TextView_autoSizeTextType
1809      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1810      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1811      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1812      *
1813      * @see #setAutoSizeTextTypeWithDefaults(int)
1814      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1815      * @see #getAutoSizeMinTextSize()
1816      * @see #getAutoSizeMaxTextSize()
1817      * @see #getAutoSizeStepGranularity()
1818      * @see #getAutoSizeTextAvailableSizes()
1819      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1820     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1821             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1822         if (supportsAutoSizeText()) {
1823             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1824             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1825                     unit, autoSizeMinTextSize, displayMetrics);
1826             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1827                     unit, autoSizeMaxTextSize, displayMetrics);
1828             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1829                     unit, autoSizeStepGranularity, displayMetrics);
1830 
1831             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1832                     autoSizeMaxTextSizeInPx,
1833                     autoSizeStepGranularityInPx);
1834 
1835             if (setupAutoSizeText()) {
1836                 autoSizeText();
1837                 invalidate();
1838             }
1839         }
1840     }
1841 
1842     /**
1843      * Specify whether this widget should automatically scale the text to try to perfectly fit
1844      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1845      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1846      *
1847      * @param presetSizes an {@code int} array of sizes in pixels
1848      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1849      *             the possible dimension units
1850      *
1851      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1852      *
1853      * @attr ref android.R.styleable#TextView_autoSizeTextType
1854      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1855      *
1856      * @see #setAutoSizeTextTypeWithDefaults(int)
1857      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1858      * @see #getAutoSizeMinTextSize()
1859      * @see #getAutoSizeMaxTextSize()
1860      * @see #getAutoSizeTextAvailableSizes()
1861      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1862     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1863         if (supportsAutoSizeText()) {
1864             final int presetSizesLength = presetSizes.length;
1865             if (presetSizesLength > 0) {
1866                 int[] presetSizesInPx = new int[presetSizesLength];
1867 
1868                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1869                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1870                 } else {
1871                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1872                     // Convert all to sizes to pixels.
1873                     for (int i = 0; i < presetSizesLength; i++) {
1874                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
1875                             presetSizes[i], displayMetrics));
1876                     }
1877                 }
1878 
1879                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1880                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
1881                     throw new IllegalArgumentException("None of the preset sizes is valid: "
1882                             + Arrays.toString(presetSizes));
1883                 }
1884             } else {
1885                 mHasPresetAutoSizeValues = false;
1886             }
1887 
1888             if (setupAutoSizeText()) {
1889                 autoSizeText();
1890                 invalidate();
1891             }
1892         }
1893     }
1894 
1895     /**
1896      * Returns the type of auto-size set for this widget.
1897      *
1898      * @return an {@code int} corresponding to one of the auto-size types:
1899      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1900      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1901      *
1902      * @attr ref android.R.styleable#TextView_autoSizeTextType
1903      *
1904      * @see #setAutoSizeTextTypeWithDefaults(int)
1905      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1906      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1907      */
1908     @InspectableProperty(enumMapping = {
1909             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
1910             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
1911     })
1912     @AutoSizeTextType
getAutoSizeTextType()1913     public int getAutoSizeTextType() {
1914         return mAutoSizeTextType;
1915     }
1916 
1917     /**
1918      * @return the current auto-size step granularity in pixels.
1919      *
1920      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1921      *
1922      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1923      */
1924     @InspectableProperty
getAutoSizeStepGranularity()1925     public int getAutoSizeStepGranularity() {
1926         return Math.round(mAutoSizeStepGranularityInPx);
1927     }
1928 
1929     /**
1930      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
1931      *         if auto-size has not been configured this function returns {@code -1}.
1932      *
1933      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1934      *
1935      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1936      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1937      */
1938     @InspectableProperty
getAutoSizeMinTextSize()1939     public int getAutoSizeMinTextSize() {
1940         return Math.round(mAutoSizeMinTextSizeInPx);
1941     }
1942 
1943     /**
1944      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
1945      *         if auto-size has not been configured this function returns {@code -1}.
1946      *
1947      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1948      *
1949      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1950      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1951      */
1952     @InspectableProperty
getAutoSizeMaxTextSize()1953     public int getAutoSizeMaxTextSize() {
1954         return Math.round(mAutoSizeMaxTextSizeInPx);
1955     }
1956 
1957     /**
1958      * @return the current auto-size {@code int} sizes array (in pixels).
1959      *
1960      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1961      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1962      */
getAutoSizeTextAvailableSizes()1963     public int[] getAutoSizeTextAvailableSizes() {
1964         return mAutoSizeTextSizesInPx;
1965     }
1966 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)1967     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
1968         final int textSizesLength = textSizes.length();
1969         final int[] parsedSizes = new int[textSizesLength];
1970 
1971         if (textSizesLength > 0) {
1972             for (int i = 0; i < textSizesLength; i++) {
1973                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
1974             }
1975             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
1976             setupAutoSizeUniformPresetSizesConfiguration();
1977         }
1978     }
1979 
setupAutoSizeUniformPresetSizesConfiguration()1980     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
1981         final int sizesLength = mAutoSizeTextSizesInPx.length;
1982         mHasPresetAutoSizeValues = sizesLength > 0;
1983         if (mHasPresetAutoSizeValues) {
1984             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1985             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
1986             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
1987             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1988         }
1989         return mHasPresetAutoSizeValues;
1990     }
1991 
1992     /**
1993      * If all params are valid then save the auto-size configuration.
1994      *
1995      * @throws IllegalArgumentException if any of the params are invalid
1996      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)1997     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
1998             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
1999         // First validate.
2000         if (autoSizeMinTextSizeInPx <= 0) {
2001             throw new IllegalArgumentException("Minimum auto-size text size ("
2002                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
2003         }
2004 
2005         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2006             throw new IllegalArgumentException("Maximum auto-size text size ("
2007                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2008                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2009         }
2010 
2011         if (autoSizeStepGranularityInPx <= 0) {
2012             throw new IllegalArgumentException("The auto-size step granularity ("
2013                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2014         }
2015 
2016         // All good, persist the configuration.
2017         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2018         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2019         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2020         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2021         mHasPresetAutoSizeValues = false;
2022     }
2023 
clearAutoSizeConfiguration()2024     private void clearAutoSizeConfiguration() {
2025         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2026         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2027         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2028         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2029         mAutoSizeTextSizesInPx = EmptyArray.INT;
2030         mNeedsAutoSizeText = false;
2031     }
2032 
2033     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)2034     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2035         final int presetValuesLength = presetValues.length;
2036         if (presetValuesLength == 0) {
2037             return presetValues;
2038         }
2039         Arrays.sort(presetValues);
2040 
2041         final IntArray uniqueValidSizes = new IntArray();
2042         for (int i = 0; i < presetValuesLength; i++) {
2043             final int currentPresetValue = presetValues[i];
2044 
2045             if (currentPresetValue > 0
2046                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2047                 uniqueValidSizes.add(currentPresetValue);
2048             }
2049         }
2050 
2051         return presetValuesLength == uniqueValidSizes.size()
2052             ? presetValues
2053             : uniqueValidSizes.toArray();
2054     }
2055 
setupAutoSizeText()2056     private boolean setupAutoSizeText() {
2057         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2058             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2059             // not have a predefined set of sizes or if the current sizes array is empty.
2060             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2061                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2062                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2063                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2064                 for (int i = 0; i < autoSizeValuesLength; i++) {
2065                     autoSizeTextSizesInPx[i] = Math.round(
2066                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2067                 }
2068                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2069             }
2070 
2071             mNeedsAutoSizeText = true;
2072         } else {
2073             mNeedsAutoSizeText = false;
2074         }
2075 
2076         return mNeedsAutoSizeText;
2077     }
2078 
parseDimensionArray(TypedArray dimens)2079     private int[] parseDimensionArray(TypedArray dimens) {
2080         if (dimens == null) {
2081             return null;
2082         }
2083         int[] result = new int[dimens.length()];
2084         for (int i = 0; i < result.length; i++) {
2085             result[i] = dimens.getDimensionPixelSize(i, 0);
2086         }
2087         return result;
2088     }
2089 
2090     /**
2091      * @hide
2092      */
2093     @Override
onActivityResult(int requestCode, int resultCode, Intent data)2094     public void onActivityResult(int requestCode, int resultCode, Intent data) {
2095         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2096             if (resultCode == Activity.RESULT_OK && data != null) {
2097                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2098                 if (result != null) {
2099                     if (isTextEditable()) {
2100                         replaceSelectionWithText(result);
2101                         if (mEditor != null) {
2102                             mEditor.refreshTextActionMode();
2103                         }
2104                     } else {
2105                         if (result.length() > 0) {
2106                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2107                                 .show();
2108                         }
2109                     }
2110                 }
2111             } else if (mSpannable != null) {
2112                 // Reset the selection.
2113                 Selection.setSelection(mSpannable, getSelectionEnd());
2114             }
2115         }
2116     }
2117 
2118     /**
2119      * Sets the Typeface taking into account the given attributes.
2120      *
2121      * @param typeface a typeface
2122      * @param familyName family name string, e.g. "serif"
2123      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2124      * @param style a typeface style
2125      * @param weight a weight value for the Typeface or -1 if not specified.
2126      */
setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2127     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2128             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2129             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2130         if (typeface == null && familyName != null) {
2131             // Lookup normal Typeface from system font map.
2132             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2133             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2134         } else if (typeface != null) {
2135             resolveStyleAndSetTypeface(typeface, style, weight);
2136         } else {  // both typeface and familyName is null.
2137             switch (typefaceIndex) {
2138                 case SANS:
2139                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2140                     break;
2141                 case SERIF:
2142                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2143                     break;
2144                 case MONOSPACE:
2145                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2146                     break;
2147                 case DEFAULT_TYPEFACE:
2148                 default:
2149                     resolveStyleAndSetTypeface(null, style, weight);
2150                     break;
2151             }
2152         }
2153     }
2154 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2155     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2156             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2157         if (weight >= 0) {
2158             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2159             final boolean italic = (style & Typeface.ITALIC) != 0;
2160             setTypeface(Typeface.create(typeface, weight, italic));
2161         } else {
2162             setTypeface(typeface, style);
2163         }
2164     }
2165 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2166     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2167         boolean hasRelativeDrawables = (start != null) || (end != null);
2168         if (hasRelativeDrawables) {
2169             Drawables dr = mDrawables;
2170             if (dr == null) {
2171                 mDrawables = dr = new Drawables(getContext());
2172             }
2173             mDrawables.mOverride = true;
2174             final Rect compoundRect = dr.mCompoundRect;
2175             int[] state = getDrawableState();
2176             if (start != null) {
2177                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2178                 start.setState(state);
2179                 start.copyBounds(compoundRect);
2180                 start.setCallback(this);
2181 
2182                 dr.mDrawableStart = start;
2183                 dr.mDrawableSizeStart = compoundRect.width();
2184                 dr.mDrawableHeightStart = compoundRect.height();
2185             } else {
2186                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2187             }
2188             if (end != null) {
2189                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2190                 end.setState(state);
2191                 end.copyBounds(compoundRect);
2192                 end.setCallback(this);
2193 
2194                 dr.mDrawableEnd = end;
2195                 dr.mDrawableSizeEnd = compoundRect.width();
2196                 dr.mDrawableHeightEnd = compoundRect.height();
2197             } else {
2198                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2199             }
2200             resetResolvedDrawables();
2201             resolveDrawables();
2202             applyCompoundDrawableTint();
2203         }
2204     }
2205 
2206     @android.view.RemotableViewMethod
2207     @Override
setEnabled(boolean enabled)2208     public void setEnabled(boolean enabled) {
2209         if (enabled == isEnabled()) {
2210             return;
2211         }
2212 
2213         if (!enabled) {
2214             // Hide the soft input if the currently active TextView is disabled
2215             InputMethodManager imm = getInputMethodManager();
2216             if (imm != null && imm.isActive(this)) {
2217                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2218             }
2219         }
2220 
2221         super.setEnabled(enabled);
2222 
2223         if (enabled) {
2224             // Make sure IME is updated with current editor info.
2225             InputMethodManager imm = getInputMethodManager();
2226             if (imm != null) imm.restartInput(this);
2227         }
2228 
2229         // Will change text color
2230         if (mEditor != null) {
2231             mEditor.invalidateTextDisplayList();
2232             mEditor.prepareCursorControllers();
2233 
2234             // start or stop the cursor blinking as appropriate
2235             mEditor.makeBlink();
2236         }
2237     }
2238 
2239     /**
2240      * Sets the typeface and style in which the text should be displayed,
2241      * and turns on the fake bold and italic bits in the Paint if the
2242      * Typeface that you provided does not have all the bits in the
2243      * style that you specified.
2244      *
2245      * @attr ref android.R.styleable#TextView_typeface
2246      * @attr ref android.R.styleable#TextView_textStyle
2247      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2248     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2249         if (style > 0) {
2250             if (tf == null) {
2251                 tf = Typeface.defaultFromStyle(style);
2252             } else {
2253                 tf = Typeface.create(tf, style);
2254             }
2255 
2256             setTypeface(tf);
2257             // now compute what (if any) algorithmic styling is needed
2258             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2259             int need = style & ~typefaceStyle;
2260             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2261             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2262         } else {
2263             mTextPaint.setFakeBoldText(false);
2264             mTextPaint.setTextSkewX(0);
2265             setTypeface(tf);
2266         }
2267     }
2268 
2269     /**
2270      * Subclasses override this to specify that they have a KeyListener
2271      * by default even if not specifically called for in the XML options.
2272      */
getDefaultEditable()2273     protected boolean getDefaultEditable() {
2274         return false;
2275     }
2276 
2277     /**
2278      * Subclasses override this to specify a default movement method.
2279      */
getDefaultMovementMethod()2280     protected MovementMethod getDefaultMovementMethod() {
2281         return null;
2282     }
2283 
2284     /**
2285      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2286      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2287      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2288      * the return value from this method to Spannable or Editable, respectively.
2289      *
2290      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2291      * should make your own copy first.</p>
2292      *
2293      * @return The text displayed by the text view.
2294      * @attr ref android.R.styleable#TextView_text
2295      */
2296     @ViewDebug.CapturedViewProperty
2297     @InspectableProperty
getText()2298     public CharSequence getText() {
2299         return mText;
2300     }
2301 
2302     /**
2303      * Returns the length, in characters, of the text managed by this TextView
2304      * @return The length of the text managed by the TextView in characters.
2305      */
length()2306     public int length() {
2307         return mText.length();
2308     }
2309 
2310     /**
2311      * Return the text that TextView is displaying as an Editable object. If the text is not
2312      * editable, null is returned.
2313      *
2314      * @see #getText
2315      */
getEditableText()2316     public Editable getEditableText() {
2317         return (mText instanceof Editable) ? (Editable) mText : null;
2318     }
2319 
2320     /**
2321      * @hide
2322      */
2323     @VisibleForTesting
getTransformed()2324     public CharSequence getTransformed() {
2325         return mTransformed;
2326     }
2327 
2328     /**
2329      * Gets the vertical distance between lines of text, in pixels.
2330      * Note that markup within the text can cause individual lines
2331      * to be taller or shorter than this height, and the layout may
2332      * contain additional first-or last-line padding.
2333      * @return The height of one standard line in pixels.
2334      */
2335     @InspectableProperty
getLineHeight()2336     public int getLineHeight() {
2337         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2338     }
2339 
2340     /**
2341      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2342      * This value can be null if the text or width has recently changed.
2343      * @return The Layout that is currently being used to display the text.
2344      */
getLayout()2345     public final Layout getLayout() {
2346         return mLayout;
2347     }
2348 
2349     /**
2350      * @return the {@link android.text.Layout} that is currently being used to
2351      * display the hint text. This can be null.
2352      */
2353     @UnsupportedAppUsage
getHintLayout()2354     final Layout getHintLayout() {
2355         return mHintLayout;
2356     }
2357 
2358     /**
2359      * Retrieve the {@link android.content.UndoManager} that is currently associated
2360      * with this TextView.  By default there is no associated UndoManager, so null
2361      * is returned.  One can be associated with the TextView through
2362      * {@link #setUndoManager(android.content.UndoManager, String)}
2363      *
2364      * @hide
2365      */
getUndoManager()2366     public final UndoManager getUndoManager() {
2367         // TODO: Consider supporting a global undo manager.
2368         throw new UnsupportedOperationException("not implemented");
2369     }
2370 
2371 
2372     /**
2373      * @hide
2374      */
2375     @VisibleForTesting
getEditorForTesting()2376     public final Editor getEditorForTesting() {
2377         return mEditor;
2378     }
2379 
2380     /**
2381      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2382      * done, all edit operations on the TextView will result in appropriate
2383      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2384      * stack.
2385      *
2386      * @param undoManager The {@link android.content.UndoManager} to associate with
2387      * this TextView, or null to clear any existing association.
2388      * @param tag String tag identifying this particular TextView owner in the
2389      * UndoManager.  This is used to keep the correct association with the
2390      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2391      *
2392      * @hide
2393      */
setUndoManager(UndoManager undoManager, String tag)2394     public final void setUndoManager(UndoManager undoManager, String tag) {
2395         // TODO: Consider supporting a global undo manager. An implementation will need to:
2396         // * createEditorIfNeeded()
2397         // * Promote to BufferType.EDITABLE if needed.
2398         // * Update the UndoManager and UndoOwner.
2399         // Likewise it will need to be able to restore the default UndoManager.
2400         throw new UnsupportedOperationException("not implemented");
2401     }
2402 
2403     /**
2404      * Gets the current {@link KeyListener} for the TextView.
2405      * This will frequently be null for non-EditText TextViews.
2406      * @return the current key listener for this TextView.
2407      *
2408      * @attr ref android.R.styleable#TextView_numeric
2409      * @attr ref android.R.styleable#TextView_digits
2410      * @attr ref android.R.styleable#TextView_phoneNumber
2411      * @attr ref android.R.styleable#TextView_inputMethod
2412      * @attr ref android.R.styleable#TextView_capitalize
2413      * @attr ref android.R.styleable#TextView_autoText
2414      */
getKeyListener()2415     public final KeyListener getKeyListener() {
2416         return mEditor == null ? null : mEditor.mKeyListener;
2417     }
2418 
2419     /**
2420      * Sets the key listener to be used with this TextView.  This can be null
2421      * to disallow user input.  Note that this method has significant and
2422      * subtle interactions with soft keyboards and other input method:
2423      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2424      * for important details.  Calling this method will replace the current
2425      * content type of the text view with the content type returned by the
2426      * key listener.
2427      * <p>
2428      * Be warned that if you want a TextView with a key listener or movement
2429      * method not to be focusable, or if you want a TextView without a
2430      * key listener or movement method to be focusable, you must call
2431      * {@link #setFocusable} again after calling this to get the focusability
2432      * back the way you want it.
2433      *
2434      * @attr ref android.R.styleable#TextView_numeric
2435      * @attr ref android.R.styleable#TextView_digits
2436      * @attr ref android.R.styleable#TextView_phoneNumber
2437      * @attr ref android.R.styleable#TextView_inputMethod
2438      * @attr ref android.R.styleable#TextView_capitalize
2439      * @attr ref android.R.styleable#TextView_autoText
2440      */
setKeyListener(KeyListener input)2441     public void setKeyListener(KeyListener input) {
2442         mListenerChanged = true;
2443         setKeyListenerOnly(input);
2444         fixFocusableAndClickableSettings();
2445 
2446         if (input != null) {
2447             createEditorIfNeeded();
2448             setInputTypeFromEditor();
2449         } else {
2450             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2451         }
2452 
2453         InputMethodManager imm = getInputMethodManager();
2454         if (imm != null) imm.restartInput(this);
2455     }
2456 
setInputTypeFromEditor()2457     private void setInputTypeFromEditor() {
2458         try {
2459             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2460         } catch (IncompatibleClassChangeError e) {
2461             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2462         }
2463         // Change inputType, without affecting transformation.
2464         // No need to applySingleLine since mSingleLine is unchanged.
2465         setInputTypeSingleLine(mSingleLine);
2466     }
2467 
setKeyListenerOnly(KeyListener input)2468     private void setKeyListenerOnly(KeyListener input) {
2469         if (mEditor == null && input == null) return; // null is the default value
2470 
2471         createEditorIfNeeded();
2472         if (mEditor.mKeyListener != input) {
2473             mEditor.mKeyListener = input;
2474             if (input != null && !(mText instanceof Editable)) {
2475                 setText(mText);
2476             }
2477 
2478             setFilters((Editable) mText, mFilters);
2479         }
2480     }
2481 
2482     /**
2483      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2484      * which provides positioning, scrolling, and text selection functionality.
2485      * This will frequently be null for non-EditText TextViews.
2486      * @return the movement method being used for this TextView.
2487      * @see android.text.method.MovementMethod
2488      */
getMovementMethod()2489     public final MovementMethod getMovementMethod() {
2490         return mMovement;
2491     }
2492 
2493     /**
2494      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2495      * for this TextView. This can be null to disallow using the arrow keys to move the
2496      * cursor or scroll the view.
2497      * <p>
2498      * Be warned that if you want a TextView with a key listener or movement
2499      * method not to be focusable, or if you want a TextView without a
2500      * key listener or movement method to be focusable, you must call
2501      * {@link #setFocusable} again after calling this to get the focusability
2502      * back the way you want it.
2503      */
setMovementMethod(MovementMethod movement)2504     public final void setMovementMethod(MovementMethod movement) {
2505         if (mMovement != movement) {
2506             mMovement = movement;
2507 
2508             if (movement != null && mSpannable == null) {
2509                 setText(mText);
2510             }
2511 
2512             fixFocusableAndClickableSettings();
2513 
2514             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2515             // mMovement
2516             if (mEditor != null) mEditor.prepareCursorControllers();
2517         }
2518     }
2519 
fixFocusableAndClickableSettings()2520     private void fixFocusableAndClickableSettings() {
2521         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2522             setFocusable(FOCUSABLE);
2523             setClickable(true);
2524             setLongClickable(true);
2525         } else {
2526             setFocusable(FOCUSABLE_AUTO);
2527             setClickable(false);
2528             setLongClickable(false);
2529         }
2530     }
2531 
2532     /**
2533      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2534      * This is frequently null, except for single-line and password fields.
2535      * @return the current transformation method for this TextView.
2536      *
2537      * @attr ref android.R.styleable#TextView_password
2538      * @attr ref android.R.styleable#TextView_singleLine
2539      */
getTransformationMethod()2540     public final TransformationMethod getTransformationMethod() {
2541         return mTransformation;
2542     }
2543 
2544     /**
2545      * Sets the transformation that is applied to the text that this
2546      * TextView is displaying.
2547      *
2548      * @attr ref android.R.styleable#TextView_password
2549      * @attr ref android.R.styleable#TextView_singleLine
2550      */
setTransformationMethod(TransformationMethod method)2551     public final void setTransformationMethod(TransformationMethod method) {
2552         if (method == mTransformation) {
2553             // Avoid the setText() below if the transformation is
2554             // the same.
2555             return;
2556         }
2557         if (mTransformation != null) {
2558             if (mSpannable != null) {
2559                 mSpannable.removeSpan(mTransformation);
2560             }
2561         }
2562 
2563         mTransformation = method;
2564 
2565         if (method instanceof TransformationMethod2) {
2566             TransformationMethod2 method2 = (TransformationMethod2) method;
2567             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2568             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2569         } else {
2570             mAllowTransformationLengthChange = false;
2571         }
2572 
2573         setText(mText);
2574 
2575         if (hasPasswordTransformationMethod()) {
2576             notifyViewAccessibilityStateChangedIfNeeded(
2577                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2578         }
2579 
2580         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2581         // getTextDirectionHeuristic, needs reset
2582         mTextDir = getTextDirectionHeuristic();
2583     }
2584 
2585     /**
2586      * Returns the top padding of the view, plus space for the top
2587      * Drawable if any.
2588      */
getCompoundPaddingTop()2589     public int getCompoundPaddingTop() {
2590         final Drawables dr = mDrawables;
2591         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2592             return mPaddingTop;
2593         } else {
2594             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2595         }
2596     }
2597 
2598     /**
2599      * Returns the bottom padding of the view, plus space for the bottom
2600      * Drawable if any.
2601      */
getCompoundPaddingBottom()2602     public int getCompoundPaddingBottom() {
2603         final Drawables dr = mDrawables;
2604         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2605             return mPaddingBottom;
2606         } else {
2607             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2608         }
2609     }
2610 
2611     /**
2612      * Returns the left padding of the view, plus space for the left
2613      * Drawable if any.
2614      */
getCompoundPaddingLeft()2615     public int getCompoundPaddingLeft() {
2616         final Drawables dr = mDrawables;
2617         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2618             return mPaddingLeft;
2619         } else {
2620             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2621         }
2622     }
2623 
2624     /**
2625      * Returns the right padding of the view, plus space for the right
2626      * Drawable if any.
2627      */
getCompoundPaddingRight()2628     public int getCompoundPaddingRight() {
2629         final Drawables dr = mDrawables;
2630         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2631             return mPaddingRight;
2632         } else {
2633             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2634         }
2635     }
2636 
2637     /**
2638      * Returns the start padding of the view, plus space for the start
2639      * Drawable if any.
2640      */
getCompoundPaddingStart()2641     public int getCompoundPaddingStart() {
2642         resolveDrawables();
2643         switch(getLayoutDirection()) {
2644             default:
2645             case LAYOUT_DIRECTION_LTR:
2646                 return getCompoundPaddingLeft();
2647             case LAYOUT_DIRECTION_RTL:
2648                 return getCompoundPaddingRight();
2649         }
2650     }
2651 
2652     /**
2653      * Returns the end padding of the view, plus space for the end
2654      * Drawable if any.
2655      */
getCompoundPaddingEnd()2656     public int getCompoundPaddingEnd() {
2657         resolveDrawables();
2658         switch(getLayoutDirection()) {
2659             default:
2660             case LAYOUT_DIRECTION_LTR:
2661                 return getCompoundPaddingRight();
2662             case LAYOUT_DIRECTION_RTL:
2663                 return getCompoundPaddingLeft();
2664         }
2665     }
2666 
2667     /**
2668      * Returns the extended top padding of the view, including both the
2669      * top Drawable if any and any extra space to keep more than maxLines
2670      * of text from showing.  It is only valid to call this after measuring.
2671      */
getExtendedPaddingTop()2672     public int getExtendedPaddingTop() {
2673         if (mMaxMode != LINES) {
2674             return getCompoundPaddingTop();
2675         }
2676 
2677         if (mLayout == null) {
2678             assumeLayout();
2679         }
2680 
2681         if (mLayout.getLineCount() <= mMaximum) {
2682             return getCompoundPaddingTop();
2683         }
2684 
2685         int top = getCompoundPaddingTop();
2686         int bottom = getCompoundPaddingBottom();
2687         int viewht = getHeight() - top - bottom;
2688         int layoutht = mLayout.getLineTop(mMaximum);
2689 
2690         if (layoutht >= viewht) {
2691             return top;
2692         }
2693 
2694         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2695         if (gravity == Gravity.TOP) {
2696             return top;
2697         } else if (gravity == Gravity.BOTTOM) {
2698             return top + viewht - layoutht;
2699         } else { // (gravity == Gravity.CENTER_VERTICAL)
2700             return top + (viewht - layoutht) / 2;
2701         }
2702     }
2703 
2704     /**
2705      * Returns the extended bottom padding of the view, including both the
2706      * bottom Drawable if any and any extra space to keep more than maxLines
2707      * of text from showing.  It is only valid to call this after measuring.
2708      */
getExtendedPaddingBottom()2709     public int getExtendedPaddingBottom() {
2710         if (mMaxMode != LINES) {
2711             return getCompoundPaddingBottom();
2712         }
2713 
2714         if (mLayout == null) {
2715             assumeLayout();
2716         }
2717 
2718         if (mLayout.getLineCount() <= mMaximum) {
2719             return getCompoundPaddingBottom();
2720         }
2721 
2722         int top = getCompoundPaddingTop();
2723         int bottom = getCompoundPaddingBottom();
2724         int viewht = getHeight() - top - bottom;
2725         int layoutht = mLayout.getLineTop(mMaximum);
2726 
2727         if (layoutht >= viewht) {
2728             return bottom;
2729         }
2730 
2731         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2732         if (gravity == Gravity.TOP) {
2733             return bottom + viewht - layoutht;
2734         } else if (gravity == Gravity.BOTTOM) {
2735             return bottom;
2736         } else { // (gravity == Gravity.CENTER_VERTICAL)
2737             return bottom + (viewht - layoutht) / 2;
2738         }
2739     }
2740 
2741     /**
2742      * Returns the total left padding of the view, including the left
2743      * Drawable if any.
2744      */
getTotalPaddingLeft()2745     public int getTotalPaddingLeft() {
2746         return getCompoundPaddingLeft();
2747     }
2748 
2749     /**
2750      * Returns the total right padding of the view, including the right
2751      * Drawable if any.
2752      */
getTotalPaddingRight()2753     public int getTotalPaddingRight() {
2754         return getCompoundPaddingRight();
2755     }
2756 
2757     /**
2758      * Returns the total start padding of the view, including the start
2759      * Drawable if any.
2760      */
getTotalPaddingStart()2761     public int getTotalPaddingStart() {
2762         return getCompoundPaddingStart();
2763     }
2764 
2765     /**
2766      * Returns the total end padding of the view, including the end
2767      * Drawable if any.
2768      */
getTotalPaddingEnd()2769     public int getTotalPaddingEnd() {
2770         return getCompoundPaddingEnd();
2771     }
2772 
2773     /**
2774      * Returns the total top padding of the view, including the top
2775      * Drawable if any, the extra space to keep more than maxLines
2776      * from showing, and the vertical offset for gravity, if any.
2777      */
getTotalPaddingTop()2778     public int getTotalPaddingTop() {
2779         return getExtendedPaddingTop() + getVerticalOffset(true);
2780     }
2781 
2782     /**
2783      * Returns the total bottom padding of the view, including the bottom
2784      * Drawable if any, the extra space to keep more than maxLines
2785      * from showing, and the vertical offset for gravity, if any.
2786      */
getTotalPaddingBottom()2787     public int getTotalPaddingBottom() {
2788         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2789     }
2790 
2791     /**
2792      * Sets the Drawables (if any) to appear to the left of, above, to the
2793      * right of, and below the text. Use {@code null} if you do not want a
2794      * Drawable there. The Drawables must already have had
2795      * {@link Drawable#setBounds} called.
2796      * <p>
2797      * Calling this method will overwrite any Drawables previously set using
2798      * {@link #setCompoundDrawablesRelative} or related methods.
2799      *
2800      * @attr ref android.R.styleable#TextView_drawableLeft
2801      * @attr ref android.R.styleable#TextView_drawableTop
2802      * @attr ref android.R.styleable#TextView_drawableRight
2803      * @attr ref android.R.styleable#TextView_drawableBottom
2804      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2805     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2806             @Nullable Drawable right, @Nullable Drawable bottom) {
2807         Drawables dr = mDrawables;
2808 
2809         // We're switching to absolute, discard relative.
2810         if (dr != null) {
2811             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2812             dr.mDrawableStart = null;
2813             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2814             dr.mDrawableEnd = null;
2815             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2816             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2817         }
2818 
2819         final boolean drawables = left != null || top != null || right != null || bottom != null;
2820         if (!drawables) {
2821             // Clearing drawables...  can we free the data structure?
2822             if (dr != null) {
2823                 if (!dr.hasMetadata()) {
2824                     mDrawables = null;
2825                 } else {
2826                     // We need to retain the last set padding, so just clear
2827                     // out all of the fields in the existing structure.
2828                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2829                         if (dr.mShowing[i] != null) {
2830                             dr.mShowing[i].setCallback(null);
2831                         }
2832                         dr.mShowing[i] = null;
2833                     }
2834                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2835                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2836                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2837                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2838                 }
2839             }
2840         } else {
2841             if (dr == null) {
2842                 mDrawables = dr = new Drawables(getContext());
2843             }
2844 
2845             mDrawables.mOverride = false;
2846 
2847             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2848                 dr.mShowing[Drawables.LEFT].setCallback(null);
2849             }
2850             dr.mShowing[Drawables.LEFT] = left;
2851 
2852             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2853                 dr.mShowing[Drawables.TOP].setCallback(null);
2854             }
2855             dr.mShowing[Drawables.TOP] = top;
2856 
2857             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2858                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2859             }
2860             dr.mShowing[Drawables.RIGHT] = right;
2861 
2862             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2863                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2864             }
2865             dr.mShowing[Drawables.BOTTOM] = bottom;
2866 
2867             final Rect compoundRect = dr.mCompoundRect;
2868             int[] state;
2869 
2870             state = getDrawableState();
2871 
2872             if (left != null) {
2873                 left.setState(state);
2874                 left.copyBounds(compoundRect);
2875                 left.setCallback(this);
2876                 dr.mDrawableSizeLeft = compoundRect.width();
2877                 dr.mDrawableHeightLeft = compoundRect.height();
2878             } else {
2879                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2880             }
2881 
2882             if (right != null) {
2883                 right.setState(state);
2884                 right.copyBounds(compoundRect);
2885                 right.setCallback(this);
2886                 dr.mDrawableSizeRight = compoundRect.width();
2887                 dr.mDrawableHeightRight = compoundRect.height();
2888             } else {
2889                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2890             }
2891 
2892             if (top != null) {
2893                 top.setState(state);
2894                 top.copyBounds(compoundRect);
2895                 top.setCallback(this);
2896                 dr.mDrawableSizeTop = compoundRect.height();
2897                 dr.mDrawableWidthTop = compoundRect.width();
2898             } else {
2899                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2900             }
2901 
2902             if (bottom != null) {
2903                 bottom.setState(state);
2904                 bottom.copyBounds(compoundRect);
2905                 bottom.setCallback(this);
2906                 dr.mDrawableSizeBottom = compoundRect.height();
2907                 dr.mDrawableWidthBottom = compoundRect.width();
2908             } else {
2909                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2910             }
2911         }
2912 
2913         // Save initial left/right drawables
2914         if (dr != null) {
2915             dr.mDrawableLeftInitial = left;
2916             dr.mDrawableRightInitial = right;
2917         }
2918 
2919         resetResolvedDrawables();
2920         resolveDrawables();
2921         applyCompoundDrawableTint();
2922         invalidate();
2923         requestLayout();
2924     }
2925 
2926     /**
2927      * Sets the Drawables (if any) to appear to the left of, above, to the
2928      * right of, and below the text. Use 0 if you do not want a Drawable there.
2929      * The Drawables' bounds will be set to their intrinsic bounds.
2930      * <p>
2931      * Calling this method will overwrite any Drawables previously set using
2932      * {@link #setCompoundDrawablesRelative} or related methods.
2933      *
2934      * @param left Resource identifier of the left Drawable.
2935      * @param top Resource identifier of the top Drawable.
2936      * @param right Resource identifier of the right Drawable.
2937      * @param bottom Resource identifier of the bottom Drawable.
2938      *
2939      * @attr ref android.R.styleable#TextView_drawableLeft
2940      * @attr ref android.R.styleable#TextView_drawableTop
2941      * @attr ref android.R.styleable#TextView_drawableRight
2942      * @attr ref android.R.styleable#TextView_drawableBottom
2943      */
2944     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)2945     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2946             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2947         final Context context = getContext();
2948         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2949                 top != 0 ? context.getDrawable(top) : null,
2950                 right != 0 ? context.getDrawable(right) : null,
2951                 bottom != 0 ? context.getDrawable(bottom) : null);
2952     }
2953 
2954     /**
2955      * Sets the Drawables (if any) to appear to the left of, above, to the
2956      * right of, and below the text. Use {@code null} if you do not want a
2957      * Drawable there. The Drawables' bounds will be set to their intrinsic
2958      * bounds.
2959      * <p>
2960      * Calling this method will overwrite any Drawables previously set using
2961      * {@link #setCompoundDrawablesRelative} or related methods.
2962      *
2963      * @attr ref android.R.styleable#TextView_drawableLeft
2964      * @attr ref android.R.styleable#TextView_drawableTop
2965      * @attr ref android.R.styleable#TextView_drawableRight
2966      * @attr ref android.R.styleable#TextView_drawableBottom
2967      */
2968     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2969     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2970             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2971 
2972         if (left != null) {
2973             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2974         }
2975         if (right != null) {
2976             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2977         }
2978         if (top != null) {
2979             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2980         }
2981         if (bottom != null) {
2982             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2983         }
2984         setCompoundDrawables(left, top, right, bottom);
2985     }
2986 
2987     /**
2988      * Sets the Drawables (if any) to appear to the start of, above, to the end
2989      * of, and below the text. Use {@code null} if you do not want a Drawable
2990      * there. The Drawables must already have had {@link Drawable#setBounds}
2991      * called.
2992      * <p>
2993      * Calling this method will overwrite any Drawables previously set using
2994      * {@link #setCompoundDrawables} or related methods.
2995      *
2996      * @attr ref android.R.styleable#TextView_drawableStart
2997      * @attr ref android.R.styleable#TextView_drawableTop
2998      * @attr ref android.R.styleable#TextView_drawableEnd
2999      * @attr ref android.R.styleable#TextView_drawableBottom
3000      */
3001     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3002     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
3003             @Nullable Drawable end, @Nullable Drawable bottom) {
3004         Drawables dr = mDrawables;
3005 
3006         // We're switching to relative, discard absolute.
3007         if (dr != null) {
3008             if (dr.mShowing[Drawables.LEFT] != null) {
3009                 dr.mShowing[Drawables.LEFT].setCallback(null);
3010             }
3011             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3012             if (dr.mShowing[Drawables.RIGHT] != null) {
3013                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3014             }
3015             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3016             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3017             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3018         }
3019 
3020         final boolean drawables = start != null || top != null
3021                 || end != null || bottom != null;
3022 
3023         if (!drawables) {
3024             // Clearing drawables...  can we free the data structure?
3025             if (dr != null) {
3026                 if (!dr.hasMetadata()) {
3027                     mDrawables = null;
3028                 } else {
3029                     // We need to retain the last set padding, so just clear
3030                     // out all of the fields in the existing structure.
3031                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3032                     dr.mDrawableStart = null;
3033                     if (dr.mShowing[Drawables.TOP] != null) {
3034                         dr.mShowing[Drawables.TOP].setCallback(null);
3035                     }
3036                     dr.mShowing[Drawables.TOP] = null;
3037                     if (dr.mDrawableEnd != null) {
3038                         dr.mDrawableEnd.setCallback(null);
3039                     }
3040                     dr.mDrawableEnd = null;
3041                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3042                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3043                     }
3044                     dr.mShowing[Drawables.BOTTOM] = null;
3045                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3046                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3047                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3048                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3049                 }
3050             }
3051         } else {
3052             if (dr == null) {
3053                 mDrawables = dr = new Drawables(getContext());
3054             }
3055 
3056             mDrawables.mOverride = true;
3057 
3058             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3059                 dr.mDrawableStart.setCallback(null);
3060             }
3061             dr.mDrawableStart = start;
3062 
3063             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3064                 dr.mShowing[Drawables.TOP].setCallback(null);
3065             }
3066             dr.mShowing[Drawables.TOP] = top;
3067 
3068             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3069                 dr.mDrawableEnd.setCallback(null);
3070             }
3071             dr.mDrawableEnd = end;
3072 
3073             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3074                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3075             }
3076             dr.mShowing[Drawables.BOTTOM] = bottom;
3077 
3078             final Rect compoundRect = dr.mCompoundRect;
3079             int[] state;
3080 
3081             state = getDrawableState();
3082 
3083             if (start != null) {
3084                 start.setState(state);
3085                 start.copyBounds(compoundRect);
3086                 start.setCallback(this);
3087                 dr.mDrawableSizeStart = compoundRect.width();
3088                 dr.mDrawableHeightStart = compoundRect.height();
3089             } else {
3090                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3091             }
3092 
3093             if (end != null) {
3094                 end.setState(state);
3095                 end.copyBounds(compoundRect);
3096                 end.setCallback(this);
3097                 dr.mDrawableSizeEnd = compoundRect.width();
3098                 dr.mDrawableHeightEnd = compoundRect.height();
3099             } else {
3100                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3101             }
3102 
3103             if (top != null) {
3104                 top.setState(state);
3105                 top.copyBounds(compoundRect);
3106                 top.setCallback(this);
3107                 dr.mDrawableSizeTop = compoundRect.height();
3108                 dr.mDrawableWidthTop = compoundRect.width();
3109             } else {
3110                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3111             }
3112 
3113             if (bottom != null) {
3114                 bottom.setState(state);
3115                 bottom.copyBounds(compoundRect);
3116                 bottom.setCallback(this);
3117                 dr.mDrawableSizeBottom = compoundRect.height();
3118                 dr.mDrawableWidthBottom = compoundRect.width();
3119             } else {
3120                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3121             }
3122         }
3123 
3124         resetResolvedDrawables();
3125         resolveDrawables();
3126         invalidate();
3127         requestLayout();
3128     }
3129 
3130     /**
3131      * Sets the Drawables (if any) to appear to the start of, above, to the end
3132      * of, and below the text. Use 0 if you do not want a Drawable there. The
3133      * Drawables' bounds will be set to their intrinsic bounds.
3134      * <p>
3135      * Calling this method will overwrite any Drawables previously set using
3136      * {@link #setCompoundDrawables} or related methods.
3137      *
3138      * @param start Resource identifier of the start Drawable.
3139      * @param top Resource identifier of the top Drawable.
3140      * @param end Resource identifier of the end Drawable.
3141      * @param bottom Resource identifier of the bottom Drawable.
3142      *
3143      * @attr ref android.R.styleable#TextView_drawableStart
3144      * @attr ref android.R.styleable#TextView_drawableTop
3145      * @attr ref android.R.styleable#TextView_drawableEnd
3146      * @attr ref android.R.styleable#TextView_drawableBottom
3147      */
3148     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3149     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3150             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3151         final Context context = getContext();
3152         setCompoundDrawablesRelativeWithIntrinsicBounds(
3153                 start != 0 ? context.getDrawable(start) : null,
3154                 top != 0 ? context.getDrawable(top) : null,
3155                 end != 0 ? context.getDrawable(end) : null,
3156                 bottom != 0 ? context.getDrawable(bottom) : null);
3157     }
3158 
3159     /**
3160      * Sets the Drawables (if any) to appear to the start of, above, to the end
3161      * of, and below the text. Use {@code null} if you do not want a Drawable
3162      * there. The Drawables' bounds will be set to their intrinsic bounds.
3163      * <p>
3164      * Calling this method will overwrite any Drawables previously set using
3165      * {@link #setCompoundDrawables} or related methods.
3166      *
3167      * @attr ref android.R.styleable#TextView_drawableStart
3168      * @attr ref android.R.styleable#TextView_drawableTop
3169      * @attr ref android.R.styleable#TextView_drawableEnd
3170      * @attr ref android.R.styleable#TextView_drawableBottom
3171      */
3172     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3173     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3174             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3175 
3176         if (start != null) {
3177             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3178         }
3179         if (end != null) {
3180             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3181         }
3182         if (top != null) {
3183             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3184         }
3185         if (bottom != null) {
3186             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3187         }
3188         setCompoundDrawablesRelative(start, top, end, bottom);
3189     }
3190 
3191     /**
3192      * Returns drawables for the left, top, right, and bottom borders.
3193      *
3194      * @attr ref android.R.styleable#TextView_drawableLeft
3195      * @attr ref android.R.styleable#TextView_drawableTop
3196      * @attr ref android.R.styleable#TextView_drawableRight
3197      * @attr ref android.R.styleable#TextView_drawableBottom
3198      */
3199     @NonNull
getCompoundDrawables()3200     public Drawable[] getCompoundDrawables() {
3201         final Drawables dr = mDrawables;
3202         if (dr != null) {
3203             return dr.mShowing.clone();
3204         } else {
3205             return new Drawable[] { null, null, null, null };
3206         }
3207     }
3208 
3209     /**
3210      * Returns drawables for the start, top, end, and bottom borders.
3211      *
3212      * @attr ref android.R.styleable#TextView_drawableStart
3213      * @attr ref android.R.styleable#TextView_drawableTop
3214      * @attr ref android.R.styleable#TextView_drawableEnd
3215      * @attr ref android.R.styleable#TextView_drawableBottom
3216      */
3217     @NonNull
getCompoundDrawablesRelative()3218     public Drawable[] getCompoundDrawablesRelative() {
3219         final Drawables dr = mDrawables;
3220         if (dr != null) {
3221             return new Drawable[] {
3222                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3223                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3224             };
3225         } else {
3226             return new Drawable[] { null, null, null, null };
3227         }
3228     }
3229 
3230     /**
3231      * Sets the size of the padding between the compound drawables and
3232      * the text.
3233      *
3234      * @attr ref android.R.styleable#TextView_drawablePadding
3235      */
3236     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3237     public void setCompoundDrawablePadding(int pad) {
3238         Drawables dr = mDrawables;
3239         if (pad == 0) {
3240             if (dr != null) {
3241                 dr.mDrawablePadding = pad;
3242             }
3243         } else {
3244             if (dr == null) {
3245                 mDrawables = dr = new Drawables(getContext());
3246             }
3247             dr.mDrawablePadding = pad;
3248         }
3249 
3250         invalidate();
3251         requestLayout();
3252     }
3253 
3254     /**
3255      * Returns the padding between the compound drawables and the text.
3256      *
3257      * @attr ref android.R.styleable#TextView_drawablePadding
3258      */
3259     @InspectableProperty(name = "drawablePadding")
getCompoundDrawablePadding()3260     public int getCompoundDrawablePadding() {
3261         final Drawables dr = mDrawables;
3262         return dr != null ? dr.mDrawablePadding : 0;
3263     }
3264 
3265     /**
3266      * Applies a tint to the compound drawables. Does not modify the
3267      * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
3268      * <p>
3269      * Subsequent calls to
3270      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3271      * and related methods will automatically mutate the drawables and apply
3272      * the specified tint and tint mode using
3273      * {@link Drawable#setTintList(ColorStateList)}.
3274      *
3275      * @param tint the tint to apply, may be {@code null} to clear tint
3276      *
3277      * @attr ref android.R.styleable#TextView_drawableTint
3278      * @see #getCompoundDrawableTintList()
3279      * @see Drawable#setTintList(ColorStateList)
3280      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3281     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3282         if (mDrawables == null) {
3283             mDrawables = new Drawables(getContext());
3284         }
3285         mDrawables.mTintList = tint;
3286         mDrawables.mHasTint = true;
3287 
3288         applyCompoundDrawableTint();
3289     }
3290 
3291     /**
3292      * @return the tint applied to the compound drawables
3293      * @attr ref android.R.styleable#TextView_drawableTint
3294      * @see #setCompoundDrawableTintList(ColorStateList)
3295      */
3296     @InspectableProperty(name = "drawableTint")
getCompoundDrawableTintList()3297     public ColorStateList getCompoundDrawableTintList() {
3298         return mDrawables != null ? mDrawables.mTintList : null;
3299     }
3300 
3301     /**
3302      * Specifies the blending mode used to apply the tint specified by
3303      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3304      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3305      *
3306      * @param tintMode the blending mode used to apply the tint, may be
3307      *                 {@code null} to clear tint
3308      * @attr ref android.R.styleable#TextView_drawableTintMode
3309      * @see #setCompoundDrawableTintList(ColorStateList)
3310      * @see Drawable#setTintMode(PorterDuff.Mode)
3311      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3312     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3313         setCompoundDrawableTintBlendMode(tintMode != null
3314                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3315     }
3316 
3317     /**
3318      * Specifies the blending mode used to apply the tint specified by
3319      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3320      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3321      *
3322      * @param blendMode the blending mode used to apply the tint, may be
3323      *                 {@code null} to clear tint
3324      * @attr ref android.R.styleable#TextView_drawableTintMode
3325      * @see #setCompoundDrawableTintList(ColorStateList)
3326      * @see Drawable#setTintBlendMode(BlendMode)
3327      */
setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3328     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3329         if (mDrawables == null) {
3330             mDrawables = new Drawables(getContext());
3331         }
3332         mDrawables.mBlendMode = blendMode;
3333         mDrawables.mHasTintMode = true;
3334 
3335         applyCompoundDrawableTint();
3336     }
3337 
3338     /**
3339      * Returns the blending mode used to apply the tint to the compound
3340      * drawables, if specified.
3341      *
3342      * @return the blending mode used to apply the tint to the compound
3343      *         drawables
3344      * @attr ref android.R.styleable#TextView_drawableTintMode
3345      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3346      *
3347      */
3348     @InspectableProperty(name = "drawableTintMode")
getCompoundDrawableTintMode()3349     public PorterDuff.Mode getCompoundDrawableTintMode() {
3350         BlendMode mode = getCompoundDrawableTintBlendMode();
3351         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3352     }
3353 
3354     /**
3355      * Returns the blending mode used to apply the tint to the compound
3356      * drawables, if specified.
3357      *
3358      * @return the blending mode used to apply the tint to the compound
3359      *         drawables
3360      * @attr ref android.R.styleable#TextView_drawableTintMode
3361      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3362      */
3363     @InspectableProperty(name = "drawableBlendMode",
3364             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
getCompoundDrawableTintBlendMode()3365     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3366         return mDrawables != null ? mDrawables.mBlendMode : null;
3367     }
3368 
applyCompoundDrawableTint()3369     private void applyCompoundDrawableTint() {
3370         if (mDrawables == null) {
3371             return;
3372         }
3373 
3374         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3375             final ColorStateList tintList = mDrawables.mTintList;
3376             final BlendMode blendMode = mDrawables.mBlendMode;
3377             final boolean hasTint = mDrawables.mHasTint;
3378             final boolean hasTintMode = mDrawables.mHasTintMode;
3379             final int[] state = getDrawableState();
3380 
3381             for (Drawable dr : mDrawables.mShowing) {
3382                 if (dr == null) {
3383                     continue;
3384                 }
3385 
3386                 if (dr == mDrawables.mDrawableError) {
3387                     // From a developer's perspective, the error drawable isn't
3388                     // a compound drawable. Don't apply the generic compound
3389                     // drawable tint to it.
3390                     continue;
3391                 }
3392 
3393                 dr.mutate();
3394 
3395                 if (hasTint) {
3396                     dr.setTintList(tintList);
3397                 }
3398 
3399                 if (hasTintMode) {
3400                     dr.setTintBlendMode(blendMode);
3401                 }
3402 
3403                 // The drawable (or one of its children) may not have been
3404                 // stateful before applying the tint, so let's try again.
3405                 if (dr.isStateful()) {
3406                     dr.setState(state);
3407                 }
3408             }
3409         }
3410     }
3411 
3412     /**
3413      * @inheritDoc
3414      *
3415      * @see #setFirstBaselineToTopHeight(int)
3416      * @see #setLastBaselineToBottomHeight(int)
3417      */
3418     @Override
setPadding(int left, int top, int right, int bottom)3419     public void setPadding(int left, int top, int right, int bottom) {
3420         if (left != mPaddingLeft
3421                 || right != mPaddingRight
3422                 || top != mPaddingTop
3423                 ||  bottom != mPaddingBottom) {
3424             nullLayouts();
3425         }
3426 
3427         // the super call will requestLayout()
3428         super.setPadding(left, top, right, bottom);
3429         invalidate();
3430     }
3431 
3432     /**
3433      * @inheritDoc
3434      *
3435      * @see #setFirstBaselineToTopHeight(int)
3436      * @see #setLastBaselineToBottomHeight(int)
3437      */
3438     @Override
setPaddingRelative(int start, int top, int end, int bottom)3439     public void setPaddingRelative(int start, int top, int end, int bottom) {
3440         if (start != getPaddingStart()
3441                 || end != getPaddingEnd()
3442                 || top != mPaddingTop
3443                 || bottom != mPaddingBottom) {
3444             nullLayouts();
3445         }
3446 
3447         // the super call will requestLayout()
3448         super.setPaddingRelative(start, top, end, bottom);
3449         invalidate();
3450     }
3451 
3452     /**
3453      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3454      * the distance between the top of the TextView and first line's baseline.
3455      * <p>
3456      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3457      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3458      *
3459      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3460      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3461      * Moreover since this function sets the top padding, if the height of the TextView is less than
3462      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3463      * down and bottom will be clipped.
3464      *
3465      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3466      *      in pixels
3467      *
3468      * @see #getFirstBaselineToTopHeight()
3469      * @see #setLastBaselineToBottomHeight(int)
3470      * @see #setPadding(int, int, int, int)
3471      * @see #setPaddingRelative(int, int, int, int)
3472      *
3473      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3474      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3475     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3476         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3477 
3478         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3479         final int fontMetricsTop;
3480         if (getIncludeFontPadding()) {
3481             fontMetricsTop = fontMetrics.top;
3482         } else {
3483             fontMetricsTop = fontMetrics.ascent;
3484         }
3485 
3486         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3487         // in settings). At the moment, we don't.
3488 
3489         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3490             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3491             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3492         }
3493     }
3494 
3495     /**
3496      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3497      * the distance between the bottom of the TextView and the last line's baseline.
3498      * <p>
3499      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3500      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3501      *
3502      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3503      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3504      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3505      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3506      * clipped.
3507      *
3508      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3509      *      in pixels
3510      *
3511      * @see #getLastBaselineToBottomHeight()
3512      * @see #setFirstBaselineToTopHeight(int)
3513      * @see #setPadding(int, int, int, int)
3514      * @see #setPaddingRelative(int, int, int, int)
3515      *
3516      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3517      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3518     public void setLastBaselineToBottomHeight(
3519             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3520         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3521 
3522         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3523         final int fontMetricsBottom;
3524         if (getIncludeFontPadding()) {
3525             fontMetricsBottom = fontMetrics.bottom;
3526         } else {
3527             fontMetricsBottom = fontMetrics.descent;
3528         }
3529 
3530         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3531         // in settings). At the moment, we don't.
3532 
3533         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3534             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3535             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3536         }
3537     }
3538 
3539     /**
3540      * Returns the distance between the first text baseline and the top of this TextView.
3541      *
3542      * @see #setFirstBaselineToTopHeight(int)
3543      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3544      */
3545     @InspectableProperty
getFirstBaselineToTopHeight()3546     public int getFirstBaselineToTopHeight() {
3547         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3548     }
3549 
3550     /**
3551      * Returns the distance between the last text baseline and the bottom of this TextView.
3552      *
3553      * @see #setLastBaselineToBottomHeight(int)
3554      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3555      */
3556     @InspectableProperty
getLastBaselineToBottomHeight()3557     public int getLastBaselineToBottomHeight() {
3558         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3559     }
3560 
3561     /**
3562      * Gets the autolink mask of the text.
3563      *
3564      * See {@link Linkify#ALL} and peers for possible values.
3565      *
3566      * @attr ref android.R.styleable#TextView_autoLink
3567      */
3568     @InspectableProperty(name = "autoLink", flagMapping = {
3569             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3570             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3571             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3572             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3573     })
getAutoLinkMask()3574     public final int getAutoLinkMask() {
3575         return mAutoLinkMask;
3576     }
3577 
3578     /**
3579      * Sets the Drawable corresponding to the selection handle used for
3580      * positioning the cursor within text. The Drawable defaults to the value
3581      * of the textSelectHandle attribute.
3582      * Note that any change applied to the handle Drawable will not be visible
3583      * until the handle is hidden and then drawn again.
3584      *
3585      * @see #setTextSelectHandle(int)
3586      * @attr ref android.R.styleable#TextView_textSelectHandle
3587      */
3588     @android.view.RemotableViewMethod
setTextSelectHandle(@onNull Drawable textSelectHandle)3589     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3590         Preconditions.checkNotNull(textSelectHandle,
3591                 "The text select handle should not be null.");
3592         mTextSelectHandle = textSelectHandle;
3593         mTextSelectHandleRes = 0;
3594         if (mEditor != null) {
3595             mEditor.loadHandleDrawables(true /* overwrite */);
3596         }
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(Drawable)
3607      * @attr ref android.R.styleable#TextView_textSelectHandle
3608      */
3609     @android.view.RemotableViewMethod
setTextSelectHandle(@rawableRes int textSelectHandle)3610     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3611         Preconditions.checkArgument(textSelectHandle != 0,
3612                 "The text select handle should be a valid drawable resource id.");
3613         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3614     }
3615 
3616     /**
3617      * Returns the Drawable corresponding to the selection handle used
3618      * for positioning the cursor within text.
3619      * Note that any change applied to the handle Drawable will not be visible
3620      * until the handle is hidden and then drawn again.
3621      *
3622      * @return the text select handle drawable
3623      *
3624      * @see #setTextSelectHandle(Drawable)
3625      * @see #setTextSelectHandle(int)
3626      * @attr ref android.R.styleable#TextView_textSelectHandle
3627      */
getTextSelectHandle()3628     @Nullable public Drawable getTextSelectHandle() {
3629         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3630             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3631         }
3632         return mTextSelectHandle;
3633     }
3634 
3635     /**
3636      * Sets the Drawable corresponding to the left handle used
3637      * for selecting text. The Drawable defaults to the value of the
3638      * textSelectHandleLeft attribute.
3639      * Note that any change applied to the handle Drawable will not be visible
3640      * until the handle is hidden and then drawn again.
3641      *
3642      * @see #setTextSelectHandleLeft(int)
3643      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3644      */
3645     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3646     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3647         Preconditions.checkNotNull(textSelectHandleLeft,
3648                 "The left text select handle should not be null.");
3649         mTextSelectHandleLeft = textSelectHandleLeft;
3650         mTextSelectHandleLeftRes = 0;
3651         if (mEditor != null) {
3652             mEditor.loadHandleDrawables(true /* overwrite */);
3653         }
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(Drawable)
3664      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3665      */
3666     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3667     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
3668         Preconditions.checkArgument(textSelectHandleLeft != 0,
3669                 "The text select left handle should be a valid drawable resource id.");
3670         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
3671     }
3672 
3673     /**
3674      * Returns the Drawable corresponding to the left handle used
3675      * for selecting text.
3676      * Note that any change applied to the handle Drawable will not be visible
3677      * until the handle is hidden and then drawn again.
3678      *
3679      * @return the left text selection handle drawable
3680      *
3681      * @see #setTextSelectHandleLeft(Drawable)
3682      * @see #setTextSelectHandleLeft(int)
3683      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3684      */
getTextSelectHandleLeft()3685     @Nullable public Drawable getTextSelectHandleLeft() {
3686         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
3687             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
3688         }
3689         return mTextSelectHandleLeft;
3690     }
3691 
3692     /**
3693      * Sets the Drawable corresponding to the right handle used
3694      * for selecting text. The Drawable defaults to the value of the
3695      * textSelectHandleRight attribute.
3696      * Note that any change applied to the handle Drawable will not be visible
3697      * until the handle is hidden and then drawn again.
3698      *
3699      * @see #setTextSelectHandleRight(int)
3700      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3701      */
3702     @android.view.RemotableViewMethod
setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3703     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
3704         Preconditions.checkNotNull(textSelectHandleRight,
3705                 "The right text select handle should not be null.");
3706         mTextSelectHandleRight = textSelectHandleRight;
3707         mTextSelectHandleRightRes = 0;
3708         if (mEditor != null) {
3709             mEditor.loadHandleDrawables(true /* overwrite */);
3710         }
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(Drawable)
3721      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3722      */
3723     @android.view.RemotableViewMethod
setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3724     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
3725         Preconditions.checkArgument(textSelectHandleRight != 0,
3726                 "The text select right handle should be a valid drawable resource id.");
3727         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
3728     }
3729 
3730     /**
3731      * Returns the Drawable corresponding to the right handle used
3732      * for selecting text.
3733      * Note that any change applied to the handle Drawable will not be visible
3734      * until the handle is hidden and then drawn again.
3735      *
3736      * @return the right text selection handle drawable
3737      *
3738      * @see #setTextSelectHandleRight(Drawable)
3739      * @see #setTextSelectHandleRight(int)
3740      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3741      */
getTextSelectHandleRight()3742     @Nullable public Drawable getTextSelectHandleRight() {
3743         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
3744             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
3745         }
3746         return mTextSelectHandleRight;
3747     }
3748 
3749     /**
3750      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3751      * value of the textCursorDrawable attribute.
3752      * Note that any change applied to the cursor Drawable will not be visible
3753      * until the cursor is hidden and then drawn again.
3754      *
3755      * @see #setTextCursorDrawable(int)
3756      * @attr ref android.R.styleable#TextView_textCursorDrawable
3757      */
setTextCursorDrawable(@ullable Drawable textCursorDrawable)3758     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
3759         mCursorDrawable = textCursorDrawable;
3760         mCursorDrawableRes = 0;
3761         if (mEditor != null) {
3762             mEditor.loadCursorDrawable();
3763         }
3764     }
3765 
3766     /**
3767      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3768      * value of the textCursorDrawable attribute.
3769      * Note that any change applied to the cursor Drawable will not be visible
3770      * until the cursor is hidden and then drawn again.
3771      *
3772      * @see #setTextCursorDrawable(Drawable)
3773      * @attr ref android.R.styleable#TextView_textCursorDrawable
3774      */
setTextCursorDrawable(@rawableRes int textCursorDrawable)3775     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
3776         setTextCursorDrawable(
3777                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
3778     }
3779 
3780     /**
3781      * Returns the Drawable corresponding to the text cursor.
3782      * Note that any change applied to the cursor Drawable will not be visible
3783      * until the cursor is hidden and then drawn again.
3784      *
3785      * @return the text cursor drawable
3786      *
3787      * @see #setTextCursorDrawable(Drawable)
3788      * @see #setTextCursorDrawable(int)
3789      * @attr ref android.R.styleable#TextView_textCursorDrawable
3790      */
getTextCursorDrawable()3791     @Nullable public Drawable getTextCursorDrawable() {
3792         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
3793             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
3794         }
3795         return mCursorDrawable;
3796     }
3797 
3798     /**
3799      * Sets the text appearance from the specified style resource.
3800      * <p>
3801      * Use a framework-defined {@code TextAppearance} style like
3802      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3803      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3804      * set of attributes that can be used in a custom style.
3805      *
3806      * @param resId the resource identifier of the style to apply
3807      * @attr ref android.R.styleable#TextView_textAppearance
3808      */
3809     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)3810     public void setTextAppearance(@StyleRes int resId) {
3811         setTextAppearance(mContext, resId);
3812     }
3813 
3814     /**
3815      * Sets the text color, size, style, hint color, and highlight color
3816      * from the specified TextAppearance resource.
3817      *
3818      * @deprecated Use {@link #setTextAppearance(int)} instead.
3819      */
3820     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)3821     public void setTextAppearance(Context context, @StyleRes int resId) {
3822         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3823         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
3824         readTextAppearance(context, ta, attributes, false /* styleArray */);
3825         ta.recycle();
3826         applyTextAppearance(attributes);
3827     }
3828 
3829     /**
3830      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
3831      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
3832      */
3833     private static class TextAppearanceAttributes {
3834         int mTextColorHighlight = 0;
3835         ColorStateList mTextColor = null;
3836         ColorStateList mTextColorHint = null;
3837         ColorStateList mTextColorLink = null;
3838         int mTextSize = -1;
3839         LocaleList mTextLocales = null;
3840         String mFontFamily = null;
3841         Typeface mFontTypeface = null;
3842         boolean mFontFamilyExplicit = false;
3843         int mTypefaceIndex = -1;
3844         int mTextStyle = 0;
3845         int mFontWeight = -1;
3846         boolean mAllCaps = false;
3847         int mShadowColor = 0;
3848         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
3849         boolean mHasElegant = false;
3850         boolean mElegant = false;
3851         boolean mHasFallbackLineSpacing = false;
3852         boolean mFallbackLineSpacing = false;
3853         boolean mHasLetterSpacing = false;
3854         float mLetterSpacing = 0;
3855         String mFontFeatureSettings = null;
3856         String mFontVariationSettings = null;
3857 
3858         @Override
toString()3859         public String toString() {
3860             return "TextAppearanceAttributes {\n"
3861                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
3862                     + "    mTextColor:" + mTextColor + "\n"
3863                     + "    mTextColorHint:" + mTextColorHint + "\n"
3864                     + "    mTextColorLink:" + mTextColorLink + "\n"
3865                     + "    mTextSize:" + mTextSize + "\n"
3866                     + "    mTextLocales:" + mTextLocales + "\n"
3867                     + "    mFontFamily:" + mFontFamily + "\n"
3868                     + "    mFontTypeface:" + mFontTypeface + "\n"
3869                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
3870                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
3871                     + "    mTextStyle:" + mTextStyle + "\n"
3872                     + "    mFontWeight:" + mFontWeight + "\n"
3873                     + "    mAllCaps:" + mAllCaps + "\n"
3874                     + "    mShadowColor:" + mShadowColor + "\n"
3875                     + "    mShadowDx:" + mShadowDx + "\n"
3876                     + "    mShadowDy:" + mShadowDy + "\n"
3877                     + "    mShadowRadius:" + mShadowRadius + "\n"
3878                     + "    mHasElegant:" + mHasElegant + "\n"
3879                     + "    mElegant:" + mElegant + "\n"
3880                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
3881                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
3882                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
3883                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
3884                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
3885                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
3886                     + "}";
3887         }
3888     }
3889 
3890     // Maps styleable attributes that exist both in TextView style and TextAppearance.
3891     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
3892     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3893         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
3894                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3895         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
3896                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3897         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
3898                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3899         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
3900                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3901         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
3902                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3903         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
3904                 com.android.internal.R.styleable.TextAppearance_textLocale);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3905         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
3906                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3907         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
3908                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3909         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
3910                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)3911         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
3912                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)3913         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
3914                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)3915         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
3916                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)3917         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
3918                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)3919         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
3920                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)3921         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
3922                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)3923         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
3924                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)3925         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
3926                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)3927         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
3928                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)3929         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
3930                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)3931         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
3932                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
3933     }
3934 
3935     /**
3936      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
3937      * set. If the TypedArray contains a value that was already set in the given attributes, that
3938      * will be overridden.
3939      *
3940      * @param context The Context to be used
3941      * @param appearance The TypedArray to read properties from
3942      * @param attributes the TextAppearanceAttributes to fill in
3943      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
3944      *                   what attribute indexes will be used to read the properties.
3945      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)3946     private void readTextAppearance(Context context, TypedArray appearance,
3947             TextAppearanceAttributes attributes, boolean styleArray) {
3948         final int n = appearance.getIndexCount();
3949         for (int i = 0; i < n; i++) {
3950             final int attr = appearance.getIndex(i);
3951             int index = attr;
3952             // Translate style array index ids to TextAppearance ids.
3953             if (styleArray) {
3954                 index = sAppearanceValues.get(attr, -1);
3955                 if (index == -1) {
3956                     // This value is not part of a Text Appearance and should be ignored.
3957                     continue;
3958                 }
3959             }
3960             switch (index) {
3961                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
3962                     attributes.mTextColorHighlight =
3963                             appearance.getColor(attr, attributes.mTextColorHighlight);
3964                     break;
3965                 case com.android.internal.R.styleable.TextAppearance_textColor:
3966                     attributes.mTextColor = appearance.getColorStateList(attr);
3967                     break;
3968                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
3969                     attributes.mTextColorHint = appearance.getColorStateList(attr);
3970                     break;
3971                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
3972                     attributes.mTextColorLink = appearance.getColorStateList(attr);
3973                     break;
3974                 case com.android.internal.R.styleable.TextAppearance_textSize:
3975                     attributes.mTextSize =
3976                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
3977                     break;
3978                 case com.android.internal.R.styleable.TextAppearance_textLocale:
3979                     final String localeString = appearance.getString(attr);
3980                     if (localeString != null) {
3981                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
3982                         if (!localeList.isEmpty()) {
3983                             attributes.mTextLocales = localeList;
3984                         }
3985                     }
3986                     break;
3987                 case com.android.internal.R.styleable.TextAppearance_typeface:
3988                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
3989                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
3990                         attributes.mFontFamily = null;
3991                     }
3992                     break;
3993                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
3994                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
3995                         try {
3996                             attributes.mFontTypeface = appearance.getFont(attr);
3997                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
3998                             // Expected if it is not a font resource.
3999                         }
4000                     }
4001                     if (attributes.mFontTypeface == null) {
4002                         attributes.mFontFamily = appearance.getString(attr);
4003                     }
4004                     attributes.mFontFamilyExplicit = true;
4005                     break;
4006                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4007                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4008                     break;
4009                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4010                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4011                     break;
4012                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4013                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4014                     break;
4015                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4016                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4017                     break;
4018                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4019                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4020                     break;
4021                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4022                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4023                     break;
4024                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4025                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4026                     break;
4027                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4028                     attributes.mHasElegant = true;
4029                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4030                     break;
4031                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4032                     attributes.mHasFallbackLineSpacing = true;
4033                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4034                             attributes.mFallbackLineSpacing);
4035                     break;
4036                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4037                     attributes.mHasLetterSpacing = true;
4038                     attributes.mLetterSpacing =
4039                             appearance.getFloat(attr, attributes.mLetterSpacing);
4040                     break;
4041                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4042                     attributes.mFontFeatureSettings = appearance.getString(attr);
4043                     break;
4044                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4045                     attributes.mFontVariationSettings = appearance.getString(attr);
4046                     break;
4047                 default:
4048             }
4049         }
4050     }
4051 
applyTextAppearance(TextAppearanceAttributes attributes)4052     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4053         if (attributes.mTextColor != null) {
4054             setTextColor(attributes.mTextColor);
4055         }
4056 
4057         if (attributes.mTextColorHint != null) {
4058             setHintTextColor(attributes.mTextColorHint);
4059         }
4060 
4061         if (attributes.mTextColorLink != null) {
4062             setLinkTextColor(attributes.mTextColorLink);
4063         }
4064 
4065         if (attributes.mTextColorHighlight != 0) {
4066             setHighlightColor(attributes.mTextColorHighlight);
4067         }
4068 
4069         if (attributes.mTextSize != -1) {
4070             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4071         }
4072 
4073         if (attributes.mTextLocales != null) {
4074             setTextLocales(attributes.mTextLocales);
4075         }
4076 
4077         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4078             attributes.mFontFamily = null;
4079         }
4080         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4081                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4082 
4083         if (attributes.mShadowColor != 0) {
4084             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4085                     attributes.mShadowColor);
4086         }
4087 
4088         if (attributes.mAllCaps) {
4089             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4090         }
4091 
4092         if (attributes.mHasElegant) {
4093             setElegantTextHeight(attributes.mElegant);
4094         }
4095 
4096         if (attributes.mHasFallbackLineSpacing) {
4097             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4098         }
4099 
4100         if (attributes.mHasLetterSpacing) {
4101             setLetterSpacing(attributes.mLetterSpacing);
4102         }
4103 
4104         if (attributes.mFontFeatureSettings != null) {
4105             setFontFeatureSettings(attributes.mFontFeatureSettings);
4106         }
4107 
4108         if (attributes.mFontVariationSettings != null) {
4109             setFontVariationSettings(attributes.mFontVariationSettings);
4110         }
4111     }
4112 
4113     /**
4114      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4115      * the first member of {@link #getTextLocales()}.
4116      * @return the default primary {@link Locale} of the text in this TextView.
4117      */
4118     @NonNull
getTextLocale()4119     public Locale getTextLocale() {
4120         return mTextPaint.getTextLocale();
4121     }
4122 
4123     /**
4124      * Get the default {@link LocaleList} of the text in this TextView.
4125      * @return the default {@link LocaleList} of the text in this TextView.
4126      */
4127     @NonNull @Size(min = 1)
getTextLocales()4128     public LocaleList getTextLocales() {
4129         return mTextPaint.getTextLocales();
4130     }
4131 
changeListenerLocaleTo(@ullable Locale locale)4132     private void changeListenerLocaleTo(@Nullable Locale locale) {
4133         if (mListenerChanged) {
4134             // If a listener has been explicitly set, don't change it. We may break something.
4135             return;
4136         }
4137         // The following null check is not absolutely necessary since all calling points of
4138         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4139         // here in case others would want to call this method in the future.
4140         if (mEditor != null) {
4141             KeyListener listener = mEditor.mKeyListener;
4142             if (listener instanceof DigitsKeyListener) {
4143                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4144             } else if (listener instanceof DateKeyListener) {
4145                 listener = DateKeyListener.getInstance(locale);
4146             } else if (listener instanceof TimeKeyListener) {
4147                 listener = TimeKeyListener.getInstance(locale);
4148             } else if (listener instanceof DateTimeKeyListener) {
4149                 listener = DateTimeKeyListener.getInstance(locale);
4150             } else {
4151                 return;
4152             }
4153             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4154             setKeyListenerOnly(listener);
4155             setInputTypeFromEditor();
4156             if (wasPasswordType) {
4157                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4158                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4159                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4160                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4161                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4162                 }
4163             }
4164         }
4165     }
4166 
4167     /**
4168      * Set the default {@link Locale} of the text in this TextView to a one-member
4169      * {@link LocaleList} containing just the given Locale.
4170      *
4171      * @param locale the {@link Locale} for drawing text, must not be null.
4172      *
4173      * @see #setTextLocales
4174      */
setTextLocale(@onNull Locale locale)4175     public void setTextLocale(@NonNull Locale locale) {
4176         mLocalesChanged = true;
4177         mTextPaint.setTextLocale(locale);
4178         if (mLayout != null) {
4179             nullLayouts();
4180             requestLayout();
4181             invalidate();
4182         }
4183     }
4184 
4185     /**
4186      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4187      *
4188      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4189      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4190      * other aspects of text display, including line breaking.
4191      *
4192      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4193      *
4194      * @see Paint#setTextLocales
4195      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)4196     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4197         mLocalesChanged = true;
4198         mTextPaint.setTextLocales(locales);
4199         if (mLayout != null) {
4200             nullLayouts();
4201             requestLayout();
4202             invalidate();
4203         }
4204     }
4205 
4206     @Override
onConfigurationChanged(Configuration newConfig)4207     protected void onConfigurationChanged(Configuration newConfig) {
4208         super.onConfigurationChanged(newConfig);
4209         if (!mLocalesChanged) {
4210             mTextPaint.setTextLocales(LocaleList.getDefault());
4211             if (mLayout != null) {
4212                 nullLayouts();
4213                 requestLayout();
4214                 invalidate();
4215             }
4216         }
4217     }
4218 
4219     /**
4220      * @return the size (in pixels) of the default text size in this TextView.
4221      */
4222     @InspectableProperty
4223     @ViewDebug.ExportedProperty(category = "text")
getTextSize()4224     public float getTextSize() {
4225         return mTextPaint.getTextSize();
4226     }
4227 
4228     /**
4229      * @return the size (in scaled pixels) of the default text size in this TextView.
4230      * @hide
4231      */
4232     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()4233     public float getScaledTextSize() {
4234         return mTextPaint.getTextSize() / mTextPaint.density;
4235     }
4236 
4237     /** @hide */
4238     @ViewDebug.ExportedProperty(category = "text", mapping = {
4239             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4240             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4241             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4242             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4243     })
getTypefaceStyle()4244     public int getTypefaceStyle() {
4245         Typeface typeface = mTextPaint.getTypeface();
4246         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4247     }
4248 
4249     /**
4250      * Set the default text size to the given value, interpreted as "scaled
4251      * pixel" units.  This size is adjusted based on the current density and
4252      * user font size preference.
4253      *
4254      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4255      *
4256      * @param size The scaled pixel size.
4257      *
4258      * @attr ref android.R.styleable#TextView_textSize
4259      */
4260     @android.view.RemotableViewMethod
setTextSize(float size)4261     public void setTextSize(float size) {
4262         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4263     }
4264 
4265     /**
4266      * Set the default text size to a given unit and value. See {@link
4267      * TypedValue} for the possible dimension units.
4268      *
4269      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4270      *
4271      * @param unit The desired dimension unit.
4272      * @param size The desired size in the given units.
4273      *
4274      * @attr ref android.R.styleable#TextView_textSize
4275      */
setTextSize(int unit, float size)4276     public void setTextSize(int unit, float size) {
4277         if (!isAutoSizeEnabled()) {
4278             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4279         }
4280     }
4281 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4282     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4283         Context c = getContext();
4284         Resources r;
4285 
4286         if (c == null) {
4287             r = Resources.getSystem();
4288         } else {
4289             r = c.getResources();
4290         }
4291 
4292         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
4293                 shouldRequestLayout);
4294     }
4295 
4296     @UnsupportedAppUsage
setRawTextSize(float size, boolean shouldRequestLayout)4297     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4298         if (size != mTextPaint.getTextSize()) {
4299             mTextPaint.setTextSize(size);
4300 
4301             if (shouldRequestLayout && mLayout != null) {
4302                 // Do not auto-size right after setting the text size.
4303                 mNeedsAutoSizeText = false;
4304                 nullLayouts();
4305                 requestLayout();
4306                 invalidate();
4307             }
4308         }
4309     }
4310 
4311     /**
4312      * Gets the extent by which text should be stretched horizontally.
4313      * This will usually be 1.0.
4314      * @return The horizontal scale factor.
4315      */
4316     @InspectableProperty
getTextScaleX()4317     public float getTextScaleX() {
4318         return mTextPaint.getTextScaleX();
4319     }
4320 
4321     /**
4322      * Sets the horizontal scale factor for text. The default value
4323      * is 1.0. Values greater than 1.0 stretch the text wider.
4324      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4325      * @param size The horizontal scale factor.
4326      * @attr ref android.R.styleable#TextView_textScaleX
4327      */
4328     @android.view.RemotableViewMethod
setTextScaleX(float size)4329     public void setTextScaleX(float size) {
4330         if (size != mTextPaint.getTextScaleX()) {
4331             mUserSetTextScaleX = true;
4332             mTextPaint.setTextScaleX(size);
4333 
4334             if (mLayout != null) {
4335                 nullLayouts();
4336                 requestLayout();
4337                 invalidate();
4338             }
4339         }
4340     }
4341 
4342     /**
4343      * Sets the typeface and style in which the text should be displayed.
4344      * Note that not all Typeface families actually have bold and italic
4345      * variants, so you may need to use
4346      * {@link #setTypeface(Typeface, int)} to get the appearance
4347      * that you actually want.
4348      *
4349      * @see #getTypeface()
4350      *
4351      * @attr ref android.R.styleable#TextView_fontFamily
4352      * @attr ref android.R.styleable#TextView_typeface
4353      * @attr ref android.R.styleable#TextView_textStyle
4354      */
setTypeface(@ullable Typeface tf)4355     public void setTypeface(@Nullable Typeface tf) {
4356         if (mTextPaint.getTypeface() != tf) {
4357             mTextPaint.setTypeface(tf);
4358 
4359             if (mLayout != null) {
4360                 nullLayouts();
4361                 requestLayout();
4362                 invalidate();
4363             }
4364         }
4365     }
4366 
4367     /**
4368      * Gets the current {@link Typeface} that is used to style the text.
4369      * @return The current Typeface.
4370      *
4371      * @see #setTypeface(Typeface)
4372      *
4373      * @attr ref android.R.styleable#TextView_fontFamily
4374      * @attr ref android.R.styleable#TextView_typeface
4375      * @attr ref android.R.styleable#TextView_textStyle
4376      */
4377     @InspectableProperty
getTypeface()4378     public Typeface getTypeface() {
4379         return mTextPaint.getTypeface();
4380     }
4381 
4382     /**
4383      * Set the TextView's elegant height metrics flag. This setting selects font
4384      * variants that have not been compacted to fit Latin-based vertical
4385      * metrics, and also increases top and bottom bounds to provide more space.
4386      *
4387      * @param elegant set the paint's elegant metrics flag.
4388      *
4389      * @see #isElegantTextHeight()
4390      * @see Paint#isElegantTextHeight()
4391      *
4392      * @attr ref android.R.styleable#TextView_elegantTextHeight
4393      */
setElegantTextHeight(boolean elegant)4394     public void setElegantTextHeight(boolean elegant) {
4395         if (elegant != mTextPaint.isElegantTextHeight()) {
4396             mTextPaint.setElegantTextHeight(elegant);
4397             if (mLayout != null) {
4398                 nullLayouts();
4399                 requestLayout();
4400                 invalidate();
4401             }
4402         }
4403     }
4404 
4405     /**
4406      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4407      * displaying the text (which is needed to avoid text from consecutive lines running into
4408      * each other). If set, fallback fonts that end up getting used can increase the ascent
4409      * and descent of the lines that they are used on.
4410      * <p/>
4411      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4412      * is typically much taller or deeper than Latin text.
4413      *
4414      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4415      *
4416      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4417      *
4418      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4419      */
setFallbackLineSpacing(boolean enabled)4420     public void setFallbackLineSpacing(boolean enabled) {
4421         if (mUseFallbackLineSpacing != enabled) {
4422             mUseFallbackLineSpacing = enabled;
4423             if (mLayout != null) {
4424                 nullLayouts();
4425                 requestLayout();
4426                 invalidate();
4427             }
4428         }
4429     }
4430 
4431     /**
4432      * @return whether fallback line spacing is enabled, {@code true} by default
4433      *
4434      * @see #setFallbackLineSpacing(boolean)
4435      *
4436      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4437      */
4438     @InspectableProperty
isFallbackLineSpacing()4439     public boolean isFallbackLineSpacing() {
4440         return mUseFallbackLineSpacing;
4441     }
4442 
4443     /**
4444      * Get the value of the TextView's elegant height metrics flag. This setting selects font
4445      * variants that have not been compacted to fit Latin-based vertical
4446      * metrics, and also increases top and bottom bounds to provide more space.
4447      * @return {@code true} if the elegant height metrics flag is set.
4448      *
4449      * @see #setElegantTextHeight(boolean)
4450      * @see Paint#setElegantTextHeight(boolean)
4451      */
4452     @InspectableProperty
isElegantTextHeight()4453     public boolean isElegantTextHeight() {
4454         return mTextPaint.isElegantTextHeight();
4455     }
4456 
4457     /**
4458      * Gets the text letter-space value, which determines the spacing between characters.
4459      * The value returned is in ems. Normally, this value is 0.0.
4460      * @return The text letter-space value in ems.
4461      *
4462      * @see #setLetterSpacing(float)
4463      * @see Paint#setLetterSpacing
4464      */
4465     @InspectableProperty
getLetterSpacing()4466     public float getLetterSpacing() {
4467         return mTextPaint.getLetterSpacing();
4468     }
4469 
4470     /**
4471      * Sets text letter-spacing in em units.  Typical values
4472      * for slight expansion will be around 0.05.  Negative values tighten text.
4473      *
4474      * @see #getLetterSpacing()
4475      * @see Paint#getLetterSpacing
4476      *
4477      * @param letterSpacing A text letter-space value in ems.
4478      * @attr ref android.R.styleable#TextView_letterSpacing
4479      */
4480     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)4481     public void setLetterSpacing(float letterSpacing) {
4482         if (letterSpacing != mTextPaint.getLetterSpacing()) {
4483             mTextPaint.setLetterSpacing(letterSpacing);
4484 
4485             if (mLayout != null) {
4486                 nullLayouts();
4487                 requestLayout();
4488                 invalidate();
4489             }
4490         }
4491     }
4492 
4493     /**
4494      * Returns the font feature settings. The format is the same as the CSS
4495      * font-feature-settings attribute:
4496      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4497      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4498      *
4499      * @return the currently set font feature settings.  Default is null.
4500      *
4501      * @see #setFontFeatureSettings(String)
4502      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
4503      */
4504     @InspectableProperty
4505     @Nullable
getFontFeatureSettings()4506     public String getFontFeatureSettings() {
4507         return mTextPaint.getFontFeatureSettings();
4508     }
4509 
4510     /**
4511      * Returns the font variation settings.
4512      *
4513      * @return the currently set font variation settings.  Returns null if no variation is
4514      * specified.
4515      *
4516      * @see #setFontVariationSettings(String)
4517      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
4518      */
4519     @Nullable
getFontVariationSettings()4520     public String getFontVariationSettings() {
4521         return mTextPaint.getFontVariationSettings();
4522     }
4523 
4524     /**
4525      * Sets the break strategy for breaking paragraphs into lines. The default value for
4526      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
4527      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
4528      * text "dancing" when being edited.
4529      * <p/>
4530      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4531      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4532      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4533      * improves the structure of text layout however has performance impact and requires more time
4534      * to do the text layout.
4535      *
4536      * @attr ref android.R.styleable#TextView_breakStrategy
4537      * @see #getBreakStrategy()
4538      * @see #setHyphenationFrequency(int)
4539      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4540     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
4541         mBreakStrategy = breakStrategy;
4542         if (mLayout != null) {
4543             nullLayouts();
4544             requestLayout();
4545             invalidate();
4546         }
4547     }
4548 
4549     /**
4550      * Gets the current strategy for breaking paragraphs into lines.
4551      * @return the current strategy for breaking paragraphs into lines.
4552      *
4553      * @attr ref android.R.styleable#TextView_breakStrategy
4554      * @see #setBreakStrategy(int)
4555      */
4556     @InspectableProperty(enumMapping = {
4557             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
4558             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
4559             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
4560     })
4561     @Layout.BreakStrategy
getBreakStrategy()4562     public int getBreakStrategy() {
4563         return mBreakStrategy;
4564     }
4565 
4566     /**
4567      * Sets the frequency of automatic hyphenation to use when determining word breaks.
4568      * The default value for both TextView and {@link EditText} is
4569      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
4570      * is set from the theme.
4571      * <p/>
4572      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4573      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4574      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4575      * improves the structure of text layout however has performance impact and requires more time
4576      * to do the text layout.
4577      * <p/>
4578      * Note: Before Android Q, in the theme hyphenation frequency is set to
4579      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
4580      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
4581      *
4582      * @param hyphenationFrequency the hyphenation frequency to use, one of
4583      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
4584      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
4585      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
4586      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4587      * @see #getHyphenationFrequency()
4588      * @see #getBreakStrategy()
4589      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4590     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
4591         mHyphenationFrequency = hyphenationFrequency;
4592         if (mLayout != null) {
4593             nullLayouts();
4594             requestLayout();
4595             invalidate();
4596         }
4597     }
4598 
4599     /**
4600      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
4601      * @return the current frequency of automatic hyphenation to be used when determining word
4602      * breaks.
4603      *
4604      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4605      * @see #setHyphenationFrequency(int)
4606      */
4607     @InspectableProperty(enumMapping = {
4608             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
4609             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
4610             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
4611     })
4612     @Layout.HyphenationFrequency
getHyphenationFrequency()4613     public int getHyphenationFrequency() {
4614         return mHyphenationFrequency;
4615     }
4616 
4617     /**
4618      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
4619      *
4620      * @return a current {@link PrecomputedText.Params}
4621      * @see PrecomputedText
4622      */
getTextMetricsParams()4623     public @NonNull PrecomputedText.Params getTextMetricsParams() {
4624         return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
4625                 mBreakStrategy, mHyphenationFrequency);
4626     }
4627 
4628     /**
4629      * Apply the text layout parameter.
4630      *
4631      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
4632      * @see PrecomputedText
4633      */
setTextMetricsParams(@onNull PrecomputedText.Params params)4634     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
4635         mTextPaint.set(params.getTextPaint());
4636         mUserSetTextScaleX = true;
4637         mTextDir = params.getTextDirection();
4638         mBreakStrategy = params.getBreakStrategy();
4639         mHyphenationFrequency = params.getHyphenationFrequency();
4640         if (mLayout != null) {
4641             nullLayouts();
4642             requestLayout();
4643             invalidate();
4644         }
4645     }
4646 
4647     /**
4648      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
4649      * last line is too short for justification, the last line will be displayed with the
4650      * alignment set by {@link android.view.View#setTextAlignment}.
4651      *
4652      * @see #getJustificationMode()
4653      */
4654     @Layout.JustificationMode
setJustificationMode(@ayout.JustificationMode int justificationMode)4655     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
4656         mJustificationMode = justificationMode;
4657         if (mLayout != null) {
4658             nullLayouts();
4659             requestLayout();
4660             invalidate();
4661         }
4662     }
4663 
4664     /**
4665      * @return true if currently paragraph justification mode.
4666      *
4667      * @see #setJustificationMode(int)
4668      */
4669     @InspectableProperty(enumMapping = {
4670             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
4671             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
4672     })
getJustificationMode()4673     public @Layout.JustificationMode int getJustificationMode() {
4674         return mJustificationMode;
4675     }
4676 
4677     /**
4678      * Sets font feature settings. The format is the same as the CSS
4679      * font-feature-settings attribute:
4680      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4681      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4682      *
4683      * @param fontFeatureSettings font feature settings represented as CSS compatible string
4684      *
4685      * @see #getFontFeatureSettings()
4686      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
4687      *
4688      * @attr ref android.R.styleable#TextView_fontFeatureSettings
4689      */
4690     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)4691     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
4692         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
4693             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
4694 
4695             if (mLayout != null) {
4696                 nullLayouts();
4697                 requestLayout();
4698                 invalidate();
4699             }
4700         }
4701     }
4702 
4703 
4704     /**
4705      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
4706      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
4707      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
4708      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
4709      * are invalid. If a specified axis name is not defined in the font, the settings will be
4710      * ignored.
4711      *
4712      * <p>
4713      * Examples,
4714      * <ul>
4715      * <li>Set font width to 150.
4716      * <pre>
4717      * <code>
4718      *   TextView textView = (TextView) findViewById(R.id.textView);
4719      *   textView.setFontVariationSettings("'wdth' 150");
4720      * </code>
4721      * </pre>
4722      * </li>
4723      *
4724      * <li>Set the font slant to 20 degrees and ask for italic style.
4725      * <pre>
4726      * <code>
4727      *   TextView textView = (TextView) findViewById(R.id.textView);
4728      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
4729      * </code>
4730      * </pre>
4731      * </p>
4732      * </li>
4733      * </ul>
4734      *
4735      * @param fontVariationSettings font variation settings. You can pass null or empty string as
4736      *                              no variation settings.
4737      * @return true if the given settings is effective to at least one font file underlying this
4738      *         TextView. This function also returns true for empty settings string. Otherwise
4739      *         returns false.
4740      *
4741      * @throws IllegalArgumentException If given string is not a valid font variation settings
4742      *                                  format.
4743      *
4744      * @see #getFontVariationSettings()
4745      * @see FontVariationAxis
4746      *
4747      * @attr ref android.R.styleable#TextView_fontVariationSettings
4748      */
setFontVariationSettings(@ullable String fontVariationSettings)4749     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
4750         final String existingSettings = mTextPaint.getFontVariationSettings();
4751         if (fontVariationSettings == existingSettings
4752                 || (fontVariationSettings != null
4753                         && fontVariationSettings.equals(existingSettings))) {
4754             return true;
4755         }
4756         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
4757 
4758         if (effective && mLayout != null) {
4759             nullLayouts();
4760             requestLayout();
4761             invalidate();
4762         }
4763         return effective;
4764     }
4765 
4766     /**
4767      * Sets the text color for all the states (normal, selected,
4768      * focused) to be this color.
4769      *
4770      * @param color A color value in the form 0xAARRGGBB.
4771      * Do not pass a resource ID. To get a color value from a resource ID, call
4772      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
4773      *
4774      * @see #setTextColor(ColorStateList)
4775      * @see #getTextColors()
4776      *
4777      * @attr ref android.R.styleable#TextView_textColor
4778      */
4779     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)4780     public void setTextColor(@ColorInt int color) {
4781         mTextColor = ColorStateList.valueOf(color);
4782         updateTextColors();
4783     }
4784 
4785     /**
4786      * Sets the text color.
4787      *
4788      * @see #setTextColor(int)
4789      * @see #getTextColors()
4790      * @see #setHintTextColor(ColorStateList)
4791      * @see #setLinkTextColor(ColorStateList)
4792      *
4793      * @attr ref android.R.styleable#TextView_textColor
4794      */
4795     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)4796     public void setTextColor(ColorStateList colors) {
4797         if (colors == null) {
4798             throw new NullPointerException();
4799         }
4800 
4801         mTextColor = colors;
4802         updateTextColors();
4803     }
4804 
4805     /**
4806      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
4807      *
4808      * @see #setTextColor(ColorStateList)
4809      * @see #setTextColor(int)
4810      *
4811      * @attr ref android.R.styleable#TextView_textColor
4812      */
4813     @InspectableProperty(name = "textColor")
getTextColors()4814     public final ColorStateList getTextColors() {
4815         return mTextColor;
4816     }
4817 
4818     /**
4819      * Return the current color selected for normal text.
4820      *
4821      * @return Returns the current text color.
4822      */
4823     @ColorInt
getCurrentTextColor()4824     public final int getCurrentTextColor() {
4825         return mCurTextColor;
4826     }
4827 
4828     /**
4829      * Sets the color used to display the selection highlight.
4830      *
4831      * @attr ref android.R.styleable#TextView_textColorHighlight
4832      */
4833     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)4834     public void setHighlightColor(@ColorInt int color) {
4835         if (mHighlightColor != color) {
4836             mHighlightColor = color;
4837             invalidate();
4838         }
4839     }
4840 
4841     /**
4842      * @return the color used to display the selection highlight
4843      *
4844      * @see #setHighlightColor(int)
4845      *
4846      * @attr ref android.R.styleable#TextView_textColorHighlight
4847      */
4848     @InspectableProperty(name = "textColorHighlight")
4849     @ColorInt
getHighlightColor()4850     public int getHighlightColor() {
4851         return mHighlightColor;
4852     }
4853 
4854     /**
4855      * Sets whether the soft input method will be made visible when this
4856      * TextView gets focused. The default is true.
4857      */
4858     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)4859     public final void setShowSoftInputOnFocus(boolean show) {
4860         createEditorIfNeeded();
4861         mEditor.mShowSoftInputOnFocus = show;
4862     }
4863 
4864     /**
4865      * Returns whether the soft input method will be made visible when this
4866      * TextView gets focused. The default is true.
4867      */
getShowSoftInputOnFocus()4868     public final boolean getShowSoftInputOnFocus() {
4869         // When there is no Editor, return default true value
4870         return mEditor == null || mEditor.mShowSoftInputOnFocus;
4871     }
4872 
4873     /**
4874      * Gives the text a shadow of the specified blur radius and color, the specified
4875      * distance from its drawn position.
4876      * <p>
4877      * The text shadow produced does not interact with the properties on view
4878      * that are responsible for real time shadows,
4879      * {@link View#getElevation() elevation} and
4880      * {@link View#getTranslationZ() translationZ}.
4881      *
4882      * @see Paint#setShadowLayer(float, float, float, int)
4883      *
4884      * @attr ref android.R.styleable#TextView_shadowColor
4885      * @attr ref android.R.styleable#TextView_shadowDx
4886      * @attr ref android.R.styleable#TextView_shadowDy
4887      * @attr ref android.R.styleable#TextView_shadowRadius
4888      */
setShadowLayer(float radius, float dx, float dy, int color)4889     public void setShadowLayer(float radius, float dx, float dy, int color) {
4890         mTextPaint.setShadowLayer(radius, dx, dy, color);
4891 
4892         mShadowRadius = radius;
4893         mShadowDx = dx;
4894         mShadowDy = dy;
4895         mShadowColor = color;
4896 
4897         // Will change text clip region
4898         if (mEditor != null) {
4899             mEditor.invalidateTextDisplayList();
4900             mEditor.invalidateHandlesAndActionMode();
4901         }
4902         invalidate();
4903     }
4904 
4905     /**
4906      * Gets the radius of the shadow layer.
4907      *
4908      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
4909      *
4910      * @see #setShadowLayer(float, float, float, int)
4911      *
4912      * @attr ref android.R.styleable#TextView_shadowRadius
4913      */
4914     @InspectableProperty
getShadowRadius()4915     public float getShadowRadius() {
4916         return mShadowRadius;
4917     }
4918 
4919     /**
4920      * @return the horizontal offset of the shadow layer
4921      *
4922      * @see #setShadowLayer(float, float, float, int)
4923      *
4924      * @attr ref android.R.styleable#TextView_shadowDx
4925      */
4926     @InspectableProperty
getShadowDx()4927     public float getShadowDx() {
4928         return mShadowDx;
4929     }
4930 
4931     /**
4932      * Gets the vertical offset of the shadow layer.
4933      * @return The vertical offset of the shadow layer.
4934      *
4935      * @see #setShadowLayer(float, float, float, int)
4936      *
4937      * @attr ref android.R.styleable#TextView_shadowDy
4938      */
4939     @InspectableProperty
getShadowDy()4940     public float getShadowDy() {
4941         return mShadowDy;
4942     }
4943 
4944     /**
4945      * Gets the color of the shadow layer.
4946      * @return the color of the shadow layer
4947      *
4948      * @see #setShadowLayer(float, float, float, int)
4949      *
4950      * @attr ref android.R.styleable#TextView_shadowColor
4951      */
4952     @InspectableProperty
4953     @ColorInt
getShadowColor()4954     public int getShadowColor() {
4955         return mShadowColor;
4956     }
4957 
4958     /**
4959      * Gets the {@link TextPaint} used for the text.
4960      * Use this only to consult the Paint's properties and not to change them.
4961      * @return The base paint used for the text.
4962      */
getPaint()4963     public TextPaint getPaint() {
4964         return mTextPaint;
4965     }
4966 
4967     /**
4968      * Sets the autolink mask of the text.  See {@link
4969      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
4970      * possible values.
4971      *
4972      * <p class="note"><b>Note:</b>
4973      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
4974      * is deprecated and should be avoided; see its documentation.
4975      *
4976      * @attr ref android.R.styleable#TextView_autoLink
4977      */
4978     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)4979     public final void setAutoLinkMask(int mask) {
4980         mAutoLinkMask = mask;
4981     }
4982 
4983     /**
4984      * Sets whether the movement method will automatically be set to
4985      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4986      * set to nonzero and links are detected in {@link #setText}.
4987      * The default is true.
4988      *
4989      * @attr ref android.R.styleable#TextView_linksClickable
4990      */
4991     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)4992     public final void setLinksClickable(boolean whether) {
4993         mLinksClickable = whether;
4994     }
4995 
4996     /**
4997      * Returns whether the movement method will automatically be set to
4998      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4999      * set to nonzero and links are detected in {@link #setText}.
5000      * The default is true.
5001      *
5002      * @attr ref android.R.styleable#TextView_linksClickable
5003      */
5004     @InspectableProperty
getLinksClickable()5005     public final boolean getLinksClickable() {
5006         return mLinksClickable;
5007     }
5008 
5009     /**
5010      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5011      * (by {@link Linkify} or otherwise) if any.  You can call
5012      * {@link URLSpan#getURL} on them to find where they link to
5013      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5014      * to find the region of the text they are attached to.
5015      */
getUrls()5016     public URLSpan[] getUrls() {
5017         if (mText instanceof Spanned) {
5018             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5019         } else {
5020             return new URLSpan[0];
5021         }
5022     }
5023 
5024     /**
5025      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5026      * TextView.
5027      *
5028      * @see #setHintTextColor(ColorStateList)
5029      * @see #getHintTextColors()
5030      * @see #setTextColor(int)
5031      *
5032      * @attr ref android.R.styleable#TextView_textColorHint
5033      */
5034     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)5035     public final void setHintTextColor(@ColorInt int color) {
5036         mHintTextColor = ColorStateList.valueOf(color);
5037         updateTextColors();
5038     }
5039 
5040     /**
5041      * Sets the color of the hint text.
5042      *
5043      * @see #getHintTextColors()
5044      * @see #setHintTextColor(int)
5045      * @see #setTextColor(ColorStateList)
5046      * @see #setLinkTextColor(ColorStateList)
5047      *
5048      * @attr ref android.R.styleable#TextView_textColorHint
5049      */
setHintTextColor(ColorStateList colors)5050     public final void setHintTextColor(ColorStateList colors) {
5051         mHintTextColor = colors;
5052         updateTextColors();
5053     }
5054 
5055     /**
5056      * @return the color of the hint text, for the different states of this TextView.
5057      *
5058      * @see #setHintTextColor(ColorStateList)
5059      * @see #setHintTextColor(int)
5060      * @see #setTextColor(ColorStateList)
5061      * @see #setLinkTextColor(ColorStateList)
5062      *
5063      * @attr ref android.R.styleable#TextView_textColorHint
5064      */
5065     @InspectableProperty(name = "textColorHint")
getHintTextColors()5066     public final ColorStateList getHintTextColors() {
5067         return mHintTextColor;
5068     }
5069 
5070     /**
5071      * <p>Return the current color selected to paint the hint text.</p>
5072      *
5073      * @return Returns the current hint text color.
5074      */
5075     @ColorInt
getCurrentHintTextColor()5076     public final int getCurrentHintTextColor() {
5077         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5078     }
5079 
5080     /**
5081      * Sets the color of links in the text.
5082      *
5083      * @see #setLinkTextColor(ColorStateList)
5084      * @see #getLinkTextColors()
5085      *
5086      * @attr ref android.R.styleable#TextView_textColorLink
5087      */
5088     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)5089     public final void setLinkTextColor(@ColorInt int color) {
5090         mLinkTextColor = ColorStateList.valueOf(color);
5091         updateTextColors();
5092     }
5093 
5094     /**
5095      * Sets the color of links in the text.
5096      *
5097      * @see #setLinkTextColor(int)
5098      * @see #getLinkTextColors()
5099      * @see #setTextColor(ColorStateList)
5100      * @see #setHintTextColor(ColorStateList)
5101      *
5102      * @attr ref android.R.styleable#TextView_textColorLink
5103      */
setLinkTextColor(ColorStateList colors)5104     public final void setLinkTextColor(ColorStateList colors) {
5105         mLinkTextColor = colors;
5106         updateTextColors();
5107     }
5108 
5109     /**
5110      * @return the list of colors used to paint the links in the text, for the different states of
5111      * this TextView
5112      *
5113      * @see #setLinkTextColor(ColorStateList)
5114      * @see #setLinkTextColor(int)
5115      *
5116      * @attr ref android.R.styleable#TextView_textColorLink
5117      */
5118     @InspectableProperty(name = "textColorLink")
getLinkTextColors()5119     public final ColorStateList getLinkTextColors() {
5120         return mLinkTextColor;
5121     }
5122 
5123     /**
5124      * Sets the horizontal alignment of the text and the
5125      * vertical gravity that will be used when there is extra space
5126      * in the TextView beyond what is required for the text itself.
5127      *
5128      * @see android.view.Gravity
5129      * @attr ref android.R.styleable#TextView_gravity
5130      */
setGravity(int gravity)5131     public void setGravity(int gravity) {
5132         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5133             gravity |= Gravity.START;
5134         }
5135         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5136             gravity |= Gravity.TOP;
5137         }
5138 
5139         boolean newLayout = false;
5140 
5141         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5142                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5143             newLayout = true;
5144         }
5145 
5146         if (gravity != mGravity) {
5147             invalidate();
5148         }
5149 
5150         mGravity = gravity;
5151 
5152         if (mLayout != null && newLayout) {
5153             // XXX this is heavy-handed because no actual content changes.
5154             int want = mLayout.getWidth();
5155             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5156 
5157             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5158                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5159         }
5160     }
5161 
5162     /**
5163      * Returns the horizontal and vertical alignment of this TextView.
5164      *
5165      * @see android.view.Gravity
5166      * @attr ref android.R.styleable#TextView_gravity
5167      */
5168     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()5169     public int getGravity() {
5170         return mGravity;
5171     }
5172 
5173     /**
5174      * Gets the flags on the Paint being used to display the text.
5175      * @return The flags on the Paint being used to display the text.
5176      * @see Paint#getFlags
5177      */
getPaintFlags()5178     public int getPaintFlags() {
5179         return mTextPaint.getFlags();
5180     }
5181 
5182     /**
5183      * Sets flags on the Paint being used to display the text and
5184      * reflows the text if they are different from the old flags.
5185      * @see Paint#setFlags
5186      */
5187     @android.view.RemotableViewMethod
setPaintFlags(int flags)5188     public void setPaintFlags(int flags) {
5189         if (mTextPaint.getFlags() != flags) {
5190             mTextPaint.setFlags(flags);
5191 
5192             if (mLayout != null) {
5193                 nullLayouts();
5194                 requestLayout();
5195                 invalidate();
5196             }
5197         }
5198     }
5199 
5200     /**
5201      * Sets whether the text should be allowed to be wider than the
5202      * View is.  If false, it will be wrapped to the width of the View.
5203      *
5204      * @attr ref android.R.styleable#TextView_scrollHorizontally
5205      */
setHorizontallyScrolling(boolean whether)5206     public void setHorizontallyScrolling(boolean whether) {
5207         if (mHorizontallyScrolling != whether) {
5208             mHorizontallyScrolling = whether;
5209 
5210             if (mLayout != null) {
5211                 nullLayouts();
5212                 requestLayout();
5213                 invalidate();
5214             }
5215         }
5216     }
5217 
5218     /**
5219      * Returns whether the text is allowed to be wider than the View.
5220      * If false, the text will be wrapped to the width of the View.
5221      *
5222      * @attr ref android.R.styleable#TextView_scrollHorizontally
5223      * @see #setHorizontallyScrolling(boolean)
5224      */
5225     @InspectableProperty(name = "scrollHorizontally")
isHorizontallyScrollable()5226     public final boolean isHorizontallyScrollable() {
5227         return mHorizontallyScrolling;
5228     }
5229 
5230     /**
5231      * Returns whether the text is allowed to be wider than the View.
5232      * If false, the text will be wrapped to the width of the View.
5233      *
5234      * @attr ref android.R.styleable#TextView_scrollHorizontally
5235      * @hide
5236      */
5237     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
getHorizontallyScrolling()5238     public boolean getHorizontallyScrolling() {
5239         return mHorizontallyScrolling;
5240     }
5241 
5242     /**
5243      * Sets the height of the TextView to be at least {@code minLines} tall.
5244      * <p>
5245      * This value is used for height calculation if LayoutParams does not force TextView to have an
5246      * exact height. Setting this value overrides other previous minimum height configurations such
5247      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
5248      * this value to 1.
5249      *
5250      * @param minLines the minimum height of TextView in terms of number of lines
5251      *
5252      * @see #getMinLines()
5253      * @see #setLines(int)
5254      *
5255      * @attr ref android.R.styleable#TextView_minLines
5256      */
5257     @android.view.RemotableViewMethod
setMinLines(int minLines)5258     public void setMinLines(int minLines) {
5259         mMinimum = minLines;
5260         mMinMode = LINES;
5261 
5262         requestLayout();
5263         invalidate();
5264     }
5265 
5266     /**
5267      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
5268      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
5269      *
5270      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
5271      *         height is not defined in lines
5272      *
5273      * @see #setMinLines(int)
5274      * @see #setLines(int)
5275      *
5276      * @attr ref android.R.styleable#TextView_minLines
5277      */
5278     @InspectableProperty
getMinLines()5279     public int getMinLines() {
5280         return mMinMode == LINES ? mMinimum : -1;
5281     }
5282 
5283     /**
5284      * Sets the height of the TextView to be at least {@code minPixels} tall.
5285      * <p>
5286      * This value is used for height calculation if LayoutParams does not force TextView to have an
5287      * exact height. Setting this value overrides previous minimum height configurations such as
5288      * {@link #setMinLines(int)} or {@link #setLines(int)}.
5289      * <p>
5290      * The value given here is different than {@link #setMinimumHeight(int)}. Between
5291      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
5292      * used to decide the final height.
5293      *
5294      * @param minPixels the minimum height of TextView in terms of pixels
5295      *
5296      * @see #getMinHeight()
5297      * @see #setHeight(int)
5298      *
5299      * @attr ref android.R.styleable#TextView_minHeight
5300      */
5301     @android.view.RemotableViewMethod
setMinHeight(int minPixels)5302     public void setMinHeight(int minPixels) {
5303         mMinimum = minPixels;
5304         mMinMode = PIXELS;
5305 
5306         requestLayout();
5307         invalidate();
5308     }
5309 
5310     /**
5311      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
5312      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
5313      *
5314      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
5315      *         defined in pixels
5316      *
5317      * @see #setMinHeight(int)
5318      * @see #setHeight(int)
5319      *
5320      * @attr ref android.R.styleable#TextView_minHeight
5321      */
getMinHeight()5322     public int getMinHeight() {
5323         return mMinMode == PIXELS ? mMinimum : -1;
5324     }
5325 
5326     /**
5327      * Sets the height of the TextView to be at most {@code maxLines} tall.
5328      * <p>
5329      * This value is used for height calculation if LayoutParams does not force TextView to have an
5330      * exact height. Setting this value overrides previous maximum height configurations such as
5331      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
5332      *
5333      * @param maxLines the maximum height of TextView in terms of number of lines
5334      *
5335      * @see #getMaxLines()
5336      * @see #setLines(int)
5337      *
5338      * @attr ref android.R.styleable#TextView_maxLines
5339      */
5340     @android.view.RemotableViewMethod
setMaxLines(int maxLines)5341     public void setMaxLines(int maxLines) {
5342         mMaximum = maxLines;
5343         mMaxMode = LINES;
5344 
5345         requestLayout();
5346         invalidate();
5347     }
5348 
5349     /**
5350      * Returns the maximum height of TextView in terms of number of lines or -1 if the
5351      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
5352      *
5353      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
5354      *         is not defined in lines.
5355      *
5356      * @see #setMaxLines(int)
5357      * @see #setLines(int)
5358      *
5359      * @attr ref android.R.styleable#TextView_maxLines
5360      */
5361     @InspectableProperty
getMaxLines()5362     public int getMaxLines() {
5363         return mMaxMode == LINES ? mMaximum : -1;
5364     }
5365 
5366     /**
5367      * Sets the height of the TextView to be at most {@code maxPixels} tall.
5368      * <p>
5369      * This value is used for height calculation if LayoutParams does not force TextView to have an
5370      * exact height. Setting this value overrides previous maximum height configurations such as
5371      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
5372      *
5373      * @param maxPixels the maximum height of TextView in terms of pixels
5374      *
5375      * @see #getMaxHeight()
5376      * @see #setHeight(int)
5377      *
5378      * @attr ref android.R.styleable#TextView_maxHeight
5379      */
5380     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)5381     public void setMaxHeight(int maxPixels) {
5382         mMaximum = maxPixels;
5383         mMaxMode = PIXELS;
5384 
5385         requestLayout();
5386         invalidate();
5387     }
5388 
5389     /**
5390      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
5391      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
5392      *
5393      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
5394      *         is not defined in pixels
5395      *
5396      * @see #setMaxHeight(int)
5397      * @see #setHeight(int)
5398      *
5399      * @attr ref android.R.styleable#TextView_maxHeight
5400      */
5401     @InspectableProperty
getMaxHeight()5402     public int getMaxHeight() {
5403         return mMaxMode == PIXELS ? mMaximum : -1;
5404     }
5405 
5406     /**
5407      * Sets the height of the TextView to be exactly {@code lines} tall.
5408      * <p>
5409      * This value is used for height calculation if LayoutParams does not force TextView to have an
5410      * exact height. Setting this value overrides previous minimum/maximum height configurations
5411      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
5412      * set this value to 1.
5413      *
5414      * @param lines the exact height of the TextView in terms of lines
5415      *
5416      * @see #setHeight(int)
5417      *
5418      * @attr ref android.R.styleable#TextView_lines
5419      */
5420     @android.view.RemotableViewMethod
setLines(int lines)5421     public void setLines(int lines) {
5422         mMaximum = mMinimum = lines;
5423         mMaxMode = mMinMode = LINES;
5424 
5425         requestLayout();
5426         invalidate();
5427     }
5428 
5429     /**
5430      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
5431      * <p>
5432      * This value is used for height calculation if LayoutParams does not force TextView to have an
5433      * exact height. Setting this value overrides previous minimum/maximum height configurations
5434      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
5435      *
5436      * @param pixels the exact height of the TextView in terms of pixels
5437      *
5438      * @see #setLines(int)
5439      *
5440      * @attr ref android.R.styleable#TextView_height
5441      */
5442     @android.view.RemotableViewMethod
setHeight(int pixels)5443     public void setHeight(int pixels) {
5444         mMaximum = mMinimum = pixels;
5445         mMaxMode = mMinMode = PIXELS;
5446 
5447         requestLayout();
5448         invalidate();
5449     }
5450 
5451     /**
5452      * Sets the width of the TextView to be at least {@code minEms} wide.
5453      * <p>
5454      * This value is used for width calculation if LayoutParams does not force TextView to have an
5455      * exact width. Setting this value overrides previous minimum width configurations such as
5456      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5457      *
5458      * @param minEms the minimum width of TextView in terms of ems
5459      *
5460      * @see #getMinEms()
5461      * @see #setEms(int)
5462      *
5463      * @attr ref android.R.styleable#TextView_minEms
5464      */
5465     @android.view.RemotableViewMethod
setMinEms(int minEms)5466     public void setMinEms(int minEms) {
5467         mMinWidth = minEms;
5468         mMinWidthMode = EMS;
5469 
5470         requestLayout();
5471         invalidate();
5472     }
5473 
5474     /**
5475      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
5476      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5477      *
5478      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
5479      *         defined in ems
5480      *
5481      * @see #setMinEms(int)
5482      * @see #setEms(int)
5483      *
5484      * @attr ref android.R.styleable#TextView_minEms
5485      */
5486     @InspectableProperty
getMinEms()5487     public int getMinEms() {
5488         return mMinWidthMode == EMS ? mMinWidth : -1;
5489     }
5490 
5491     /**
5492      * Sets the width of the TextView to be at least {@code minPixels} wide.
5493      * <p>
5494      * This value is used for width calculation if LayoutParams does not force TextView to have an
5495      * exact width. Setting this value overrides previous minimum width configurations such as
5496      * {@link #setMinEms(int)} or {@link #setEms(int)}.
5497      * <p>
5498      * The value given here is different than {@link #setMinimumWidth(int)}. Between
5499      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
5500      * to decide the final width.
5501      *
5502      * @param minPixels the minimum width of TextView in terms of pixels
5503      *
5504      * @see #getMinWidth()
5505      * @see #setWidth(int)
5506      *
5507      * @attr ref android.R.styleable#TextView_minWidth
5508      */
5509     @android.view.RemotableViewMethod
setMinWidth(int minPixels)5510     public void setMinWidth(int minPixels) {
5511         mMinWidth = minPixels;
5512         mMinWidthMode = PIXELS;
5513 
5514         requestLayout();
5515         invalidate();
5516     }
5517 
5518     /**
5519      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
5520      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
5521      *
5522      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
5523      *         defined in pixels
5524      *
5525      * @see #setMinWidth(int)
5526      * @see #setWidth(int)
5527      *
5528      * @attr ref android.R.styleable#TextView_minWidth
5529      */
5530     @InspectableProperty
getMinWidth()5531     public int getMinWidth() {
5532         return mMinWidthMode == PIXELS ? mMinWidth : -1;
5533     }
5534 
5535     /**
5536      * Sets the width of the TextView to be at most {@code maxEms} wide.
5537      * <p>
5538      * This value is used for width calculation if LayoutParams does not force TextView to have an
5539      * exact width. Setting this value overrides previous maximum width configurations such as
5540      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5541      *
5542      * @param maxEms the maximum width of TextView in terms of ems
5543      *
5544      * @see #getMaxEms()
5545      * @see #setEms(int)
5546      *
5547      * @attr ref android.R.styleable#TextView_maxEms
5548      */
5549     @android.view.RemotableViewMethod
setMaxEms(int maxEms)5550     public void setMaxEms(int maxEms) {
5551         mMaxWidth = maxEms;
5552         mMaxWidthMode = EMS;
5553 
5554         requestLayout();
5555         invalidate();
5556     }
5557 
5558     /**
5559      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
5560      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5561      *
5562      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
5563      *         defined in ems
5564      *
5565      * @see #setMaxEms(int)
5566      * @see #setEms(int)
5567      *
5568      * @attr ref android.R.styleable#TextView_maxEms
5569      */
5570     @InspectableProperty
getMaxEms()5571     public int getMaxEms() {
5572         return mMaxWidthMode == EMS ? mMaxWidth : -1;
5573     }
5574 
5575     /**
5576      * Sets the width of the TextView to be at most {@code maxPixels} wide.
5577      * <p>
5578      * This value is used for width calculation if LayoutParams does not force TextView to have an
5579      * exact width. Setting this value overrides previous maximum width configurations such as
5580      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
5581      *
5582      * @param maxPixels the maximum width of TextView in terms of pixels
5583      *
5584      * @see #getMaxWidth()
5585      * @see #setWidth(int)
5586      *
5587      * @attr ref android.R.styleable#TextView_maxWidth
5588      */
5589     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)5590     public void setMaxWidth(int maxPixels) {
5591         mMaxWidth = maxPixels;
5592         mMaxWidthMode = PIXELS;
5593 
5594         requestLayout();
5595         invalidate();
5596     }
5597 
5598     /**
5599      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
5600      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
5601      *
5602      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
5603      *         defined in pixels
5604      *
5605      * @see #setMaxWidth(int)
5606      * @see #setWidth(int)
5607      *
5608      * @attr ref android.R.styleable#TextView_maxWidth
5609      */
5610     @InspectableProperty
getMaxWidth()5611     public int getMaxWidth() {
5612         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
5613     }
5614 
5615     /**
5616      * Sets the width of the TextView to be exactly {@code ems} wide.
5617      *
5618      * This value is used for width calculation if LayoutParams does not force TextView to have an
5619      * exact width. Setting this value overrides previous minimum/maximum configurations such as
5620      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
5621      *
5622      * @param ems the exact width of the TextView in terms of ems
5623      *
5624      * @see #setWidth(int)
5625      *
5626      * @attr ref android.R.styleable#TextView_ems
5627      */
5628     @android.view.RemotableViewMethod
setEms(int ems)5629     public void setEms(int ems) {
5630         mMaxWidth = mMinWidth = ems;
5631         mMaxWidthMode = mMinWidthMode = EMS;
5632 
5633         requestLayout();
5634         invalidate();
5635     }
5636 
5637     /**
5638      * Sets the width of the TextView to be exactly {@code pixels} wide.
5639      * <p>
5640      * This value is used for width calculation if LayoutParams does not force TextView to have an
5641      * exact width. Setting this value overrides previous minimum/maximum width configurations
5642      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
5643      *
5644      * @param pixels the exact width of the TextView in terms of pixels
5645      *
5646      * @see #setEms(int)
5647      *
5648      * @attr ref android.R.styleable#TextView_width
5649      */
5650     @android.view.RemotableViewMethod
setWidth(int pixels)5651     public void setWidth(int pixels) {
5652         mMaxWidth = mMinWidth = pixels;
5653         mMaxWidthMode = mMinWidthMode = PIXELS;
5654 
5655         requestLayout();
5656         invalidate();
5657     }
5658 
5659     /**
5660      * Sets line spacing for this TextView.  Each line other than the last line will have its height
5661      * multiplied by {@code mult} and have {@code add} added to it.
5662      *
5663      * @param add The value in pixels that should be added to each line other than the last line.
5664      *            This will be applied after the multiplier
5665      * @param mult The value by which each line height other than the last line will be multiplied
5666      *             by
5667      *
5668      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5669      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5670      */
setLineSpacing(float add, float mult)5671     public void setLineSpacing(float add, float mult) {
5672         if (mSpacingAdd != add || mSpacingMult != mult) {
5673             mSpacingAdd = add;
5674             mSpacingMult = mult;
5675 
5676             if (mLayout != null) {
5677                 nullLayouts();
5678                 requestLayout();
5679                 invalidate();
5680             }
5681         }
5682     }
5683 
5684     /**
5685      * Gets the line spacing multiplier
5686      *
5687      * @return the value by which each line's height is multiplied to get its actual height.
5688      *
5689      * @see #setLineSpacing(float, float)
5690      * @see #getLineSpacingExtra()
5691      *
5692      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5693      */
5694     @InspectableProperty
getLineSpacingMultiplier()5695     public float getLineSpacingMultiplier() {
5696         return mSpacingMult;
5697     }
5698 
5699     /**
5700      * Gets the line spacing extra space
5701      *
5702      * @return the extra space that is added to the height of each lines of this TextView.
5703      *
5704      * @see #setLineSpacing(float, float)
5705      * @see #getLineSpacingMultiplier()
5706      *
5707      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5708      */
5709     @InspectableProperty
getLineSpacingExtra()5710     public float getLineSpacingExtra() {
5711         return mSpacingAdd;
5712     }
5713 
5714     /**
5715      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
5716      * between subsequent baselines in the TextView.
5717      *
5718      * @param lineHeight the line height in pixels
5719      *
5720      * @see #setLineSpacing(float, float)
5721      * @see #getLineSpacingExtra()
5722      *
5723      * @attr ref android.R.styleable#TextView_lineHeight
5724      */
setLineHeight(@x @ntRangefrom = 0) int lineHeight)5725     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
5726         Preconditions.checkArgumentNonnegative(lineHeight);
5727 
5728         final int fontHeight = getPaint().getFontMetricsInt(null);
5729         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
5730         if (lineHeight != fontHeight) {
5731             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
5732             setLineSpacing(lineHeight - fontHeight, 1f);
5733         }
5734     }
5735 
5736     /**
5737      * Convenience method to append the specified text to the TextView's
5738      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5739      * if it was not already editable.
5740      *
5741      * @param text text to be appended to the already displayed text
5742      */
append(CharSequence text)5743     public final void append(CharSequence text) {
5744         append(text, 0, text.length());
5745     }
5746 
5747     /**
5748      * Convenience method to append the specified text slice to the TextView's
5749      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5750      * if it was not already editable.
5751      *
5752      * @param text text to be appended to the already displayed text
5753      * @param start the index of the first character in the {@code text}
5754      * @param end the index of the character following the last character in the {@code text}
5755      *
5756      * @see Appendable#append(CharSequence, int, int)
5757      */
append(CharSequence text, int start, int end)5758     public void append(CharSequence text, int start, int end) {
5759         if (!(mText instanceof Editable)) {
5760             setText(mText, BufferType.EDITABLE);
5761         }
5762 
5763         ((Editable) mText).append(text, start, end);
5764 
5765         if (mAutoLinkMask != 0) {
5766             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
5767             // Do not change the movement method for text that support text selection as it
5768             // would prevent an arbitrary cursor displacement.
5769             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
5770                 setMovementMethod(LinkMovementMethod.getInstance());
5771             }
5772         }
5773     }
5774 
updateTextColors()5775     private void updateTextColors() {
5776         boolean inval = false;
5777         final int[] drawableState = getDrawableState();
5778         int color = mTextColor.getColorForState(drawableState, 0);
5779         if (color != mCurTextColor) {
5780             mCurTextColor = color;
5781             inval = true;
5782         }
5783         if (mLinkTextColor != null) {
5784             color = mLinkTextColor.getColorForState(drawableState, 0);
5785             if (color != mTextPaint.linkColor) {
5786                 mTextPaint.linkColor = color;
5787                 inval = true;
5788             }
5789         }
5790         if (mHintTextColor != null) {
5791             color = mHintTextColor.getColorForState(drawableState, 0);
5792             if (color != mCurHintTextColor) {
5793                 mCurHintTextColor = color;
5794                 if (mText.length() == 0) {
5795                     inval = true;
5796                 }
5797             }
5798         }
5799         if (inval) {
5800             // Text needs to be redrawn with the new color
5801             if (mEditor != null) mEditor.invalidateTextDisplayList();
5802             invalidate();
5803         }
5804     }
5805 
5806     @Override
drawableStateChanged()5807     protected void drawableStateChanged() {
5808         super.drawableStateChanged();
5809 
5810         if (mTextColor != null && mTextColor.isStateful()
5811                 || (mHintTextColor != null && mHintTextColor.isStateful())
5812                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
5813             updateTextColors();
5814         }
5815 
5816         if (mDrawables != null) {
5817             final int[] state = getDrawableState();
5818             for (Drawable dr : mDrawables.mShowing) {
5819                 if (dr != null && dr.isStateful() && dr.setState(state)) {
5820                     invalidateDrawable(dr);
5821                 }
5822             }
5823         }
5824     }
5825 
5826     @Override
drawableHotspotChanged(float x, float y)5827     public void drawableHotspotChanged(float x, float y) {
5828         super.drawableHotspotChanged(x, y);
5829 
5830         if (mDrawables != null) {
5831             for (Drawable dr : mDrawables.mShowing) {
5832                 if (dr != null) {
5833                     dr.setHotspot(x, y);
5834                 }
5835             }
5836         }
5837     }
5838 
5839     @Override
onSaveInstanceState()5840     public Parcelable onSaveInstanceState() {
5841         Parcelable superState = super.onSaveInstanceState();
5842 
5843         // Save state if we are forced to
5844         final boolean freezesText = getFreezesText();
5845         boolean hasSelection = false;
5846         int start = -1;
5847         int end = -1;
5848 
5849         if (mText != null) {
5850             start = getSelectionStart();
5851             end = getSelectionEnd();
5852             if (start >= 0 || end >= 0) {
5853                 // Or save state if there is a selection
5854                 hasSelection = true;
5855             }
5856         }
5857 
5858         if (freezesText || hasSelection) {
5859             SavedState ss = new SavedState(superState);
5860 
5861             if (freezesText) {
5862                 if (mText instanceof Spanned) {
5863                     final Spannable sp = new SpannableStringBuilder(mText);
5864 
5865                     if (mEditor != null) {
5866                         removeMisspelledSpans(sp);
5867                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
5868                     }
5869 
5870                     ss.text = sp;
5871                 } else {
5872                     ss.text = mText.toString();
5873                 }
5874             }
5875 
5876             if (hasSelection) {
5877                 // XXX Should also save the current scroll position!
5878                 ss.selStart = start;
5879                 ss.selEnd = end;
5880             }
5881 
5882             if (isFocused() && start >= 0 && end >= 0) {
5883                 ss.frozenWithFocus = true;
5884             }
5885 
5886             ss.error = getError();
5887 
5888             if (mEditor != null) {
5889                 ss.editorState = mEditor.saveInstanceState();
5890             }
5891             return ss;
5892         }
5893 
5894         return superState;
5895     }
5896 
removeMisspelledSpans(Spannable spannable)5897     void removeMisspelledSpans(Spannable spannable) {
5898         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
5899                 SuggestionSpan.class);
5900         for (int i = 0; i < suggestionSpans.length; i++) {
5901             int flags = suggestionSpans[i].getFlags();
5902             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
5903                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
5904                 spannable.removeSpan(suggestionSpans[i]);
5905             }
5906         }
5907     }
5908 
5909     @Override
onRestoreInstanceState(Parcelable state)5910     public void onRestoreInstanceState(Parcelable state) {
5911         if (!(state instanceof SavedState)) {
5912             super.onRestoreInstanceState(state);
5913             return;
5914         }
5915 
5916         SavedState ss = (SavedState) state;
5917         super.onRestoreInstanceState(ss.getSuperState());
5918 
5919         // XXX restore buffer type too, as well as lots of other stuff
5920         if (ss.text != null) {
5921             setText(ss.text);
5922         }
5923 
5924         if (ss.selStart >= 0 && ss.selEnd >= 0) {
5925             if (mSpannable != null) {
5926                 int len = mText.length();
5927 
5928                 if (ss.selStart > len || ss.selEnd > len) {
5929                     String restored = "";
5930 
5931                     if (ss.text != null) {
5932                         restored = "(restored) ";
5933                     }
5934 
5935                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
5936                             + " out of range for " + restored + "text " + mText);
5937                 } else {
5938                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
5939 
5940                     if (ss.frozenWithFocus) {
5941                         createEditorIfNeeded();
5942                         mEditor.mFrozenWithFocus = true;
5943                     }
5944                 }
5945             }
5946         }
5947 
5948         if (ss.error != null) {
5949             final CharSequence error = ss.error;
5950             // Display the error later, after the first layout pass
5951             post(new Runnable() {
5952                 public void run() {
5953                     if (mEditor == null || !mEditor.mErrorWasChanged) {
5954                         setError(error);
5955                     }
5956                 }
5957             });
5958         }
5959 
5960         if (ss.editorState != null) {
5961             createEditorIfNeeded();
5962             mEditor.restoreInstanceState(ss.editorState);
5963         }
5964     }
5965 
5966     /**
5967      * Control whether this text view saves its entire text contents when
5968      * freezing to an icicle, in addition to dynamic state such as cursor
5969      * position.  By default this is false, not saving the text.  Set to true
5970      * if the text in the text view is not being saved somewhere else in
5971      * persistent storage (such as in a content provider) so that if the
5972      * view is later thawed the user will not lose their data. For
5973      * {@link android.widget.EditText} it is always enabled, regardless of
5974      * the value of the attribute.
5975      *
5976      * @param freezesText Controls whether a frozen icicle should include the
5977      * entire text data: true to include it, false to not.
5978      *
5979      * @attr ref android.R.styleable#TextView_freezesText
5980      */
5981     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)5982     public void setFreezesText(boolean freezesText) {
5983         mFreezesText = freezesText;
5984     }
5985 
5986     /**
5987      * Return whether this text view is including its entire text contents
5988      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
5989      *
5990      * @return Returns true if text is included, false if it isn't.
5991      *
5992      * @see #setFreezesText
5993      */
5994     @InspectableProperty
getFreezesText()5995     public boolean getFreezesText() {
5996         return mFreezesText;
5997     }
5998 
5999     ///////////////////////////////////////////////////////////////////////////
6000 
6001     /**
6002      * Sets the Factory used to create new {@link Editable Editables}.
6003      *
6004      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
6005      *
6006      * @see android.text.Editable.Factory
6007      * @see android.widget.TextView.BufferType#EDITABLE
6008      */
setEditableFactory(Editable.Factory factory)6009     public final void setEditableFactory(Editable.Factory factory) {
6010         mEditableFactory = factory;
6011         setText(mText);
6012     }
6013 
6014     /**
6015      * Sets the Factory used to create new {@link Spannable Spannables}.
6016      *
6017      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
6018      *
6019      * @see android.text.Spannable.Factory
6020      * @see android.widget.TextView.BufferType#SPANNABLE
6021      */
setSpannableFactory(Spannable.Factory factory)6022     public final void setSpannableFactory(Spannable.Factory factory) {
6023         mSpannableFactory = factory;
6024         setText(mText);
6025     }
6026 
6027     /**
6028      * Sets the text to be displayed. TextView <em>does not</em> accept
6029      * HTML-like formatting, which you can do with text strings in XML resource files.
6030      * To style your strings, attach android.text.style.* objects to a
6031      * {@link android.text.SpannableString}, or see the
6032      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
6033      * Available Resource Types</a> documentation for an example of setting
6034      * formatted text in the XML resource file.
6035      * <p/>
6036      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6037      * intermediate {@link Spannable Spannables}. Likewise it will use
6038      * {@link android.text.Editable.Factory} to create final or intermediate
6039      * {@link Editable Editables}.
6040      *
6041      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
6042      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
6043      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
6044      *
6045      * @param text text to be displayed
6046      *
6047      * @attr ref android.R.styleable#TextView_text
6048      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
6049      *                                  parameters used to create the PrecomputedText mismatches
6050      *                                  with this TextView.
6051      */
6052     @android.view.RemotableViewMethod
setText(CharSequence text)6053     public final void setText(CharSequence text) {
6054         setText(text, mBufferType);
6055     }
6056 
6057     /**
6058      * Sets the text to be displayed but retains the cursor position. Same as
6059      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
6060      * new text.
6061      * <p/>
6062      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6063      * intermediate {@link Spannable Spannables}. Likewise it will use
6064      * {@link android.text.Editable.Factory} to create final or intermediate
6065      * {@link Editable Editables}.
6066      *
6067      * @param text text to be displayed
6068      *
6069      * @see #setText(CharSequence)
6070      */
6071     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)6072     public final void setTextKeepState(CharSequence text) {
6073         setTextKeepState(text, mBufferType);
6074     }
6075 
6076     /**
6077      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
6078      * <p/>
6079      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6080      * intermediate {@link Spannable Spannables}. Likewise it will use
6081      * {@link android.text.Editable.Factory} to create final or intermediate
6082      * {@link Editable Editables}.
6083      *
6084      * Subclasses overriding this method should ensure that the following post condition holds,
6085      * in order to guarantee the safety of the view's measurement and layout operations:
6086      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
6087      * will be different from {@code null}.
6088      *
6089      * @param text text to be displayed
6090      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6091      *              stored as a static text, styleable/spannable text, or editable text
6092      *
6093      * @see #setText(CharSequence)
6094      * @see android.widget.TextView.BufferType
6095      * @see #setSpannableFactory(Spannable.Factory)
6096      * @see #setEditableFactory(Editable.Factory)
6097      *
6098      * @attr ref android.R.styleable#TextView_text
6099      * @attr ref android.R.styleable#TextView_bufferType
6100      */
setText(CharSequence text, BufferType type)6101     public void setText(CharSequence text, BufferType type) {
6102         setText(text, type, true, 0);
6103 
6104         if (mCharWrapper != null) {
6105             mCharWrapper.mChars = null;
6106         }
6107     }
6108 
6109     @UnsupportedAppUsage
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6110     private void setText(CharSequence text, BufferType type,
6111                          boolean notifyBefore, int oldlen) {
6112         mTextSetFromXmlOrResourceId = false;
6113         if (text == null) {
6114             text = "";
6115         }
6116 
6117         // If suggestions are not enabled, remove the suggestion spans from the text
6118         if (!isSuggestionsEnabled()) {
6119             text = removeSuggestionSpans(text);
6120         }
6121 
6122         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
6123 
6124         if (text instanceof Spanned
6125                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
6126             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
6127                 setHorizontalFadingEdgeEnabled(true);
6128                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
6129             } else {
6130                 setHorizontalFadingEdgeEnabled(false);
6131                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
6132             }
6133             setEllipsize(TextUtils.TruncateAt.MARQUEE);
6134         }
6135 
6136         int n = mFilters.length;
6137         for (int i = 0; i < n; i++) {
6138             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
6139             if (out != null) {
6140                 text = out;
6141             }
6142         }
6143 
6144         if (notifyBefore) {
6145             if (mText != null) {
6146                 oldlen = mText.length();
6147                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
6148             } else {
6149                 sendBeforeTextChanged("", 0, 0, text.length());
6150             }
6151         }
6152 
6153         boolean needEditableForNotification = false;
6154 
6155         if (mListeners != null && mListeners.size() != 0) {
6156             needEditableForNotification = true;
6157         }
6158 
6159         PrecomputedText precomputed =
6160                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
6161         if (type == BufferType.EDITABLE || getKeyListener() != null
6162                 || needEditableForNotification) {
6163             createEditorIfNeeded();
6164             mEditor.forgetUndoRedo();
6165             Editable t = mEditableFactory.newEditable(text);
6166             text = t;
6167             setFilters(t, mFilters);
6168             InputMethodManager imm = getInputMethodManager();
6169             if (imm != null) imm.restartInput(this);
6170         } else if (precomputed != null) {
6171             if (mTextDir == null) {
6172                 mTextDir = getTextDirectionHeuristic();
6173             }
6174             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
6175                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
6176                             mHyphenationFrequency);
6177             switch (checkResult) {
6178                 case PrecomputedText.Params.UNUSABLE:
6179                     throw new IllegalArgumentException(
6180                         "PrecomputedText's Parameters don't match the parameters of this TextView."
6181                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
6182                         + "to override the settings of this TextView: "
6183                         + "PrecomputedText: " + precomputed.getParams()
6184                         + "TextView: " + getTextMetricsParams());
6185                 case PrecomputedText.Params.NEED_RECOMPUTE:
6186                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
6187                     break;
6188                 case PrecomputedText.Params.USABLE:
6189                     // pass through
6190             }
6191         } else if (type == BufferType.SPANNABLE || mMovement != null) {
6192             text = mSpannableFactory.newSpannable(text);
6193         } else if (!(text instanceof CharWrapper)) {
6194             text = TextUtils.stringOrSpannedString(text);
6195         }
6196 
6197         if (mAutoLinkMask != 0) {
6198             Spannable s2;
6199 
6200             if (type == BufferType.EDITABLE || text instanceof Spannable) {
6201                 s2 = (Spannable) text;
6202             } else {
6203                 s2 = mSpannableFactory.newSpannable(text);
6204             }
6205 
6206             if (Linkify.addLinks(s2, mAutoLinkMask)) {
6207                 text = s2;
6208                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
6209 
6210                 /*
6211                  * We must go ahead and set the text before changing the
6212                  * movement method, because setMovementMethod() may call
6213                  * setText() again to try to upgrade the buffer type.
6214                  */
6215                 setTextInternal(text);
6216 
6217                 // Do not change the movement method for text that support text selection as it
6218                 // would prevent an arbitrary cursor displacement.
6219                 if (mLinksClickable && !textCanBeSelected()) {
6220                     setMovementMethod(LinkMovementMethod.getInstance());
6221                 }
6222             }
6223         }
6224 
6225         mBufferType = type;
6226         setTextInternal(text);
6227 
6228         if (mTransformation == null) {
6229             mTransformed = text;
6230         } else {
6231             mTransformed = mTransformation.getTransformation(text, this);
6232         }
6233         if (mTransformed == null) {
6234             // Should not happen if the transformation method follows the non-null postcondition.
6235             mTransformed = "";
6236         }
6237 
6238         final int textLength = text.length();
6239 
6240         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
6241             Spannable sp = (Spannable) text;
6242 
6243             // Remove any ChangeWatchers that might have come from other TextViews.
6244             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
6245             final int count = watchers.length;
6246             for (int i = 0; i < count; i++) {
6247                 sp.removeSpan(watchers[i]);
6248             }
6249 
6250             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
6251 
6252             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
6253                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
6254 
6255             if (mEditor != null) mEditor.addSpanWatchers(sp);
6256 
6257             if (mTransformation != null) {
6258                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
6259             }
6260 
6261             if (mMovement != null) {
6262                 mMovement.initialize(this, (Spannable) text);
6263 
6264                 /*
6265                  * Initializing the movement method will have set the
6266                  * selection, so reset mSelectionMoved to keep that from
6267                  * interfering with the normal on-focus selection-setting.
6268                  */
6269                 if (mEditor != null) mEditor.mSelectionMoved = false;
6270             }
6271         }
6272 
6273         if (mLayout != null) {
6274             checkForRelayout();
6275         }
6276 
6277         sendOnTextChanged(text, 0, oldlen, textLength);
6278         onTextChanged(text, 0, oldlen, textLength);
6279 
6280         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
6281 
6282         if (needEditableForNotification) {
6283             sendAfterTextChanged((Editable) text);
6284         } else {
6285             notifyListeningManagersAfterTextChanged();
6286         }
6287 
6288         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
6289         if (mEditor != null) mEditor.prepareCursorControllers();
6290     }
6291 
6292     /**
6293      * Sets the TextView to display the specified slice of the specified
6294      * char array. You must promise that you will not change the contents
6295      * of the array except for right before another call to setText(),
6296      * since the TextView has no way to know that the text
6297      * has changed and that it needs to invalidate and re-layout.
6298      *
6299      * @param text char array to be displayed
6300      * @param start start index in the char array
6301      * @param len length of char count after {@code start}
6302      */
setText(char[] text, int start, int len)6303     public final void setText(char[] text, int start, int len) {
6304         int oldlen = 0;
6305 
6306         if (start < 0 || len < 0 || start + len > text.length) {
6307             throw new IndexOutOfBoundsException(start + ", " + len);
6308         }
6309 
6310         /*
6311          * We must do the before-notification here ourselves because if
6312          * the old text is a CharWrapper we destroy it before calling
6313          * into the normal path.
6314          */
6315         if (mText != null) {
6316             oldlen = mText.length();
6317             sendBeforeTextChanged(mText, 0, oldlen, len);
6318         } else {
6319             sendBeforeTextChanged("", 0, 0, len);
6320         }
6321 
6322         if (mCharWrapper == null) {
6323             mCharWrapper = new CharWrapper(text, start, len);
6324         } else {
6325             mCharWrapper.set(text, start, len);
6326         }
6327 
6328         setText(mCharWrapper, mBufferType, false, oldlen);
6329     }
6330 
6331     /**
6332      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
6333      * the cursor position. Same as
6334      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
6335      * position (if any) is retained in the new text.
6336      * <p/>
6337      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6338      * intermediate {@link Spannable Spannables}. Likewise it will use
6339      * {@link android.text.Editable.Factory} to create final or intermediate
6340      * {@link Editable Editables}.
6341      *
6342      * @param text text to be displayed
6343      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6344      *              stored as a static text, styleable/spannable text, or editable text
6345      *
6346      * @see #setText(CharSequence, android.widget.TextView.BufferType)
6347      */
setTextKeepState(CharSequence text, BufferType type)6348     public final void setTextKeepState(CharSequence text, BufferType type) {
6349         int start = getSelectionStart();
6350         int end = getSelectionEnd();
6351         int len = text.length();
6352 
6353         setText(text, type);
6354 
6355         if (start >= 0 || end >= 0) {
6356             if (mSpannable != null) {
6357                 Selection.setSelection(mSpannable,
6358                                        Math.max(0, Math.min(start, len)),
6359                                        Math.max(0, Math.min(end, len)));
6360             }
6361         }
6362     }
6363 
6364     /**
6365      * Sets the text to be displayed using a string resource identifier.
6366      *
6367      * @param resid the resource identifier of the string resource to be displayed
6368      *
6369      * @see #setText(CharSequence)
6370      *
6371      * @attr ref android.R.styleable#TextView_text
6372      */
6373     @android.view.RemotableViewMethod
setText(@tringRes int resid)6374     public final void setText(@StringRes int resid) {
6375         setText(getContext().getResources().getText(resid));
6376         mTextSetFromXmlOrResourceId = true;
6377         mTextId = resid;
6378     }
6379 
6380     /**
6381      * Sets the text to be displayed using a string resource identifier and the
6382      * {@link android.widget.TextView.BufferType}.
6383      * <p/>
6384      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6385      * intermediate {@link Spannable Spannables}. Likewise it will use
6386      * {@link android.text.Editable.Factory} to create final or intermediate
6387      * {@link Editable Editables}.
6388      *
6389      * @param resid the resource identifier of the string resource to be displayed
6390      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6391      *              stored as a static text, styleable/spannable text, or editable text
6392      *
6393      * @see #setText(int)
6394      * @see #setText(CharSequence)
6395      * @see android.widget.TextView.BufferType
6396      * @see #setSpannableFactory(Spannable.Factory)
6397      * @see #setEditableFactory(Editable.Factory)
6398      *
6399      * @attr ref android.R.styleable#TextView_text
6400      * @attr ref android.R.styleable#TextView_bufferType
6401      */
setText(@tringRes int resid, BufferType type)6402     public final void setText(@StringRes int resid, BufferType type) {
6403         setText(getContext().getResources().getText(resid), type);
6404         mTextSetFromXmlOrResourceId = true;
6405         mTextId = resid;
6406     }
6407 
6408     /**
6409      * Sets the text to be displayed when the text of the TextView is empty.
6410      * Null means to use the normal empty text. The hint does not currently
6411      * participate in determining the size of the view.
6412      *
6413      * @attr ref android.R.styleable#TextView_hint
6414      */
6415     @android.view.RemotableViewMethod
setHint(CharSequence hint)6416     public final void setHint(CharSequence hint) {
6417         setHintInternal(hint);
6418 
6419         if (mEditor != null && isInputMethodTarget()) {
6420             mEditor.reportExtractedText();
6421         }
6422     }
6423 
setHintInternal(CharSequence hint)6424     private void setHintInternal(CharSequence hint) {
6425         mHint = TextUtils.stringOrSpannedString(hint);
6426 
6427         if (mLayout != null) {
6428             checkForRelayout();
6429         }
6430 
6431         if (mText.length() == 0) {
6432             invalidate();
6433         }
6434 
6435         // Invalidate display list if hint is currently used
6436         if (mEditor != null && mText.length() == 0 && mHint != null) {
6437             mEditor.invalidateTextDisplayList();
6438         }
6439     }
6440 
6441     /**
6442      * Sets the text to be displayed when the text of the TextView is empty,
6443      * from a resource.
6444      *
6445      * @attr ref android.R.styleable#TextView_hint
6446      */
6447     @android.view.RemotableViewMethod
setHint(@tringRes int resid)6448     public final void setHint(@StringRes int resid) {
6449         setHint(getContext().getResources().getText(resid));
6450     }
6451 
6452     /**
6453      * Returns the hint that is displayed when the text of the TextView
6454      * is empty.
6455      *
6456      * @attr ref android.R.styleable#TextView_hint
6457      */
6458     @InspectableProperty
6459     @ViewDebug.CapturedViewProperty
getHint()6460     public CharSequence getHint() {
6461         return mHint;
6462     }
6463 
6464     /**
6465      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
6466      * line characters instead of letting it wrap onto multiple lines.
6467      *
6468      * @attr ref android.R.styleable#TextView_singleLine
6469      */
6470     @InspectableProperty
isSingleLine()6471     public boolean isSingleLine() {
6472         return mSingleLine;
6473     }
6474 
isMultilineInputType(int type)6475     private static boolean isMultilineInputType(int type) {
6476         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
6477                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
6478     }
6479 
6480     /**
6481      * Removes the suggestion spans.
6482      */
removeSuggestionSpans(CharSequence text)6483     CharSequence removeSuggestionSpans(CharSequence text) {
6484         if (text instanceof Spanned) {
6485             Spannable spannable;
6486             if (text instanceof Spannable) {
6487                 spannable = (Spannable) text;
6488             } else {
6489                 spannable = mSpannableFactory.newSpannable(text);
6490             }
6491 
6492             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
6493             if (spans.length == 0) {
6494                 return text;
6495             } else {
6496                 text = spannable;
6497             }
6498 
6499             for (int i = 0; i < spans.length; i++) {
6500                 spannable.removeSpan(spans[i]);
6501             }
6502         }
6503         return text;
6504     }
6505 
6506     /**
6507      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
6508      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
6509      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
6510      * then a soft keyboard will not be displayed for this text view.
6511      *
6512      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
6513      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
6514      * type.
6515      *
6516      * @see #getInputType()
6517      * @see #setRawInputType(int)
6518      * @see android.text.InputType
6519      * @attr ref android.R.styleable#TextView_inputType
6520      */
setInputType(int type)6521     public void setInputType(int type) {
6522         final boolean wasPassword = isPasswordInputType(getInputType());
6523         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
6524         setInputType(type, false);
6525         final boolean isPassword = isPasswordInputType(type);
6526         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
6527         boolean forceUpdate = false;
6528         if (isPassword) {
6529             setTransformationMethod(PasswordTransformationMethod.getInstance());
6530             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6531                     Typeface.NORMAL, -1 /* weight, not specifeid */);
6532         } else if (isVisiblePassword) {
6533             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6534                 forceUpdate = true;
6535             }
6536             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6537                     Typeface.NORMAL, -1 /* weight, not specified */);
6538         } else if (wasPassword || wasVisiblePassword) {
6539             // not in password mode, clean up typeface and transformation
6540             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
6541                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
6542                     -1 /* weight, not specified */);
6543             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6544                 forceUpdate = true;
6545             }
6546         }
6547 
6548         boolean singleLine = !isMultilineInputType(type);
6549 
6550         // We need to update the single line mode if it has changed or we
6551         // were previously in password mode.
6552         if (mSingleLine != singleLine || forceUpdate) {
6553             // Change single line mode, but only change the transformation if
6554             // we are not in password mode.
6555             applySingleLine(singleLine, !isPassword, true);
6556         }
6557 
6558         if (!isSuggestionsEnabled()) {
6559             setTextInternal(removeSuggestionSpans(mText));
6560         }
6561 
6562         InputMethodManager imm = getInputMethodManager();
6563         if (imm != null) imm.restartInput(this);
6564     }
6565 
6566     /**
6567      * It would be better to rely on the input type for everything. A password inputType should have
6568      * a password transformation. We should hence use isPasswordInputType instead of this method.
6569      *
6570      * We should:
6571      * - Call setInputType in setKeyListener instead of changing the input type directly (which
6572      * would install the correct transformation).
6573      * - Refuse the installation of a non-password transformation in setTransformation if the input
6574      * type is password.
6575      *
6576      * However, this is like this for legacy reasons and we cannot break existing apps. This method
6577      * is useful since it matches what the user can see (obfuscated text or not).
6578      *
6579      * @return true if the current transformation method is of the password type.
6580      */
hasPasswordTransformationMethod()6581     boolean hasPasswordTransformationMethod() {
6582         return mTransformation instanceof PasswordTransformationMethod;
6583     }
6584 
6585     /**
6586      * Returns true if the current inputType is any type of password.
6587      *
6588      * @hide
6589      */
isAnyPasswordInputType()6590     public boolean isAnyPasswordInputType() {
6591         final int inputType = getInputType();
6592         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
6593     }
6594 
isPasswordInputType(int inputType)6595     static boolean isPasswordInputType(int inputType) {
6596         final int variation =
6597                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6598         return variation
6599                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
6600                 || variation
6601                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
6602                 || variation
6603                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
6604     }
6605 
isVisiblePasswordInputType(int inputType)6606     private static boolean isVisiblePasswordInputType(int inputType) {
6607         final int variation =
6608                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6609         return variation
6610                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
6611     }
6612 
6613     /**
6614      * Directly change the content type integer of the text view, without
6615      * modifying any other state.
6616      * @see #setInputType(int)
6617      * @see android.text.InputType
6618      * @attr ref android.R.styleable#TextView_inputType
6619      */
setRawInputType(int type)6620     public void setRawInputType(int type) {
6621         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
6622         createEditorIfNeeded();
6623         mEditor.mInputType = type;
6624     }
6625 
6626     /**
6627      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
6628      *         a {@code Locale} object that can be used to customize key various listeners.
6629      * @see DateKeyListener#getInstance(Locale)
6630      * @see DateTimeKeyListener#getInstance(Locale)
6631      * @see DigitsKeyListener#getInstance(Locale)
6632      * @see TimeKeyListener#getInstance(Locale)
6633      */
6634     @Nullable
getCustomLocaleForKeyListenerOrNull()6635     private Locale getCustomLocaleForKeyListenerOrNull() {
6636         if (!mUseInternationalizedInput) {
6637             // If the application does not target O, stick to the previous behavior.
6638             return null;
6639         }
6640         final LocaleList locales = getImeHintLocales();
6641         if (locales == null) {
6642             // If the application does not explicitly specify IME hint locale, also stick to the
6643             // previous behavior.
6644             return null;
6645         }
6646         return locales.get(0);
6647     }
6648 
6649     @UnsupportedAppUsage
setInputType(int type, boolean direct)6650     private void setInputType(int type, boolean direct) {
6651         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
6652         KeyListener input;
6653         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
6654             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
6655             TextKeyListener.Capitalize cap;
6656             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
6657                 cap = TextKeyListener.Capitalize.CHARACTERS;
6658             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
6659                 cap = TextKeyListener.Capitalize.WORDS;
6660             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
6661                 cap = TextKeyListener.Capitalize.SENTENCES;
6662             } else {
6663                 cap = TextKeyListener.Capitalize.NONE;
6664             }
6665             input = TextKeyListener.getInstance(autotext, cap);
6666         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
6667             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6668             input = DigitsKeyListener.getInstance(
6669                     locale,
6670                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
6671                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
6672             if (locale != null) {
6673                 // Override type, if necessary for i18n.
6674                 int newType = input.getInputType();
6675                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
6676                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
6677                     // The class is different from the original class. So we need to override
6678                     // 'type'. But we want to keep the password flag if it's there.
6679                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
6680                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
6681                     }
6682                     type = newType;
6683                 }
6684             }
6685         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
6686             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6687             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
6688                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
6689                     input = DateKeyListener.getInstance(locale);
6690                     break;
6691                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
6692                     input = TimeKeyListener.getInstance(locale);
6693                     break;
6694                 default:
6695                     input = DateTimeKeyListener.getInstance(locale);
6696                     break;
6697             }
6698             if (mUseInternationalizedInput) {
6699                 type = input.getInputType(); // Override type, if necessary for i18n.
6700             }
6701         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
6702             input = DialerKeyListener.getInstance();
6703         } else {
6704             input = TextKeyListener.getInstance();
6705         }
6706         setRawInputType(type);
6707         mListenerChanged = false;
6708         if (direct) {
6709             createEditorIfNeeded();
6710             mEditor.mKeyListener = input;
6711         } else {
6712             setKeyListenerOnly(input);
6713         }
6714     }
6715 
6716     /**
6717      * Get the type of the editable content.
6718      *
6719      * @see #setInputType(int)
6720      * @see android.text.InputType
6721      */
6722     @InspectableProperty(flagMapping = {
6723             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
6724             @FlagEntry(
6725                     name = "text",
6726                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6727                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
6728             @FlagEntry(
6729                     name = "textUri",
6730                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6731                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
6732             @FlagEntry(
6733                     name = "textEmailAddress",
6734                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6735                     target = InputType.TYPE_CLASS_TEXT
6736                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
6737             @FlagEntry(
6738                     name = "textEmailSubject",
6739                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6740                     target = InputType.TYPE_CLASS_TEXT
6741                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
6742             @FlagEntry(
6743                     name = "textShortMessage",
6744                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6745                     target = InputType.TYPE_CLASS_TEXT
6746                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
6747             @FlagEntry(
6748                     name = "textLongMessage",
6749                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6750                     target = InputType.TYPE_CLASS_TEXT
6751                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
6752             @FlagEntry(
6753                     name = "textPersonName",
6754                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6755                     target = InputType.TYPE_CLASS_TEXT
6756                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
6757             @FlagEntry(
6758                     name = "textPostalAddress",
6759                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6760                     target = InputType.TYPE_CLASS_TEXT
6761                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
6762             @FlagEntry(
6763                     name = "textPassword",
6764                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6765                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
6766             @FlagEntry(
6767                     name = "textVisiblePassword",
6768                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6769                     target = InputType.TYPE_CLASS_TEXT
6770                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
6771             @FlagEntry(
6772                     name = "textWebEditText",
6773                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6774                     target = InputType.TYPE_CLASS_TEXT
6775                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
6776             @FlagEntry(
6777                     name = "textFilter",
6778                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6779                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
6780             @FlagEntry(
6781                     name = "textPhonetic",
6782                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6783                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
6784             @FlagEntry(
6785                     name = "textWebEmailAddress",
6786                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6787                     target = InputType.TYPE_CLASS_TEXT
6788                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
6789             @FlagEntry(
6790                     name = "textWebPassword",
6791                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6792                     target = InputType.TYPE_CLASS_TEXT
6793                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
6794             @FlagEntry(
6795                     name = "number",
6796                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6797                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
6798             @FlagEntry(
6799                     name = "numberPassword",
6800                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6801                     target = InputType.TYPE_CLASS_NUMBER
6802                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
6803             @FlagEntry(
6804                     name = "phone",
6805                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6806                     target = InputType.TYPE_CLASS_PHONE),
6807             @FlagEntry(
6808                     name = "datetime",
6809                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6810                     target = InputType.TYPE_CLASS_DATETIME
6811                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
6812             @FlagEntry(
6813                     name = "date",
6814                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6815                     target = InputType.TYPE_CLASS_DATETIME
6816                             | InputType.TYPE_DATETIME_VARIATION_DATE),
6817             @FlagEntry(
6818                     name = "time",
6819                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6820                     target = InputType.TYPE_CLASS_DATETIME
6821                             | InputType.TYPE_DATETIME_VARIATION_TIME),
6822             @FlagEntry(
6823                     name = "textCapCharacters",
6824                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6825                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
6826             @FlagEntry(
6827                     name = "textCapWords",
6828                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6829                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
6830             @FlagEntry(
6831                     name = "textCapSentences",
6832                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6833                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
6834             @FlagEntry(
6835                     name = "textAutoCorrect",
6836                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6837                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
6838             @FlagEntry(
6839                     name = "textAutoComplete",
6840                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6841                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
6842             @FlagEntry(
6843                     name = "textMultiLine",
6844                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6845                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
6846             @FlagEntry(
6847                     name = "textImeMultiLine",
6848                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6849                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
6850             @FlagEntry(
6851                     name = "textNoSuggestions",
6852                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6853                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
6854             @FlagEntry(
6855                     name = "numberSigned",
6856                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6857                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
6858             @FlagEntry(
6859                     name = "numberDecimal",
6860                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6861                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
6862     })
getInputType()6863     public int getInputType() {
6864         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
6865     }
6866 
6867     /**
6868      * Change the editor type integer associated with the text view, which
6869      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
6870      * when it has focus.
6871      * @see #getImeOptions
6872      * @see android.view.inputmethod.EditorInfo
6873      * @attr ref android.R.styleable#TextView_imeOptions
6874      */
setImeOptions(int imeOptions)6875     public void setImeOptions(int imeOptions) {
6876         createEditorIfNeeded();
6877         mEditor.createInputContentTypeIfNeeded();
6878         mEditor.mInputContentType.imeOptions = imeOptions;
6879     }
6880 
6881     /**
6882      * Get the type of the Input Method Editor (IME).
6883      * @return the type of the IME
6884      * @see #setImeOptions(int)
6885      * @see EditorInfo
6886      */
6887     @InspectableProperty(flagMapping = {
6888             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
6889             @FlagEntry(
6890                     name = "actionUnspecified",
6891                     mask = EditorInfo.IME_MASK_ACTION,
6892                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
6893             @FlagEntry(
6894                     name = "actionNone",
6895                     mask = EditorInfo.IME_MASK_ACTION,
6896                     target = EditorInfo.IME_ACTION_NONE),
6897             @FlagEntry(
6898                     name = "actionGo",
6899                     mask = EditorInfo.IME_MASK_ACTION,
6900                     target = EditorInfo.IME_ACTION_GO),
6901             @FlagEntry(
6902                     name = "actionSearch",
6903                     mask = EditorInfo.IME_MASK_ACTION,
6904                     target = EditorInfo.IME_ACTION_SEARCH),
6905             @FlagEntry(
6906                     name = "actionSend",
6907                     mask = EditorInfo.IME_MASK_ACTION,
6908                     target = EditorInfo.IME_ACTION_SEND),
6909             @FlagEntry(
6910                     name = "actionNext",
6911                     mask = EditorInfo.IME_MASK_ACTION,
6912                     target = EditorInfo.IME_ACTION_NEXT),
6913             @FlagEntry(
6914                     name = "actionDone",
6915                     mask = EditorInfo.IME_MASK_ACTION,
6916                     target = EditorInfo.IME_ACTION_DONE),
6917             @FlagEntry(
6918                     name = "actionPrevious",
6919                     mask = EditorInfo.IME_MASK_ACTION,
6920                     target = EditorInfo.IME_ACTION_PREVIOUS),
6921             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
6922             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
6923             @FlagEntry(
6924                     name = "flagNavigatePrevious",
6925                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
6926             @FlagEntry(
6927                     name = "flagNoAccessoryAction",
6928                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
6929             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
6930             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
6931             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
6932             @FlagEntry(
6933                     name = "flagNoPersonalizedLearning",
6934                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
6935     })
getImeOptions()6936     public int getImeOptions() {
6937         return mEditor != null && mEditor.mInputContentType != null
6938                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
6939     }
6940 
6941     /**
6942      * Change the custom IME action associated with the text view, which
6943      * will be reported to an IME with {@link EditorInfo#actionLabel}
6944      * and {@link EditorInfo#actionId} when it has focus.
6945      * @see #getImeActionLabel
6946      * @see #getImeActionId
6947      * @see android.view.inputmethod.EditorInfo
6948      * @attr ref android.R.styleable#TextView_imeActionLabel
6949      * @attr ref android.R.styleable#TextView_imeActionId
6950      */
setImeActionLabel(CharSequence label, int actionId)6951     public void setImeActionLabel(CharSequence label, int actionId) {
6952         createEditorIfNeeded();
6953         mEditor.createInputContentTypeIfNeeded();
6954         mEditor.mInputContentType.imeActionLabel = label;
6955         mEditor.mInputContentType.imeActionId = actionId;
6956     }
6957 
6958     /**
6959      * Get the IME action label previous set with {@link #setImeActionLabel}.
6960      *
6961      * @see #setImeActionLabel
6962      * @see android.view.inputmethod.EditorInfo
6963      */
6964     @InspectableProperty
getImeActionLabel()6965     public CharSequence getImeActionLabel() {
6966         return mEditor != null && mEditor.mInputContentType != null
6967                 ? mEditor.mInputContentType.imeActionLabel : null;
6968     }
6969 
6970     /**
6971      * Get the IME action ID previous set with {@link #setImeActionLabel}.
6972      *
6973      * @see #setImeActionLabel
6974      * @see android.view.inputmethod.EditorInfo
6975      */
6976     @InspectableProperty
getImeActionId()6977     public int getImeActionId() {
6978         return mEditor != null && mEditor.mInputContentType != null
6979                 ? mEditor.mInputContentType.imeActionId : 0;
6980     }
6981 
6982     /**
6983      * Set a special listener to be called when an action is performed
6984      * on the text view.  This will be called when the enter key is pressed,
6985      * or when an action supplied to the IME is selected by the user.  Setting
6986      * this means that the normal hard key event will not insert a newline
6987      * into the text view, even if it is multi-line; holding down the ALT
6988      * modifier will, however, allow the user to insert a newline character.
6989      */
setOnEditorActionListener(OnEditorActionListener l)6990     public void setOnEditorActionListener(OnEditorActionListener l) {
6991         createEditorIfNeeded();
6992         mEditor.createInputContentTypeIfNeeded();
6993         mEditor.mInputContentType.onEditorActionListener = l;
6994     }
6995 
6996     /**
6997      * Called when an attached input method calls
6998      * {@link InputConnection#performEditorAction(int)
6999      * InputConnection.performEditorAction()}
7000      * for this text view.  The default implementation will call your action
7001      * listener supplied to {@link #setOnEditorActionListener}, or perform
7002      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
7003      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
7004      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
7005      * EditorInfo.IME_ACTION_DONE}.
7006      *
7007      * <p>For backwards compatibility, if no IME options have been set and the
7008      * text view would not normally advance focus on enter, then
7009      * the NEXT and DONE actions received here will be turned into an enter
7010      * key down/up pair to go through the normal key handling.
7011      *
7012      * @param actionCode The code of the action being performed.
7013      *
7014      * @see #setOnEditorActionListener
7015      */
onEditorAction(int actionCode)7016     public void onEditorAction(int actionCode) {
7017         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
7018         if (ict != null) {
7019             if (ict.onEditorActionListener != null) {
7020                 if (ict.onEditorActionListener.onEditorAction(this,
7021                         actionCode, null)) {
7022                     return;
7023                 }
7024             }
7025 
7026             // This is the handling for some default action.
7027             // Note that for backwards compatibility we don't do this
7028             // default handling if explicit ime options have not been given,
7029             // instead turning this into the normal enter key codes that an
7030             // app may be expecting.
7031             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
7032                 View v = focusSearch(FOCUS_FORWARD);
7033                 if (v != null) {
7034                     if (!v.requestFocus(FOCUS_FORWARD)) {
7035                         throw new IllegalStateException("focus search returned a view "
7036                                 + "that wasn't able to take focus!");
7037                     }
7038                 }
7039                 return;
7040 
7041             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
7042                 View v = focusSearch(FOCUS_BACKWARD);
7043                 if (v != null) {
7044                     if (!v.requestFocus(FOCUS_BACKWARD)) {
7045                         throw new IllegalStateException("focus search returned a view "
7046                                 + "that wasn't able to take focus!");
7047                     }
7048                 }
7049                 return;
7050 
7051             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
7052                 InputMethodManager imm = getInputMethodManager();
7053                 if (imm != null && imm.isActive(this)) {
7054                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7055                 }
7056                 return;
7057             }
7058         }
7059 
7060         ViewRootImpl viewRootImpl = getViewRootImpl();
7061         if (viewRootImpl != null) {
7062             long eventTime = SystemClock.uptimeMillis();
7063             viewRootImpl.dispatchKeyFromIme(
7064                     new KeyEvent(eventTime, eventTime,
7065                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
7066                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7067                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7068                     | KeyEvent.FLAG_EDITOR_ACTION));
7069             viewRootImpl.dispatchKeyFromIme(
7070                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
7071                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
7072                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7073                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7074                     | KeyEvent.FLAG_EDITOR_ACTION));
7075         }
7076     }
7077 
7078     /**
7079      * Set the private content type of the text, which is the
7080      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
7081      * field that will be filled in when creating an input connection.
7082      *
7083      * @see #getPrivateImeOptions()
7084      * @see EditorInfo#privateImeOptions
7085      * @attr ref android.R.styleable#TextView_privateImeOptions
7086      */
setPrivateImeOptions(String type)7087     public void setPrivateImeOptions(String type) {
7088         createEditorIfNeeded();
7089         mEditor.createInputContentTypeIfNeeded();
7090         mEditor.mInputContentType.privateImeOptions = type;
7091     }
7092 
7093     /**
7094      * Get the private type of the content.
7095      *
7096      * @see #setPrivateImeOptions(String)
7097      * @see EditorInfo#privateImeOptions
7098      */
7099     @InspectableProperty
getPrivateImeOptions()7100     public String getPrivateImeOptions() {
7101         return mEditor != null && mEditor.mInputContentType != null
7102                 ? mEditor.mInputContentType.privateImeOptions : null;
7103     }
7104 
7105     /**
7106      * Set the extra input data of the text, which is the
7107      * {@link EditorInfo#extras TextBoxAttribute.extras}
7108      * Bundle that will be filled in when creating an input connection.  The
7109      * given integer is the resource identifier of an XML resource holding an
7110      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
7111      *
7112      * @see #getInputExtras(boolean)
7113      * @see EditorInfo#extras
7114      * @attr ref android.R.styleable#TextView_editorExtras
7115      */
setInputExtras(@mlRes int xmlResId)7116     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
7117         createEditorIfNeeded();
7118         XmlResourceParser parser = getResources().getXml(xmlResId);
7119         mEditor.createInputContentTypeIfNeeded();
7120         mEditor.mInputContentType.extras = new Bundle();
7121         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
7122     }
7123 
7124     /**
7125      * Retrieve the input extras currently associated with the text view, which
7126      * can be viewed as well as modified.
7127      *
7128      * @param create If true, the extras will be created if they don't already
7129      * exist.  Otherwise, null will be returned if none have been created.
7130      * @see #setInputExtras(int)
7131      * @see EditorInfo#extras
7132      * @attr ref android.R.styleable#TextView_editorExtras
7133      */
getInputExtras(boolean create)7134     public Bundle getInputExtras(boolean create) {
7135         if (mEditor == null && !create) return null;
7136         createEditorIfNeeded();
7137         if (mEditor.mInputContentType == null) {
7138             if (!create) return null;
7139             mEditor.createInputContentTypeIfNeeded();
7140         }
7141         if (mEditor.mInputContentType.extras == null) {
7142             if (!create) return null;
7143             mEditor.mInputContentType.extras = new Bundle();
7144         }
7145         return mEditor.mInputContentType.extras;
7146     }
7147 
7148     /**
7149      * Change "hint" locales associated with the text view, which will be reported to an IME with
7150      * {@link EditorInfo#hintLocales} when it has focus.
7151      *
7152      * Starting with Android O, this also causes internationalized listeners to be created (or
7153      * change locale) based on the first locale in the input locale list.
7154      *
7155      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
7156      * call {@link InputMethodManager#restartInput(View)}.</p>
7157      * @param hintLocales List of the languages that the user is supposed to switch to no matter
7158      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
7159      * @see #getImeHintLocales()
7160      * @see android.view.inputmethod.EditorInfo#hintLocales
7161      */
setImeHintLocales(@ullable LocaleList hintLocales)7162     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
7163         createEditorIfNeeded();
7164         mEditor.createInputContentTypeIfNeeded();
7165         mEditor.mInputContentType.imeHintLocales = hintLocales;
7166         if (mUseInternationalizedInput) {
7167             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
7168         }
7169     }
7170 
7171     /**
7172      * @return The current languages list "hint". {@code null} when no "hint" is available.
7173      * @see #setImeHintLocales(LocaleList)
7174      * @see android.view.inputmethod.EditorInfo#hintLocales
7175      */
7176     @Nullable
getImeHintLocales()7177     public LocaleList getImeHintLocales() {
7178         if (mEditor == null) {
7179             return null;
7180         }
7181         if (mEditor.mInputContentType == null) {
7182             return null;
7183         }
7184         return mEditor.mInputContentType.imeHintLocales;
7185     }
7186 
7187     /**
7188      * Returns the error message that was set to be displayed with
7189      * {@link #setError}, or <code>null</code> if no error was set
7190      * or if it the error was cleared by the widget after user input.
7191      */
getError()7192     public CharSequence getError() {
7193         return mEditor == null ? null : mEditor.mError;
7194     }
7195 
7196     /**
7197      * Sets the right-hand compound drawable of the TextView to the "error"
7198      * icon and sets an error message that will be displayed in a popup when
7199      * the TextView has focus.  The icon and error message will be reset to
7200      * null when any key events cause changes to the TextView's text.  If the
7201      * <code>error</code> is <code>null</code>, the error message and icon
7202      * will be cleared.
7203      */
7204     @android.view.RemotableViewMethod
setError(CharSequence error)7205     public void setError(CharSequence error) {
7206         if (error == null) {
7207             setError(null, null);
7208         } else {
7209             Drawable dr = getContext().getDrawable(
7210                     com.android.internal.R.drawable.indicator_input_error);
7211 
7212             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
7213             setError(error, dr);
7214         }
7215     }
7216 
7217     /**
7218      * Sets the right-hand compound drawable of the TextView to the specified
7219      * icon and sets an error message that will be displayed in a popup when
7220      * the TextView has focus.  The icon and error message will be reset to
7221      * null when any key events cause changes to the TextView's text.  The
7222      * drawable must already have had {@link Drawable#setBounds} set on it.
7223      * If the <code>error</code> is <code>null</code>, the error message will
7224      * be cleared (and you should provide a <code>null</code> icon as well).
7225      */
setError(CharSequence error, Drawable icon)7226     public void setError(CharSequence error, Drawable icon) {
7227         createEditorIfNeeded();
7228         mEditor.setError(error, icon);
7229         notifyViewAccessibilityStateChangedIfNeeded(
7230                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7231     }
7232 
7233     @Override
setFrame(int l, int t, int r, int b)7234     protected boolean setFrame(int l, int t, int r, int b) {
7235         boolean result = super.setFrame(l, t, r, b);
7236 
7237         if (mEditor != null) mEditor.setFrame();
7238 
7239         restartMarqueeIfNeeded();
7240 
7241         return result;
7242     }
7243 
restartMarqueeIfNeeded()7244     private void restartMarqueeIfNeeded() {
7245         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7246             mRestartMarquee = false;
7247             startMarquee();
7248         }
7249     }
7250 
7251     /**
7252      * Sets the list of input filters that will be used if the buffer is
7253      * Editable. Has no effect otherwise.
7254      *
7255      * @attr ref android.R.styleable#TextView_maxLength
7256      */
setFilters(InputFilter[] filters)7257     public void setFilters(InputFilter[] filters) {
7258         if (filters == null) {
7259             throw new IllegalArgumentException();
7260         }
7261 
7262         mFilters = filters;
7263 
7264         if (mText instanceof Editable) {
7265             setFilters((Editable) mText, filters);
7266         }
7267     }
7268 
7269     /**
7270      * Sets the list of input filters on the specified Editable,
7271      * and includes mInput in the list if it is an InputFilter.
7272      */
setFilters(Editable e, InputFilter[] filters)7273     private void setFilters(Editable e, InputFilter[] filters) {
7274         if (mEditor != null) {
7275             final boolean undoFilter = mEditor.mUndoInputFilter != null;
7276             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
7277             int num = 0;
7278             if (undoFilter) num++;
7279             if (keyFilter) num++;
7280             if (num > 0) {
7281                 InputFilter[] nf = new InputFilter[filters.length + num];
7282 
7283                 System.arraycopy(filters, 0, nf, 0, filters.length);
7284                 num = 0;
7285                 if (undoFilter) {
7286                     nf[filters.length] = mEditor.mUndoInputFilter;
7287                     num++;
7288                 }
7289                 if (keyFilter) {
7290                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
7291                 }
7292 
7293                 e.setFilters(nf);
7294                 return;
7295             }
7296         }
7297         e.setFilters(filters);
7298     }
7299 
7300     /**
7301      * Returns the current list of input filters.
7302      *
7303      * @attr ref android.R.styleable#TextView_maxLength
7304      */
getFilters()7305     public InputFilter[] getFilters() {
7306         return mFilters;
7307     }
7308 
7309     /////////////////////////////////////////////////////////////////////////
7310 
getBoxHeight(Layout l)7311     private int getBoxHeight(Layout l) {
7312         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
7313         int padding = (l == mHintLayout)
7314                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
7315                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
7316         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
7317     }
7318 
7319     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)7320     int getVerticalOffset(boolean forceNormal) {
7321         int voffset = 0;
7322         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7323 
7324         Layout l = mLayout;
7325         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7326             l = mHintLayout;
7327         }
7328 
7329         if (gravity != Gravity.TOP) {
7330             int boxht = getBoxHeight(l);
7331             int textht = l.getHeight();
7332 
7333             if (textht < boxht) {
7334                 if (gravity == Gravity.BOTTOM) {
7335                     voffset = boxht - textht;
7336                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7337                     voffset = (boxht - textht) >> 1;
7338                 }
7339             }
7340         }
7341         return voffset;
7342     }
7343 
getBottomVerticalOffset(boolean forceNormal)7344     private int getBottomVerticalOffset(boolean forceNormal) {
7345         int voffset = 0;
7346         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7347 
7348         Layout l = mLayout;
7349         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7350             l = mHintLayout;
7351         }
7352 
7353         if (gravity != Gravity.BOTTOM) {
7354             int boxht = getBoxHeight(l);
7355             int textht = l.getHeight();
7356 
7357             if (textht < boxht) {
7358                 if (gravity == Gravity.TOP) {
7359                     voffset = boxht - textht;
7360                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7361                     voffset = (boxht - textht) >> 1;
7362                 }
7363             }
7364         }
7365         return voffset;
7366     }
7367 
invalidateCursorPath()7368     void invalidateCursorPath() {
7369         if (mHighlightPathBogus) {
7370             invalidateCursor();
7371         } else {
7372             final int horizontalPadding = getCompoundPaddingLeft();
7373             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7374 
7375             if (mEditor.mDrawableForCursor == null) {
7376                 synchronized (TEMP_RECTF) {
7377                     /*
7378                      * The reason for this concern about the thickness of the
7379                      * cursor and doing the floor/ceil on the coordinates is that
7380                      * some EditTexts (notably textfields in the Browser) have
7381                      * anti-aliased text where not all the characters are
7382                      * necessarily at integer-multiple locations.  This should
7383                      * make sure the entire cursor gets invalidated instead of
7384                      * sometimes missing half a pixel.
7385                      */
7386                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
7387                     if (thick < 1.0f) {
7388                         thick = 1.0f;
7389                     }
7390 
7391                     thick /= 2.0f;
7392 
7393                     // mHighlightPath is guaranteed to be non null at that point.
7394                     mHighlightPath.computeBounds(TEMP_RECTF, false);
7395 
7396                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
7397                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
7398                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
7399                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
7400                 }
7401             } else {
7402                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7403                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
7404                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
7405             }
7406         }
7407     }
7408 
invalidateCursor()7409     void invalidateCursor() {
7410         int where = getSelectionEnd();
7411 
7412         invalidateCursor(where, where, where);
7413     }
7414 
invalidateCursor(int a, int b, int c)7415     private void invalidateCursor(int a, int b, int c) {
7416         if (a >= 0 || b >= 0 || c >= 0) {
7417             int start = Math.min(Math.min(a, b), c);
7418             int end = Math.max(Math.max(a, b), c);
7419             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
7420         }
7421     }
7422 
7423     /**
7424      * Invalidates the region of text enclosed between the start and end text offsets.
7425      */
invalidateRegion(int start, int end, boolean invalidateCursor)7426     void invalidateRegion(int start, int end, boolean invalidateCursor) {
7427         if (mLayout == null) {
7428             invalidate();
7429         } else {
7430             int lineStart = mLayout.getLineForOffset(start);
7431             int top = mLayout.getLineTop(lineStart);
7432 
7433             // This is ridiculous, but the descent from the line above
7434             // can hang down into the line we really want to redraw,
7435             // so we have to invalidate part of the line above to make
7436             // sure everything that needs to be redrawn really is.
7437             // (But not the whole line above, because that would cause
7438             // the same problem with the descenders on the line above it!)
7439             if (lineStart > 0) {
7440                 top -= mLayout.getLineDescent(lineStart - 1);
7441             }
7442 
7443             int lineEnd;
7444 
7445             if (start == end) {
7446                 lineEnd = lineStart;
7447             } else {
7448                 lineEnd = mLayout.getLineForOffset(end);
7449             }
7450 
7451             int bottom = mLayout.getLineBottom(lineEnd);
7452 
7453             // mEditor can be null in case selection is set programmatically.
7454             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
7455                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7456                 top = Math.min(top, bounds.top);
7457                 bottom = Math.max(bottom, bounds.bottom);
7458             }
7459 
7460             final int compoundPaddingLeft = getCompoundPaddingLeft();
7461             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7462 
7463             int left, right;
7464             if (lineStart == lineEnd && !invalidateCursor) {
7465                 left = (int) mLayout.getPrimaryHorizontal(start);
7466                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
7467                 left += compoundPaddingLeft;
7468                 right += compoundPaddingLeft;
7469             } else {
7470                 // Rectangle bounding box when the region spans several lines
7471                 left = compoundPaddingLeft;
7472                 right = getWidth() - getCompoundPaddingRight();
7473             }
7474 
7475             invalidate(mScrollX + left, verticalPadding + top,
7476                     mScrollX + right, verticalPadding + bottom);
7477         }
7478     }
7479 
registerForPreDraw()7480     private void registerForPreDraw() {
7481         if (!mPreDrawRegistered) {
7482             getViewTreeObserver().addOnPreDrawListener(this);
7483             mPreDrawRegistered = true;
7484         }
7485     }
7486 
unregisterForPreDraw()7487     private void unregisterForPreDraw() {
7488         getViewTreeObserver().removeOnPreDrawListener(this);
7489         mPreDrawRegistered = false;
7490         mPreDrawListenerDetached = false;
7491     }
7492 
7493     /**
7494      * {@inheritDoc}
7495      */
7496     @Override
onPreDraw()7497     public boolean onPreDraw() {
7498         if (mLayout == null) {
7499             assumeLayout();
7500         }
7501 
7502         if (mMovement != null) {
7503             /* This code also provides auto-scrolling when a cursor is moved using a
7504              * CursorController (insertion point or selection limits).
7505              * For selection, ensure start or end is visible depending on controller's state.
7506              */
7507             int curs = getSelectionEnd();
7508             // Do not create the controller if it is not already created.
7509             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
7510                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
7511                 curs = getSelectionStart();
7512             }
7513 
7514             /*
7515              * TODO: This should really only keep the end in view if
7516              * it already was before the text changed.  I'm not sure
7517              * of a good way to tell from here if it was.
7518              */
7519             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7520                 curs = mText.length();
7521             }
7522 
7523             if (curs >= 0) {
7524                 bringPointIntoView(curs);
7525             }
7526         } else {
7527             bringTextIntoView();
7528         }
7529 
7530         // This has to be checked here since:
7531         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
7532         //   a screen rotation) since layout is not yet initialized at that point.
7533         if (mEditor != null && mEditor.mCreatedWithASelection) {
7534             mEditor.refreshTextActionMode();
7535             mEditor.mCreatedWithASelection = false;
7536         }
7537 
7538         unregisterForPreDraw();
7539 
7540         return true;
7541     }
7542 
7543     @Override
onAttachedToWindow()7544     protected void onAttachedToWindow() {
7545         super.onAttachedToWindow();
7546 
7547         if (mEditor != null) mEditor.onAttachedToWindow();
7548 
7549         if (mPreDrawListenerDetached) {
7550             getViewTreeObserver().addOnPreDrawListener(this);
7551             mPreDrawListenerDetached = false;
7552         }
7553     }
7554 
7555     /** @hide */
7556     @Override
onDetachedFromWindowInternal()7557     protected void onDetachedFromWindowInternal() {
7558         if (mPreDrawRegistered) {
7559             getViewTreeObserver().removeOnPreDrawListener(this);
7560             mPreDrawListenerDetached = true;
7561         }
7562 
7563         resetResolvedDrawables();
7564 
7565         if (mEditor != null) mEditor.onDetachedFromWindow();
7566 
7567         super.onDetachedFromWindowInternal();
7568     }
7569 
7570     @Override
onScreenStateChanged(int screenState)7571     public void onScreenStateChanged(int screenState) {
7572         super.onScreenStateChanged(screenState);
7573         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
7574     }
7575 
7576     @Override
isPaddingOffsetRequired()7577     protected boolean isPaddingOffsetRequired() {
7578         return mShadowRadius != 0 || mDrawables != null;
7579     }
7580 
7581     @Override
getLeftPaddingOffset()7582     protected int getLeftPaddingOffset() {
7583         return getCompoundPaddingLeft() - mPaddingLeft
7584                 + (int) Math.min(0, mShadowDx - mShadowRadius);
7585     }
7586 
7587     @Override
getTopPaddingOffset()7588     protected int getTopPaddingOffset() {
7589         return (int) Math.min(0, mShadowDy - mShadowRadius);
7590     }
7591 
7592     @Override
getBottomPaddingOffset()7593     protected int getBottomPaddingOffset() {
7594         return (int) Math.max(0, mShadowDy + mShadowRadius);
7595     }
7596 
7597     @Override
getRightPaddingOffset()7598     protected int getRightPaddingOffset() {
7599         return -(getCompoundPaddingRight() - mPaddingRight)
7600                 + (int) Math.max(0, mShadowDx + mShadowRadius);
7601     }
7602 
7603     @Override
verifyDrawable(@onNull Drawable who)7604     protected boolean verifyDrawable(@NonNull Drawable who) {
7605         final boolean verified = super.verifyDrawable(who);
7606         if (!verified && mDrawables != null) {
7607             for (Drawable dr : mDrawables.mShowing) {
7608                 if (who == dr) {
7609                     return true;
7610                 }
7611             }
7612         }
7613         return verified;
7614     }
7615 
7616     @Override
jumpDrawablesToCurrentState()7617     public void jumpDrawablesToCurrentState() {
7618         super.jumpDrawablesToCurrentState();
7619         if (mDrawables != null) {
7620             for (Drawable dr : mDrawables.mShowing) {
7621                 if (dr != null) {
7622                     dr.jumpToCurrentState();
7623                 }
7624             }
7625         }
7626     }
7627 
7628     @Override
invalidateDrawable(@onNull Drawable drawable)7629     public void invalidateDrawable(@NonNull Drawable drawable) {
7630         boolean handled = false;
7631 
7632         if (verifyDrawable(drawable)) {
7633             final Rect dirty = drawable.getBounds();
7634             int scrollX = mScrollX;
7635             int scrollY = mScrollY;
7636 
7637             // IMPORTANT: The coordinates below are based on the coordinates computed
7638             // for each compound drawable in onDraw(). Make sure to update each section
7639             // accordingly.
7640             final TextView.Drawables drawables = mDrawables;
7641             if (drawables != null) {
7642                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
7643                     final int compoundPaddingTop = getCompoundPaddingTop();
7644                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7645                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7646 
7647                     scrollX += mPaddingLeft;
7648                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
7649                     handled = true;
7650                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
7651                     final int compoundPaddingTop = getCompoundPaddingTop();
7652                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7653                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7654 
7655                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
7656                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
7657                     handled = true;
7658                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
7659                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7660                     final int compoundPaddingRight = getCompoundPaddingRight();
7661                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7662 
7663                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
7664                     scrollY += mPaddingTop;
7665                     handled = true;
7666                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
7667                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7668                     final int compoundPaddingRight = getCompoundPaddingRight();
7669                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7670 
7671                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
7672                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
7673                     handled = true;
7674                 }
7675             }
7676 
7677             if (handled) {
7678                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
7679                         dirty.right + scrollX, dirty.bottom + scrollY);
7680             }
7681         }
7682 
7683         if (!handled) {
7684             super.invalidateDrawable(drawable);
7685         }
7686     }
7687 
7688     @Override
hasOverlappingRendering()7689     public boolean hasOverlappingRendering() {
7690         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
7691         return ((getBackground() != null && getBackground().getCurrent() != null)
7692                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
7693                 || mShadowColor != 0);
7694     }
7695 
7696     /**
7697      *
7698      * Returns the state of the {@code textIsSelectable} flag (See
7699      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
7700      * to allow users to select and copy text in a non-editable TextView, the content of an
7701      * {@link EditText} can always be selected, independently of the value of this flag.
7702      * <p>
7703      *
7704      * @return True if the text displayed in this TextView can be selected by the user.
7705      *
7706      * @attr ref android.R.styleable#TextView_textIsSelectable
7707      */
7708     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()7709     public boolean isTextSelectable() {
7710         return mEditor == null ? false : mEditor.mTextIsSelectable;
7711     }
7712 
7713     /**
7714      * Sets whether the content of this view is selectable by the user. The default is
7715      * {@code false}, meaning that the content is not selectable.
7716      * <p>
7717      * When you use a TextView to display a useful piece of information to the user (such as a
7718      * contact's address), make it selectable, so that the user can select and copy its
7719      * content. You can also use set the XML attribute
7720      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
7721      * <p>
7722      * When you call this method to set the value of {@code textIsSelectable}, it sets
7723      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
7724      * and {@code longClickable} to the same value. These flags correspond to the attributes
7725      * {@link android.R.styleable#View_focusable android:focusable},
7726      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
7727      * {@link android.R.styleable#View_clickable android:clickable}, and
7728      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
7729      * flags to a state you had set previously, call one or more of the following methods:
7730      * {@link #setFocusable(boolean) setFocusable()},
7731      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
7732      * {@link #setClickable(boolean) setClickable()} or
7733      * {@link #setLongClickable(boolean) setLongClickable()}.
7734      *
7735      * @param selectable Whether the content of this TextView should be selectable.
7736      */
setTextIsSelectable(boolean selectable)7737     public void setTextIsSelectable(boolean selectable) {
7738         if (!selectable && mEditor == null) return; // false is default value with no edit data
7739 
7740         createEditorIfNeeded();
7741         if (mEditor.mTextIsSelectable == selectable) return;
7742 
7743         mEditor.mTextIsSelectable = selectable;
7744         setFocusableInTouchMode(selectable);
7745         setFocusable(FOCUSABLE_AUTO);
7746         setClickable(selectable);
7747         setLongClickable(selectable);
7748 
7749         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
7750 
7751         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
7752         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
7753 
7754         // Called by setText above, but safer in case of future code changes
7755         mEditor.prepareCursorControllers();
7756     }
7757 
7758     @Override
onCreateDrawableState(int extraSpace)7759     protected int[] onCreateDrawableState(int extraSpace) {
7760         final int[] drawableState;
7761 
7762         if (mSingleLine) {
7763             drawableState = super.onCreateDrawableState(extraSpace);
7764         } else {
7765             drawableState = super.onCreateDrawableState(extraSpace + 1);
7766             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
7767         }
7768 
7769         if (isTextSelectable()) {
7770             // Disable pressed state, which was introduced when TextView was made clickable.
7771             // Prevents text color change.
7772             // setClickable(false) would have a similar effect, but it also disables focus changes
7773             // and long press actions, which are both needed by text selection.
7774             final int length = drawableState.length;
7775             for (int i = 0; i < length; i++) {
7776                 if (drawableState[i] == R.attr.state_pressed) {
7777                     final int[] nonPressedState = new int[length - 1];
7778                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
7779                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
7780                     return nonPressedState;
7781                 }
7782             }
7783         }
7784 
7785         return drawableState;
7786     }
7787 
7788     @UnsupportedAppUsage
getUpdatedHighlightPath()7789     private Path getUpdatedHighlightPath() {
7790         Path highlight = null;
7791         Paint highlightPaint = mHighlightPaint;
7792 
7793         final int selStart = getSelectionStart();
7794         final int selEnd = getSelectionEnd();
7795         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
7796             if (selStart == selEnd) {
7797                 if (mEditor != null && mEditor.shouldRenderCursor()) {
7798                     if (mHighlightPathBogus) {
7799                         if (mHighlightPath == null) mHighlightPath = new Path();
7800                         mHighlightPath.reset();
7801                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
7802                         mEditor.updateCursorPosition();
7803                         mHighlightPathBogus = false;
7804                     }
7805 
7806                     // XXX should pass to skin instead of drawing directly
7807                     highlightPaint.setColor(mCurTextColor);
7808                     highlightPaint.setStyle(Paint.Style.STROKE);
7809                     highlight = mHighlightPath;
7810                 }
7811             } else {
7812                 if (mHighlightPathBogus) {
7813                     if (mHighlightPath == null) mHighlightPath = new Path();
7814                     mHighlightPath.reset();
7815                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7816                     mHighlightPathBogus = false;
7817                 }
7818 
7819                 // XXX should pass to skin instead of drawing directly
7820                 highlightPaint.setColor(mHighlightColor);
7821                 highlightPaint.setStyle(Paint.Style.FILL);
7822 
7823                 highlight = mHighlightPath;
7824             }
7825         }
7826         return highlight;
7827     }
7828 
7829     /**
7830      * @hide
7831      */
getHorizontalOffsetForDrawables()7832     public int getHorizontalOffsetForDrawables() {
7833         return 0;
7834     }
7835 
7836     @Override
onDraw(Canvas canvas)7837     protected void onDraw(Canvas canvas) {
7838         restartMarqueeIfNeeded();
7839 
7840         // Draw the background for this view
7841         super.onDraw(canvas);
7842 
7843         final int compoundPaddingLeft = getCompoundPaddingLeft();
7844         final int compoundPaddingTop = getCompoundPaddingTop();
7845         final int compoundPaddingRight = getCompoundPaddingRight();
7846         final int compoundPaddingBottom = getCompoundPaddingBottom();
7847         final int scrollX = mScrollX;
7848         final int scrollY = mScrollY;
7849         final int right = mRight;
7850         final int left = mLeft;
7851         final int bottom = mBottom;
7852         final int top = mTop;
7853         final boolean isLayoutRtl = isLayoutRtl();
7854         final int offset = getHorizontalOffsetForDrawables();
7855         final int leftOffset = isLayoutRtl ? 0 : offset;
7856         final int rightOffset = isLayoutRtl ? offset : 0;
7857 
7858         final Drawables dr = mDrawables;
7859         if (dr != null) {
7860             /*
7861              * Compound, not extended, because the icon is not clipped
7862              * if the text height is smaller.
7863              */
7864 
7865             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
7866             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
7867 
7868             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7869             // Make sure to update invalidateDrawable() when changing this code.
7870             if (dr.mShowing[Drawables.LEFT] != null) {
7871                 canvas.save();
7872                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
7873                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
7874                 dr.mShowing[Drawables.LEFT].draw(canvas);
7875                 canvas.restore();
7876             }
7877 
7878             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7879             // Make sure to update invalidateDrawable() when changing this code.
7880             if (dr.mShowing[Drawables.RIGHT] != null) {
7881                 canvas.save();
7882                 canvas.translate(scrollX + right - left - mPaddingRight
7883                         - dr.mDrawableSizeRight - rightOffset,
7884                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
7885                 dr.mShowing[Drawables.RIGHT].draw(canvas);
7886                 canvas.restore();
7887             }
7888 
7889             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7890             // Make sure to update invalidateDrawable() when changing this code.
7891             if (dr.mShowing[Drawables.TOP] != null) {
7892                 canvas.save();
7893                 canvas.translate(scrollX + compoundPaddingLeft
7894                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
7895                 dr.mShowing[Drawables.TOP].draw(canvas);
7896                 canvas.restore();
7897             }
7898 
7899             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7900             // Make sure to update invalidateDrawable() when changing this code.
7901             if (dr.mShowing[Drawables.BOTTOM] != null) {
7902                 canvas.save();
7903                 canvas.translate(scrollX + compoundPaddingLeft
7904                         + (hspace - dr.mDrawableWidthBottom) / 2,
7905                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
7906                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
7907                 canvas.restore();
7908             }
7909         }
7910 
7911         int color = mCurTextColor;
7912 
7913         if (mLayout == null) {
7914             assumeLayout();
7915         }
7916 
7917         Layout layout = mLayout;
7918 
7919         if (mHint != null && mText.length() == 0) {
7920             if (mHintTextColor != null) {
7921                 color = mCurHintTextColor;
7922             }
7923 
7924             layout = mHintLayout;
7925         }
7926 
7927         mTextPaint.setColor(color);
7928         mTextPaint.drawableState = getDrawableState();
7929 
7930         canvas.save();
7931         /*  Would be faster if we didn't have to do this. Can we chop the
7932             (displayable) text so that we don't need to do this ever?
7933         */
7934 
7935         int extendedPaddingTop = getExtendedPaddingTop();
7936         int extendedPaddingBottom = getExtendedPaddingBottom();
7937 
7938         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7939         final int maxScrollY = mLayout.getHeight() - vspace;
7940 
7941         float clipLeft = compoundPaddingLeft + scrollX;
7942         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
7943         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
7944         float clipBottom = bottom - top + scrollY
7945                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
7946 
7947         if (mShadowRadius != 0) {
7948             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
7949             clipRight += Math.max(0, mShadowDx + mShadowRadius);
7950 
7951             clipTop += Math.min(0, mShadowDy - mShadowRadius);
7952             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
7953         }
7954 
7955         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
7956 
7957         int voffsetText = 0;
7958         int voffsetCursor = 0;
7959 
7960         // translate in by our padding
7961         /* shortcircuit calling getVerticaOffset() */
7962         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7963             voffsetText = getVerticalOffset(false);
7964             voffsetCursor = getVerticalOffset(true);
7965         }
7966         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
7967 
7968         final int layoutDirection = getLayoutDirection();
7969         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7970         if (isMarqueeFadeEnabled()) {
7971             if (!mSingleLine && getLineCount() == 1 && canMarquee()
7972                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
7973                 final int width = mRight - mLeft;
7974                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
7975                 final float dx = mLayout.getLineRight(0) - (width - padding);
7976                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7977             }
7978 
7979             if (mMarquee != null && mMarquee.isRunning()) {
7980                 final float dx = -mMarquee.getScroll();
7981                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7982             }
7983         }
7984 
7985         final int cursorOffsetVertical = voffsetCursor - voffsetText;
7986 
7987         Path highlight = getUpdatedHighlightPath();
7988         if (mEditor != null) {
7989             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
7990         } else {
7991             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7992         }
7993 
7994         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
7995             final float dx = mMarquee.getGhostOffset();
7996             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7997             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7998         }
7999 
8000         canvas.restore();
8001     }
8002 
8003     @Override
getFocusedRect(Rect r)8004     public void getFocusedRect(Rect r) {
8005         if (mLayout == null) {
8006             super.getFocusedRect(r);
8007             return;
8008         }
8009 
8010         int selEnd = getSelectionEnd();
8011         if (selEnd < 0) {
8012             super.getFocusedRect(r);
8013             return;
8014         }
8015 
8016         int selStart = getSelectionStart();
8017         if (selStart < 0 || selStart >= selEnd) {
8018             int line = mLayout.getLineForOffset(selEnd);
8019             r.top = mLayout.getLineTop(line);
8020             r.bottom = mLayout.getLineBottom(line);
8021             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
8022             r.right = r.left + 4;
8023         } else {
8024             int lineStart = mLayout.getLineForOffset(selStart);
8025             int lineEnd = mLayout.getLineForOffset(selEnd);
8026             r.top = mLayout.getLineTop(lineStart);
8027             r.bottom = mLayout.getLineBottom(lineEnd);
8028             if (lineStart == lineEnd) {
8029                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
8030                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
8031             } else {
8032                 // Selection extends across multiple lines -- make the focused
8033                 // rect cover the entire width.
8034                 if (mHighlightPathBogus) {
8035                     if (mHighlightPath == null) mHighlightPath = new Path();
8036                     mHighlightPath.reset();
8037                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8038                     mHighlightPathBogus = false;
8039                 }
8040                 synchronized (TEMP_RECTF) {
8041                     mHighlightPath.computeBounds(TEMP_RECTF, true);
8042                     r.left = (int) TEMP_RECTF.left - 1;
8043                     r.right = (int) TEMP_RECTF.right + 1;
8044                 }
8045             }
8046         }
8047 
8048         // Adjust for padding and gravity.
8049         int paddingLeft = getCompoundPaddingLeft();
8050         int paddingTop = getExtendedPaddingTop();
8051         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8052             paddingTop += getVerticalOffset(false);
8053         }
8054         r.offset(paddingLeft, paddingTop);
8055         int paddingBottom = getExtendedPaddingBottom();
8056         r.bottom += paddingBottom;
8057     }
8058 
8059     /**
8060      * Return the number of lines of text, or 0 if the internal Layout has not
8061      * been built.
8062      */
getLineCount()8063     public int getLineCount() {
8064         return mLayout != null ? mLayout.getLineCount() : 0;
8065     }
8066 
8067     /**
8068      * Return the baseline for the specified line (0...getLineCount() - 1)
8069      * If bounds is not null, return the top, left, right, bottom extents
8070      * of the specified line in it. If the internal Layout has not been built,
8071      * return 0 and set bounds to (0, 0, 0, 0)
8072      * @param line which line to examine (0..getLineCount() - 1)
8073      * @param bounds Optional. If not null, it returns the extent of the line
8074      * @return the Y-coordinate of the baseline
8075      */
getLineBounds(int line, Rect bounds)8076     public int getLineBounds(int line, Rect bounds) {
8077         if (mLayout == null) {
8078             if (bounds != null) {
8079                 bounds.set(0, 0, 0, 0);
8080             }
8081             return 0;
8082         } else {
8083             int baseline = mLayout.getLineBounds(line, bounds);
8084 
8085             int voffset = getExtendedPaddingTop();
8086             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8087                 voffset += getVerticalOffset(true);
8088             }
8089             if (bounds != null) {
8090                 bounds.offset(getCompoundPaddingLeft(), voffset);
8091             }
8092             return baseline + voffset;
8093         }
8094     }
8095 
8096     @Override
getBaseline()8097     public int getBaseline() {
8098         if (mLayout == null) {
8099             return super.getBaseline();
8100         }
8101 
8102         return getBaselineOffset() + mLayout.getLineBaseline(0);
8103     }
8104 
getBaselineOffset()8105     int getBaselineOffset() {
8106         int voffset = 0;
8107         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8108             voffset = getVerticalOffset(true);
8109         }
8110 
8111         if (isLayoutModeOptical(mParent)) {
8112             voffset -= getOpticalInsets().top;
8113         }
8114 
8115         return getExtendedPaddingTop() + voffset;
8116     }
8117 
8118     /**
8119      * @hide
8120      */
8121     @Override
getFadeTop(boolean offsetRequired)8122     protected int getFadeTop(boolean offsetRequired) {
8123         if (mLayout == null) return 0;
8124 
8125         int voffset = 0;
8126         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8127             voffset = getVerticalOffset(true);
8128         }
8129 
8130         if (offsetRequired) voffset += getTopPaddingOffset();
8131 
8132         return getExtendedPaddingTop() + voffset;
8133     }
8134 
8135     /**
8136      * @hide
8137      */
8138     @Override
getFadeHeight(boolean offsetRequired)8139     protected int getFadeHeight(boolean offsetRequired) {
8140         return mLayout != null ? mLayout.getHeight() : 0;
8141     }
8142 
8143     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)8144     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
8145         if (mSpannable != null && mLinksClickable) {
8146             final float x = event.getX(pointerIndex);
8147             final float y = event.getY(pointerIndex);
8148             final int offset = getOffsetForPosition(x, y);
8149             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
8150                     ClickableSpan.class);
8151             if (clickables.length > 0) {
8152                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
8153             }
8154         }
8155         if (isTextSelectable() || isTextEditable()) {
8156             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
8157         }
8158         return super.onResolvePointerIcon(event, pointerIndex);
8159     }
8160 
8161     @Override
onKeyPreIme(int keyCode, KeyEvent event)8162     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
8163         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
8164         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
8165         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
8166         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
8167             return true;
8168         }
8169         return super.onKeyPreIme(keyCode, event);
8170     }
8171 
8172     /**
8173      * @hide
8174      */
handleBackInTextActionModeIfNeeded(KeyEvent event)8175     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
8176         // Do nothing unless mEditor is in text action mode.
8177         if (mEditor == null || mEditor.getTextActionMode() == null) {
8178             return false;
8179         }
8180 
8181         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
8182             KeyEvent.DispatcherState state = getKeyDispatcherState();
8183             if (state != null) {
8184                 state.startTracking(event, this);
8185             }
8186             return true;
8187         } else if (event.getAction() == KeyEvent.ACTION_UP) {
8188             KeyEvent.DispatcherState state = getKeyDispatcherState();
8189             if (state != null) {
8190                 state.handleUpEvent(event);
8191             }
8192             if (event.isTracking() && !event.isCanceled()) {
8193                 stopTextActionMode();
8194                 return true;
8195             }
8196         }
8197         return false;
8198     }
8199 
8200     @Override
onKeyDown(int keyCode, KeyEvent event)8201     public boolean onKeyDown(int keyCode, KeyEvent event) {
8202         final int which = doKeyDown(keyCode, event, null);
8203         if (which == KEY_EVENT_NOT_HANDLED) {
8204             return super.onKeyDown(keyCode, event);
8205         }
8206 
8207         return true;
8208     }
8209 
8210     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8211     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
8212         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
8213         final int which = doKeyDown(keyCode, down, event);
8214         if (which == KEY_EVENT_NOT_HANDLED) {
8215             // Go through default dispatching.
8216             return super.onKeyMultiple(keyCode, repeatCount, event);
8217         }
8218         if (which == KEY_EVENT_HANDLED) {
8219             // Consumed the whole thing.
8220             return true;
8221         }
8222 
8223         repeatCount--;
8224 
8225         // We are going to dispatch the remaining events to either the input
8226         // or movement method.  To do this, we will just send a repeated stream
8227         // of down and up events until we have done the complete repeatCount.
8228         // It would be nice if those interfaces had an onKeyMultiple() method,
8229         // but adding that is a more complicated change.
8230         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
8231         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
8232             // mEditor and mEditor.mInput are not null from doKeyDown
8233             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8234             while (--repeatCount > 0) {
8235                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
8236                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8237             }
8238             hideErrorIfUnchanged();
8239 
8240         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
8241             // mMovement is not null from doKeyDown
8242             mMovement.onKeyUp(this, mSpannable, keyCode, up);
8243             while (--repeatCount > 0) {
8244                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
8245                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
8246             }
8247         }
8248 
8249         return true;
8250     }
8251 
8252     /**
8253      * Returns true if pressing ENTER in this field advances focus instead
8254      * of inserting the character.  This is true mostly in single-line fields,
8255      * but also in mail addresses and subjects which will display on multiple
8256      * lines but where it doesn't make sense to insert newlines.
8257      */
shouldAdvanceFocusOnEnter()8258     private boolean shouldAdvanceFocusOnEnter() {
8259         if (getKeyListener() == null) {
8260             return false;
8261         }
8262 
8263         if (mSingleLine) {
8264             return true;
8265         }
8266 
8267         if (mEditor != null
8268                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8269                         == EditorInfo.TYPE_CLASS_TEXT) {
8270             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8271             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
8272                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
8273                 return true;
8274             }
8275         }
8276 
8277         return false;
8278     }
8279 
8280     /**
8281      * Returns true if pressing TAB in this field advances focus instead
8282      * of inserting the character.  Insert tabs only in multi-line editors.
8283      */
shouldAdvanceFocusOnTab()8284     private boolean shouldAdvanceFocusOnTab() {
8285         if (getKeyListener() != null && !mSingleLine && mEditor != null
8286                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8287                         == EditorInfo.TYPE_CLASS_TEXT) {
8288             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8289             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
8290                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
8291                 return false;
8292             }
8293         }
8294         return true;
8295     }
8296 
isDirectionalNavigationKey(int keyCode)8297     private boolean isDirectionalNavigationKey(int keyCode) {
8298         switch(keyCode) {
8299             case KeyEvent.KEYCODE_DPAD_UP:
8300             case KeyEvent.KEYCODE_DPAD_DOWN:
8301             case KeyEvent.KEYCODE_DPAD_LEFT:
8302             case KeyEvent.KEYCODE_DPAD_RIGHT:
8303                 return true;
8304         }
8305         return false;
8306     }
8307 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8308     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
8309         if (!isEnabled()) {
8310             return KEY_EVENT_NOT_HANDLED;
8311         }
8312 
8313         // If this is the initial keydown, we don't want to prevent a movement away from this view.
8314         // While this shouldn't be necessary because any time we're preventing default movement we
8315         // should be restricting the focus to remain within this view, thus we'll also receive
8316         // the key up event, occasionally key up events will get dropped and we don't want to
8317         // prevent the user from traversing out of this on the next key down.
8318         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8319             mPreventDefaultMovement = false;
8320         }
8321 
8322         switch (keyCode) {
8323             case KeyEvent.KEYCODE_ENTER:
8324                 if (event.hasNoModifiers()) {
8325                     // When mInputContentType is set, we know that we are
8326                     // running in a "modern" cupcake environment, so don't need
8327                     // to worry about the application trying to capture
8328                     // enter key events.
8329                     if (mEditor != null && mEditor.mInputContentType != null) {
8330                         // If there is an action listener, given them a
8331                         // chance to consume the event.
8332                         if (mEditor.mInputContentType.onEditorActionListener != null
8333                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8334                                         this, EditorInfo.IME_NULL, event)) {
8335                             mEditor.mInputContentType.enterDown = true;
8336                             // We are consuming the enter key for them.
8337                             return KEY_EVENT_HANDLED;
8338                         }
8339                     }
8340 
8341                     // If our editor should move focus when enter is pressed, or
8342                     // this is a generated event from an IME action button, then
8343                     // don't let it be inserted into the text.
8344                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8345                             || shouldAdvanceFocusOnEnter()) {
8346                         if (hasOnClickListeners()) {
8347                             return KEY_EVENT_NOT_HANDLED;
8348                         }
8349                         return KEY_EVENT_HANDLED;
8350                     }
8351                 }
8352                 break;
8353 
8354             case KeyEvent.KEYCODE_DPAD_CENTER:
8355                 if (event.hasNoModifiers()) {
8356                     if (shouldAdvanceFocusOnEnter()) {
8357                         return KEY_EVENT_NOT_HANDLED;
8358                     }
8359                 }
8360                 break;
8361 
8362             case KeyEvent.KEYCODE_TAB:
8363                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
8364                     if (shouldAdvanceFocusOnTab()) {
8365                         return KEY_EVENT_NOT_HANDLED;
8366                     }
8367                 }
8368                 break;
8369 
8370                 // Has to be done on key down (and not on key up) to correctly be intercepted.
8371             case KeyEvent.KEYCODE_BACK:
8372                 if (mEditor != null && mEditor.getTextActionMode() != null) {
8373                     stopTextActionMode();
8374                     return KEY_EVENT_HANDLED;
8375                 }
8376                 break;
8377 
8378             case KeyEvent.KEYCODE_CUT:
8379                 if (event.hasNoModifiers() && canCut()) {
8380                     if (onTextContextMenuItem(ID_CUT)) {
8381                         return KEY_EVENT_HANDLED;
8382                     }
8383                 }
8384                 break;
8385 
8386             case KeyEvent.KEYCODE_COPY:
8387                 if (event.hasNoModifiers() && canCopy()) {
8388                     if (onTextContextMenuItem(ID_COPY)) {
8389                         return KEY_EVENT_HANDLED;
8390                     }
8391                 }
8392                 break;
8393 
8394             case KeyEvent.KEYCODE_PASTE:
8395                 if (event.hasNoModifiers() && canPaste()) {
8396                     if (onTextContextMenuItem(ID_PASTE)) {
8397                         return KEY_EVENT_HANDLED;
8398                     }
8399                 }
8400                 break;
8401 
8402             case KeyEvent.KEYCODE_FORWARD_DEL:
8403                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
8404                     if (onTextContextMenuItem(ID_CUT)) {
8405                         return KEY_EVENT_HANDLED;
8406                     }
8407                 }
8408                 break;
8409 
8410             case KeyEvent.KEYCODE_INSERT:
8411                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
8412                     if (onTextContextMenuItem(ID_COPY)) {
8413                         return KEY_EVENT_HANDLED;
8414                     }
8415                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
8416                     if (onTextContextMenuItem(ID_PASTE)) {
8417                         return KEY_EVENT_HANDLED;
8418                     }
8419                 }
8420                 break;
8421         }
8422 
8423         if (mEditor != null && mEditor.mKeyListener != null) {
8424             boolean doDown = true;
8425             if (otherEvent != null) {
8426                 try {
8427                     beginBatchEdit();
8428                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
8429                             otherEvent);
8430                     hideErrorIfUnchanged();
8431                     doDown = false;
8432                     if (handled) {
8433                         return KEY_EVENT_HANDLED;
8434                     }
8435                 } catch (AbstractMethodError e) {
8436                     // onKeyOther was added after 1.0, so if it isn't
8437                     // implemented we need to try to dispatch as a regular down.
8438                 } finally {
8439                     endBatchEdit();
8440                 }
8441             }
8442 
8443             if (doDown) {
8444                 beginBatchEdit();
8445                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
8446                         keyCode, event);
8447                 endBatchEdit();
8448                 hideErrorIfUnchanged();
8449                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
8450             }
8451         }
8452 
8453         // bug 650865: sometimes we get a key event before a layout.
8454         // don't try to move around if we don't know the layout.
8455 
8456         if (mMovement != null && mLayout != null) {
8457             boolean doDown = true;
8458             if (otherEvent != null) {
8459                 try {
8460                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
8461                     doDown = false;
8462                     if (handled) {
8463                         return KEY_EVENT_HANDLED;
8464                     }
8465                 } catch (AbstractMethodError e) {
8466                     // onKeyOther was added after 1.0, so if it isn't
8467                     // implemented we need to try to dispatch as a regular down.
8468                 }
8469             }
8470             if (doDown) {
8471                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
8472                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8473                         mPreventDefaultMovement = true;
8474                     }
8475                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
8476                 }
8477             }
8478             // Consume arrows from keyboard devices to prevent focus leaving the editor.
8479             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
8480             // to move focus with arrows.
8481             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
8482                     && isDirectionalNavigationKey(keyCode)) {
8483                 return KEY_EVENT_HANDLED;
8484             }
8485         }
8486 
8487         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
8488                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
8489     }
8490 
8491     /**
8492      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
8493      * can be recorded.
8494      * @hide
8495      */
resetErrorChangedFlag()8496     public void resetErrorChangedFlag() {
8497         /*
8498          * Keep track of what the error was before doing the input
8499          * so that if an input filter changed the error, we leave
8500          * that error showing.  Otherwise, we take down whatever
8501          * error was showing when the user types something.
8502          */
8503         if (mEditor != null) mEditor.mErrorWasChanged = false;
8504     }
8505 
8506     /**
8507      * @hide
8508      */
hideErrorIfUnchanged()8509     public void hideErrorIfUnchanged() {
8510         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
8511             setError(null, null);
8512         }
8513     }
8514 
8515     @Override
onKeyUp(int keyCode, KeyEvent event)8516     public boolean onKeyUp(int keyCode, KeyEvent event) {
8517         if (!isEnabled()) {
8518             return super.onKeyUp(keyCode, event);
8519         }
8520 
8521         if (!KeyEvent.isModifierKey(keyCode)) {
8522             mPreventDefaultMovement = false;
8523         }
8524 
8525         switch (keyCode) {
8526             case KeyEvent.KEYCODE_DPAD_CENTER:
8527                 if (event.hasNoModifiers()) {
8528                     /*
8529                      * If there is a click listener, just call through to
8530                      * super, which will invoke it.
8531                      *
8532                      * If there isn't a click listener, try to show the soft
8533                      * input method.  (It will also
8534                      * call performClick(), but that won't do anything in
8535                      * this case.)
8536                      */
8537                     if (!hasOnClickListeners()) {
8538                         if (mMovement != null && mText instanceof Editable
8539                                 && mLayout != null && onCheckIsTextEditor()) {
8540                             InputMethodManager imm = getInputMethodManager();
8541                             viewClicked(imm);
8542                             if (imm != null && getShowSoftInputOnFocus()) {
8543                                 imm.showSoftInput(this, 0);
8544                             }
8545                         }
8546                     }
8547                 }
8548                 return super.onKeyUp(keyCode, event);
8549 
8550             case KeyEvent.KEYCODE_ENTER:
8551                 if (event.hasNoModifiers()) {
8552                     if (mEditor != null && mEditor.mInputContentType != null
8553                             && mEditor.mInputContentType.onEditorActionListener != null
8554                             && mEditor.mInputContentType.enterDown) {
8555                         mEditor.mInputContentType.enterDown = false;
8556                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8557                                 this, EditorInfo.IME_NULL, event)) {
8558                             return true;
8559                         }
8560                     }
8561 
8562                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8563                             || shouldAdvanceFocusOnEnter()) {
8564                         /*
8565                          * If there is a click listener, just call through to
8566                          * super, which will invoke it.
8567                          *
8568                          * If there isn't a click listener, try to advance focus,
8569                          * but still call through to super, which will reset the
8570                          * pressed state and longpress state.  (It will also
8571                          * call performClick(), but that won't do anything in
8572                          * this case.)
8573                          */
8574                         if (!hasOnClickListeners()) {
8575                             View v = focusSearch(FOCUS_DOWN);
8576 
8577                             if (v != null) {
8578                                 if (!v.requestFocus(FOCUS_DOWN)) {
8579                                     throw new IllegalStateException("focus search returned a view "
8580                                             + "that wasn't able to take focus!");
8581                                 }
8582 
8583                                 /*
8584                                  * Return true because we handled the key; super
8585                                  * will return false because there was no click
8586                                  * listener.
8587                                  */
8588                                 super.onKeyUp(keyCode, event);
8589                                 return true;
8590                             } else if ((event.getFlags()
8591                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
8592                                 // No target for next focus, but make sure the IME
8593                                 // if this came from it.
8594                                 InputMethodManager imm = getInputMethodManager();
8595                                 if (imm != null && imm.isActive(this)) {
8596                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
8597                                 }
8598                             }
8599                         }
8600                     }
8601                     return super.onKeyUp(keyCode, event);
8602                 }
8603                 break;
8604         }
8605 
8606         if (mEditor != null && mEditor.mKeyListener != null) {
8607             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
8608                 return true;
8609             }
8610         }
8611 
8612         if (mMovement != null && mLayout != null) {
8613             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
8614                 return true;
8615             }
8616         }
8617 
8618         return super.onKeyUp(keyCode, event);
8619     }
8620 
8621     @Override
onCheckIsTextEditor()8622     public boolean onCheckIsTextEditor() {
8623         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
8624     }
8625 
8626     @Override
onCreateInputConnection(EditorInfo outAttrs)8627     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
8628         if (onCheckIsTextEditor() && isEnabled()) {
8629             mEditor.createInputMethodStateIfNeeded();
8630             outAttrs.inputType = getInputType();
8631             if (mEditor.mInputContentType != null) {
8632                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
8633                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
8634                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
8635                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
8636                 outAttrs.extras = mEditor.mInputContentType.extras;
8637                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
8638             } else {
8639                 outAttrs.imeOptions = EditorInfo.IME_NULL;
8640                 outAttrs.hintLocales = null;
8641             }
8642             if (focusSearch(FOCUS_DOWN) != null) {
8643                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
8644             }
8645             if (focusSearch(FOCUS_UP) != null) {
8646                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
8647             }
8648             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
8649                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
8650                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
8651                     // An action has not been set, but the enter key will move to
8652                     // the next focus, so set the action to that.
8653                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
8654                 } else {
8655                     // An action has not been set, and there is no focus to move
8656                     // to, so let's just supply a "done" action.
8657                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
8658                 }
8659                 if (!shouldAdvanceFocusOnEnter()) {
8660                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8661                 }
8662             }
8663             if (isMultilineInputType(outAttrs.inputType)) {
8664                 // Multi-line text editors should always show an enter key.
8665                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8666             }
8667             outAttrs.hintText = mHint;
8668             outAttrs.targetInputMethodUser = mTextOperationUser;
8669             if (mText instanceof Editable) {
8670                 InputConnection ic = new EditableInputConnection(this);
8671                 outAttrs.initialSelStart = getSelectionStart();
8672                 outAttrs.initialSelEnd = getSelectionEnd();
8673                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
8674                 return ic;
8675             }
8676         }
8677         return null;
8678     }
8679 
8680     /**
8681      * If this TextView contains editable content, extract a portion of it
8682      * based on the information in <var>request</var> in to <var>outText</var>.
8683      * @return Returns true if the text was successfully extracted, else false.
8684      */
extractText(ExtractedTextRequest request, ExtractedText outText)8685     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
8686         createEditorIfNeeded();
8687         return mEditor.extractText(request, outText);
8688     }
8689 
8690     /**
8691      * This is used to remove all style-impacting spans from text before new
8692      * extracted text is being replaced into it, so that we don't have any
8693      * lingering spans applied during the replace.
8694      */
removeParcelableSpans(Spannable spannable, int start, int end)8695     static void removeParcelableSpans(Spannable spannable, int start, int end) {
8696         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
8697         int i = spans.length;
8698         while (i > 0) {
8699             i--;
8700             spannable.removeSpan(spans[i]);
8701         }
8702     }
8703 
8704     /**
8705      * Apply to this text view the given extracted text, as previously
8706      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
8707      */
setExtractedText(ExtractedText text)8708     public void setExtractedText(ExtractedText text) {
8709         Editable content = getEditableText();
8710         if (text.text != null) {
8711             if (content == null) {
8712                 setText(text.text, TextView.BufferType.EDITABLE);
8713             } else {
8714                 int start = 0;
8715                 int end = content.length();
8716 
8717                 if (text.partialStartOffset >= 0) {
8718                     final int N = content.length();
8719                     start = text.partialStartOffset;
8720                     if (start > N) start = N;
8721                     end = text.partialEndOffset;
8722                     if (end > N) end = N;
8723                 }
8724 
8725                 removeParcelableSpans(content, start, end);
8726                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
8727                     if (text.text instanceof Spanned) {
8728                         // OK to copy spans only.
8729                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
8730                                 Object.class, content, start);
8731                     }
8732                 } else {
8733                     content.replace(start, end, text.text);
8734                 }
8735             }
8736         }
8737 
8738         // Now set the selection position...  make sure it is in range, to
8739         // avoid crashes.  If this is a partial update, it is possible that
8740         // the underlying text may have changed, causing us problems here.
8741         // Also we just don't want to trust clients to do the right thing.
8742         Spannable sp = (Spannable) getText();
8743         final int N = sp.length();
8744         int start = text.selectionStart;
8745         if (start < 0) {
8746             start = 0;
8747         } else if (start > N) {
8748             start = N;
8749         }
8750         int end = text.selectionEnd;
8751         if (end < 0) {
8752             end = 0;
8753         } else if (end > N) {
8754             end = N;
8755         }
8756         Selection.setSelection(sp, start, end);
8757 
8758         // Finally, update the selection mode.
8759         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
8760             MetaKeyKeyListener.startSelecting(this, sp);
8761         } else {
8762             MetaKeyKeyListener.stopSelecting(this, sp);
8763         }
8764 
8765         setHintInternal(text.hint);
8766     }
8767 
8768     /**
8769      * @hide
8770      */
setExtracting(ExtractedTextRequest req)8771     public void setExtracting(ExtractedTextRequest req) {
8772         if (mEditor.mInputMethodState != null) {
8773             mEditor.mInputMethodState.mExtractedTextRequest = req;
8774         }
8775         // This would stop a possible selection mode, but no such mode is started in case
8776         // extracted mode will start. Some text is selected though, and will trigger an action mode
8777         // in the extracted view.
8778         mEditor.hideCursorAndSpanControllers();
8779         stopTextActionMode();
8780         if (mEditor.mSelectionModifierCursorController != null) {
8781             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
8782         }
8783     }
8784 
8785     /**
8786      * Called by the framework in response to a text completion from
8787      * the current input method, provided by it calling
8788      * {@link InputConnection#commitCompletion
8789      * InputConnection.commitCompletion()}.  The default implementation does
8790      * nothing; text views that are supporting auto-completion should override
8791      * this to do their desired behavior.
8792      *
8793      * @param text The auto complete text the user has selected.
8794      */
onCommitCompletion(CompletionInfo text)8795     public void onCommitCompletion(CompletionInfo text) {
8796         // intentionally empty
8797     }
8798 
8799     /**
8800      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
8801      * dictionary) from the current input method, provided by it calling
8802      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
8803      * The default implementation flashes the background of the corrected word to provide
8804      * feedback to the user.
8805      *
8806      * @param info The auto correct info about the text that was corrected.
8807      */
onCommitCorrection(CorrectionInfo info)8808     public void onCommitCorrection(CorrectionInfo info) {
8809         if (mEditor != null) mEditor.onCommitCorrection(info);
8810     }
8811 
beginBatchEdit()8812     public void beginBatchEdit() {
8813         if (mEditor != null) mEditor.beginBatchEdit();
8814     }
8815 
endBatchEdit()8816     public void endBatchEdit() {
8817         if (mEditor != null) mEditor.endBatchEdit();
8818     }
8819 
8820     /**
8821      * Called by the framework in response to a request to begin a batch
8822      * of edit operations through a call to link {@link #beginBatchEdit()}.
8823      */
onBeginBatchEdit()8824     public void onBeginBatchEdit() {
8825         // intentionally empty
8826     }
8827 
8828     /**
8829      * Called by the framework in response to a request to end a batch
8830      * of edit operations through a call to link {@link #endBatchEdit}.
8831      */
onEndBatchEdit()8832     public void onEndBatchEdit() {
8833         // intentionally empty
8834     }
8835 
8836     /**
8837      * Called by the framework in response to a private command from the
8838      * current method, provided by it calling
8839      * {@link InputConnection#performPrivateCommand
8840      * InputConnection.performPrivateCommand()}.
8841      *
8842      * @param action The action name of the command.
8843      * @param data Any additional data for the command.  This may be null.
8844      * @return Return true if you handled the command, else false.
8845      */
onPrivateIMECommand(String action, Bundle data)8846     public boolean onPrivateIMECommand(String action, Bundle data) {
8847         return false;
8848     }
8849 
8850     /** @hide */
8851     @VisibleForTesting
8852     @UnsupportedAppUsage
nullLayouts()8853     public void nullLayouts() {
8854         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
8855             mSavedLayout = (BoringLayout) mLayout;
8856         }
8857         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
8858             mSavedHintLayout = (BoringLayout) mHintLayout;
8859         }
8860 
8861         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
8862 
8863         mBoring = mHintBoring = null;
8864 
8865         // Since it depends on the value of mLayout
8866         if (mEditor != null) mEditor.prepareCursorControllers();
8867     }
8868 
8869     /**
8870      * Make a new Layout based on the already-measured size of the view,
8871      * on the assumption that it was measured correctly at some point.
8872      */
8873     @UnsupportedAppUsage
assumeLayout()8874     private void assumeLayout() {
8875         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8876 
8877         if (width < 1) {
8878             width = 0;
8879         }
8880 
8881         int physicalWidth = width;
8882 
8883         if (mHorizontallyScrolling) {
8884             width = VERY_WIDE;
8885         }
8886 
8887         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
8888                       physicalWidth, false);
8889     }
8890 
8891     @UnsupportedAppUsage
getLayoutAlignment()8892     private Layout.Alignment getLayoutAlignment() {
8893         Layout.Alignment alignment;
8894         switch (getTextAlignment()) {
8895             case TEXT_ALIGNMENT_GRAVITY:
8896                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
8897                     case Gravity.START:
8898                         alignment = Layout.Alignment.ALIGN_NORMAL;
8899                         break;
8900                     case Gravity.END:
8901                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
8902                         break;
8903                     case Gravity.LEFT:
8904                         alignment = Layout.Alignment.ALIGN_LEFT;
8905                         break;
8906                     case Gravity.RIGHT:
8907                         alignment = Layout.Alignment.ALIGN_RIGHT;
8908                         break;
8909                     case Gravity.CENTER_HORIZONTAL:
8910                         alignment = Layout.Alignment.ALIGN_CENTER;
8911                         break;
8912                     default:
8913                         alignment = Layout.Alignment.ALIGN_NORMAL;
8914                         break;
8915                 }
8916                 break;
8917             case TEXT_ALIGNMENT_TEXT_START:
8918                 alignment = Layout.Alignment.ALIGN_NORMAL;
8919                 break;
8920             case TEXT_ALIGNMENT_TEXT_END:
8921                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
8922                 break;
8923             case TEXT_ALIGNMENT_CENTER:
8924                 alignment = Layout.Alignment.ALIGN_CENTER;
8925                 break;
8926             case TEXT_ALIGNMENT_VIEW_START:
8927                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8928                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8929                 break;
8930             case TEXT_ALIGNMENT_VIEW_END:
8931                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8932                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8933                 break;
8934             case TEXT_ALIGNMENT_INHERIT:
8935                 // This should never happen as we have already resolved the text alignment
8936                 // but better safe than sorry so we just fall through
8937             default:
8938                 alignment = Layout.Alignment.ALIGN_NORMAL;
8939                 break;
8940         }
8941         return alignment;
8942     }
8943 
8944     /**
8945      * The width passed in is now the desired layout width,
8946      * not the full view width with padding.
8947      * {@hide}
8948      */
8949     @VisibleForTesting
8950     @UnsupportedAppUsage
makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8951     public void makeNewLayout(int wantWidth, int hintWidth,
8952                                  BoringLayout.Metrics boring,
8953                                  BoringLayout.Metrics hintBoring,
8954                                  int ellipsisWidth, boolean bringIntoView) {
8955         stopMarquee();
8956 
8957         // Update "old" cached values
8958         mOldMaximum = mMaximum;
8959         mOldMaxMode = mMaxMode;
8960 
8961         mHighlightPathBogus = true;
8962 
8963         if (wantWidth < 0) {
8964             wantWidth = 0;
8965         }
8966         if (hintWidth < 0) {
8967             hintWidth = 0;
8968         }
8969 
8970         Layout.Alignment alignment = getLayoutAlignment();
8971         final boolean testDirChange = mSingleLine && mLayout != null
8972                 && (alignment == Layout.Alignment.ALIGN_NORMAL
8973                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
8974         int oldDir = 0;
8975         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
8976         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
8977         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
8978                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
8979         TruncateAt effectiveEllipsize = mEllipsize;
8980         if (mEllipsize == TruncateAt.MARQUEE
8981                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8982             effectiveEllipsize = TruncateAt.END_SMALL;
8983         }
8984 
8985         if (mTextDir == null) {
8986             mTextDir = getTextDirectionHeuristic();
8987         }
8988 
8989         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
8990                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
8991         if (switchEllipsize) {
8992             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
8993                     ? TruncateAt.END : TruncateAt.MARQUEE;
8994             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
8995                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
8996         }
8997 
8998         shouldEllipsize = mEllipsize != null;
8999         mHintLayout = null;
9000 
9001         if (mHint != null) {
9002             if (shouldEllipsize) hintWidth = wantWidth;
9003 
9004             if (hintBoring == UNKNOWN_BORING) {
9005                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
9006                                                    mHintBoring);
9007                 if (hintBoring != null) {
9008                     mHintBoring = hintBoring;
9009                 }
9010             }
9011 
9012             if (hintBoring != null) {
9013                 if (hintBoring.width <= hintWidth
9014                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
9015                     if (mSavedHintLayout != null) {
9016                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9017                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9018                                 hintBoring, mIncludePad);
9019                     } else {
9020                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9021                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9022                                 hintBoring, mIncludePad);
9023                     }
9024 
9025                     mSavedHintLayout = (BoringLayout) mHintLayout;
9026                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
9027                     if (mSavedHintLayout != null) {
9028                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9029                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9030                                 hintBoring, mIncludePad, mEllipsize,
9031                                 ellipsisWidth);
9032                     } else {
9033                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9034                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9035                                 hintBoring, mIncludePad, mEllipsize,
9036                                 ellipsisWidth);
9037                     }
9038                 }
9039             }
9040             // TODO: code duplication with makeSingleLayout()
9041             if (mHintLayout == null) {
9042                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
9043                         mHint.length(), mTextPaint, hintWidth)
9044                         .setAlignment(alignment)
9045                         .setTextDirection(mTextDir)
9046                         .setLineSpacing(mSpacingAdd, mSpacingMult)
9047                         .setIncludePad(mIncludePad)
9048                         .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9049                         .setBreakStrategy(mBreakStrategy)
9050                         .setHyphenationFrequency(mHyphenationFrequency)
9051                         .setJustificationMode(mJustificationMode)
9052                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9053                 if (shouldEllipsize) {
9054                     builder.setEllipsize(mEllipsize)
9055                             .setEllipsizedWidth(ellipsisWidth);
9056                 }
9057                 mHintLayout = builder.build();
9058             }
9059         }
9060 
9061         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
9062             registerForPreDraw();
9063         }
9064 
9065         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9066             if (!compressText(ellipsisWidth)) {
9067                 final int height = mLayoutParams.height;
9068                 // If the size of the view does not depend on the size of the text, try to
9069                 // start the marquee immediately
9070                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
9071                     startMarquee();
9072                 } else {
9073                     // Defer the start of the marquee until we know our width (see setFrame())
9074                     mRestartMarquee = true;
9075                 }
9076             }
9077         }
9078 
9079         // CursorControllers need a non-null mLayout
9080         if (mEditor != null) mEditor.prepareCursorControllers();
9081     }
9082 
9083     /**
9084      * Returns true if DynamicLayout is required
9085      *
9086      * @hide
9087      */
9088     @VisibleForTesting
useDynamicLayout()9089     public boolean useDynamicLayout() {
9090         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
9091     }
9092 
9093     /**
9094      * @hide
9095      */
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9096     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
9097             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
9098             boolean useSaved) {
9099         Layout result = null;
9100         if (useDynamicLayout()) {
9101             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
9102                     wantWidth)
9103                     .setDisplayText(mTransformed)
9104                     .setAlignment(alignment)
9105                     .setTextDirection(mTextDir)
9106                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9107                     .setIncludePad(mIncludePad)
9108                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9109                     .setBreakStrategy(mBreakStrategy)
9110                     .setHyphenationFrequency(mHyphenationFrequency)
9111                     .setJustificationMode(mJustificationMode)
9112                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
9113                     .setEllipsizedWidth(ellipsisWidth);
9114             result = builder.build();
9115         } else {
9116             if (boring == UNKNOWN_BORING) {
9117                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9118                 if (boring != null) {
9119                     mBoring = boring;
9120                 }
9121             }
9122 
9123             if (boring != null) {
9124                 if (boring.width <= wantWidth
9125                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
9126                     if (useSaved && mSavedLayout != null) {
9127                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9128                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9129                                 boring, mIncludePad);
9130                     } else {
9131                         result = BoringLayout.make(mTransformed, mTextPaint,
9132                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9133                                 boring, mIncludePad);
9134                     }
9135 
9136                     if (useSaved) {
9137                         mSavedLayout = (BoringLayout) result;
9138                     }
9139                 } else if (shouldEllipsize && boring.width <= wantWidth) {
9140                     if (useSaved && mSavedLayout != null) {
9141                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9142                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9143                                 boring, mIncludePad, effectiveEllipsize,
9144                                 ellipsisWidth);
9145                     } else {
9146                         result = BoringLayout.make(mTransformed, mTextPaint,
9147                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9148                                 boring, mIncludePad, effectiveEllipsize,
9149                                 ellipsisWidth);
9150                     }
9151                 }
9152             }
9153         }
9154         if (result == null) {
9155             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
9156                     0, mTransformed.length(), mTextPaint, wantWidth)
9157                     .setAlignment(alignment)
9158                     .setTextDirection(mTextDir)
9159                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9160                     .setIncludePad(mIncludePad)
9161                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9162                     .setBreakStrategy(mBreakStrategy)
9163                     .setHyphenationFrequency(mHyphenationFrequency)
9164                     .setJustificationMode(mJustificationMode)
9165                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9166             if (shouldEllipsize) {
9167                 builder.setEllipsize(effectiveEllipsize)
9168                         .setEllipsizedWidth(ellipsisWidth);
9169             }
9170             result = builder.build();
9171         }
9172         return result;
9173     }
9174 
9175     @UnsupportedAppUsage
compressText(float width)9176     private boolean compressText(float width) {
9177         if (isHardwareAccelerated()) return false;
9178 
9179         // Only compress the text if it hasn't been compressed by the previous pass
9180         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
9181                 && mTextPaint.getTextScaleX() == 1.0f) {
9182             final float textWidth = mLayout.getLineWidth(0);
9183             final float overflow = (textWidth + 1.0f - width) / width;
9184             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
9185                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
9186                 post(new Runnable() {
9187                     public void run() {
9188                         requestLayout();
9189                     }
9190                 });
9191                 return true;
9192             }
9193         }
9194 
9195         return false;
9196     }
9197 
desired(Layout layout)9198     private static int desired(Layout layout) {
9199         int n = layout.getLineCount();
9200         CharSequence text = layout.getText();
9201         float max = 0;
9202 
9203         // if any line was wrapped, we can't use it.
9204         // but it's ok for the last line not to have a newline
9205 
9206         for (int i = 0; i < n - 1; i++) {
9207             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
9208                 return -1;
9209             }
9210         }
9211 
9212         for (int i = 0; i < n; i++) {
9213             max = Math.max(max, layout.getLineWidth(i));
9214         }
9215 
9216         return (int) Math.ceil(max);
9217     }
9218 
9219     /**
9220      * Set whether the TextView includes extra top and bottom padding to make
9221      * room for accents that go above the normal ascent and descent.
9222      * The default is true.
9223      *
9224      * @see #getIncludeFontPadding()
9225      *
9226      * @attr ref android.R.styleable#TextView_includeFontPadding
9227      */
setIncludeFontPadding(boolean includepad)9228     public void setIncludeFontPadding(boolean includepad) {
9229         if (mIncludePad != includepad) {
9230             mIncludePad = includepad;
9231 
9232             if (mLayout != null) {
9233                 nullLayouts();
9234                 requestLayout();
9235                 invalidate();
9236             }
9237         }
9238     }
9239 
9240     /**
9241      * Gets whether the TextView includes extra top and bottom padding to make
9242      * room for accents that go above the normal ascent and descent.
9243      *
9244      * @see #setIncludeFontPadding(boolean)
9245      *
9246      * @attr ref android.R.styleable#TextView_includeFontPadding
9247      */
9248     @InspectableProperty
getIncludeFontPadding()9249     public boolean getIncludeFontPadding() {
9250         return mIncludePad;
9251     }
9252 
9253     /** @hide */
9254     @VisibleForTesting
9255     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
9256 
9257     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)9258     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
9259         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
9260         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
9261         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
9262         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
9263 
9264         int width;
9265         int height;
9266 
9267         BoringLayout.Metrics boring = UNKNOWN_BORING;
9268         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
9269 
9270         if (mTextDir == null) {
9271             mTextDir = getTextDirectionHeuristic();
9272         }
9273 
9274         int des = -1;
9275         boolean fromexisting = false;
9276         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
9277                 ?  (float) widthSize : Float.MAX_VALUE;
9278 
9279         if (widthMode == MeasureSpec.EXACTLY) {
9280             // Parent has told us how big to be. So be it.
9281             width = widthSize;
9282         } else {
9283             if (mLayout != null && mEllipsize == null) {
9284                 des = desired(mLayout);
9285             }
9286 
9287             if (des < 0) {
9288                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9289                 if (boring != null) {
9290                     mBoring = boring;
9291                 }
9292             } else {
9293                 fromexisting = true;
9294             }
9295 
9296             if (boring == null || boring == UNKNOWN_BORING) {
9297                 if (des < 0) {
9298                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
9299                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
9300                 }
9301                 width = des;
9302             } else {
9303                 width = boring.width;
9304             }
9305 
9306             final Drawables dr = mDrawables;
9307             if (dr != null) {
9308                 width = Math.max(width, dr.mDrawableWidthTop);
9309                 width = Math.max(width, dr.mDrawableWidthBottom);
9310             }
9311 
9312             if (mHint != null) {
9313                 int hintDes = -1;
9314                 int hintWidth;
9315 
9316                 if (mHintLayout != null && mEllipsize == null) {
9317                     hintDes = desired(mHintLayout);
9318                 }
9319 
9320                 if (hintDes < 0) {
9321                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
9322                     if (hintBoring != null) {
9323                         mHintBoring = hintBoring;
9324                     }
9325                 }
9326 
9327                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
9328                     if (hintDes < 0) {
9329                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
9330                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
9331                     }
9332                     hintWidth = hintDes;
9333                 } else {
9334                     hintWidth = hintBoring.width;
9335                 }
9336 
9337                 if (hintWidth > width) {
9338                     width = hintWidth;
9339                 }
9340             }
9341 
9342             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
9343 
9344             if (mMaxWidthMode == EMS) {
9345                 width = Math.min(width, mMaxWidth * getLineHeight());
9346             } else {
9347                 width = Math.min(width, mMaxWidth);
9348             }
9349 
9350             if (mMinWidthMode == EMS) {
9351                 width = Math.max(width, mMinWidth * getLineHeight());
9352             } else {
9353                 width = Math.max(width, mMinWidth);
9354             }
9355 
9356             // Check against our minimum width
9357             width = Math.max(width, getSuggestedMinimumWidth());
9358 
9359             if (widthMode == MeasureSpec.AT_MOST) {
9360                 width = Math.min(widthSize, width);
9361             }
9362         }
9363 
9364         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
9365         int unpaddedWidth = want;
9366 
9367         if (mHorizontallyScrolling) want = VERY_WIDE;
9368 
9369         int hintWant = want;
9370         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
9371 
9372         if (mLayout == null) {
9373             makeNewLayout(want, hintWant, boring, hintBoring,
9374                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9375         } else {
9376             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
9377                     || (mLayout.getEllipsizedWidth()
9378                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
9379 
9380             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
9381                     && (want > mLayout.getWidth())
9382                     && (mLayout instanceof BoringLayout
9383                             || (fromexisting && des >= 0 && des <= want));
9384 
9385             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
9386 
9387             if (layoutChanged || maximumChanged) {
9388                 if (!maximumChanged && widthChanged) {
9389                     mLayout.increaseWidthTo(want);
9390                 } else {
9391                     makeNewLayout(want, hintWant, boring, hintBoring,
9392                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9393                 }
9394             } else {
9395                 // Nothing has changed
9396             }
9397         }
9398 
9399         if (heightMode == MeasureSpec.EXACTLY) {
9400             // Parent has told us how big to be. So be it.
9401             height = heightSize;
9402             mDesiredHeightAtMeasure = -1;
9403         } else {
9404             int desired = getDesiredHeight();
9405 
9406             height = desired;
9407             mDesiredHeightAtMeasure = desired;
9408 
9409             if (heightMode == MeasureSpec.AT_MOST) {
9410                 height = Math.min(desired, heightSize);
9411             }
9412         }
9413 
9414         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
9415         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
9416             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
9417         }
9418 
9419         /*
9420          * We didn't let makeNewLayout() register to bring the cursor into view,
9421          * so do it here if there is any possibility that it is needed.
9422          */
9423         if (mMovement != null
9424                 || mLayout.getWidth() > unpaddedWidth
9425                 || mLayout.getHeight() > unpaddedHeight) {
9426             registerForPreDraw();
9427         } else {
9428             scrollTo(0, 0);
9429         }
9430 
9431         setMeasuredDimension(width, height);
9432     }
9433 
9434     /**
9435      * Automatically computes and sets the text size.
9436      */
autoSizeText()9437     private void autoSizeText() {
9438         if (!isAutoSizeEnabled()) {
9439             return;
9440         }
9441 
9442         if (mNeedsAutoSizeText) {
9443             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
9444                 return;
9445             }
9446 
9447             final int availableWidth = mHorizontallyScrolling
9448                     ? VERY_WIDE
9449                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
9450             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
9451                     - getExtendedPaddingTop();
9452 
9453             if (availableWidth <= 0 || availableHeight <= 0) {
9454                 return;
9455             }
9456 
9457             synchronized (TEMP_RECTF) {
9458                 TEMP_RECTF.setEmpty();
9459                 TEMP_RECTF.right = availableWidth;
9460                 TEMP_RECTF.bottom = availableHeight;
9461                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
9462 
9463                 if (optimalTextSize != getTextSize()) {
9464                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
9465                             false /* shouldRequestLayout */);
9466 
9467                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
9468                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9469                             false /* bringIntoView */);
9470                 }
9471             }
9472         }
9473         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
9474         // after the next layout pass should set this to false.
9475         mNeedsAutoSizeText = true;
9476     }
9477 
9478     /**
9479      * Performs a binary search to find the largest text size that will still fit within the size
9480      * available to this view.
9481      */
findLargestTextSizeWhichFits(RectF availableSpace)9482     private int findLargestTextSizeWhichFits(RectF availableSpace) {
9483         final int sizesCount = mAutoSizeTextSizesInPx.length;
9484         if (sizesCount == 0) {
9485             throw new IllegalStateException("No available text sizes to choose from.");
9486         }
9487 
9488         int bestSizeIndex = 0;
9489         int lowIndex = bestSizeIndex + 1;
9490         int highIndex = sizesCount - 1;
9491         int sizeToTryIndex;
9492         while (lowIndex <= highIndex) {
9493             sizeToTryIndex = (lowIndex + highIndex) / 2;
9494             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
9495                 bestSizeIndex = lowIndex;
9496                 lowIndex = sizeToTryIndex + 1;
9497             } else {
9498                 highIndex = sizeToTryIndex - 1;
9499                 bestSizeIndex = highIndex;
9500             }
9501         }
9502 
9503         return mAutoSizeTextSizesInPx[bestSizeIndex];
9504     }
9505 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9506     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
9507         final CharSequence text = mTransformed != null
9508                 ? mTransformed
9509                 : getText();
9510         final int maxLines = getMaxLines();
9511         if (mTempTextPaint == null) {
9512             mTempTextPaint = new TextPaint();
9513         } else {
9514             mTempTextPaint.reset();
9515         }
9516         mTempTextPaint.set(getPaint());
9517         mTempTextPaint.setTextSize(suggestedSizeInPx);
9518 
9519         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
9520                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
9521 
9522         layoutBuilder.setAlignment(getLayoutAlignment())
9523                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
9524                 .setIncludePad(getIncludeFontPadding())
9525                 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9526                 .setBreakStrategy(getBreakStrategy())
9527                 .setHyphenationFrequency(getHyphenationFrequency())
9528                 .setJustificationMode(getJustificationMode())
9529                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9530                 .setTextDirection(getTextDirectionHeuristic());
9531 
9532         final StaticLayout layout = layoutBuilder.build();
9533 
9534         // Lines overflow.
9535         if (maxLines != -1 && layout.getLineCount() > maxLines) {
9536             return false;
9537         }
9538 
9539         // Height overflow.
9540         if (layout.getHeight() > availableSpace.bottom) {
9541             return false;
9542         }
9543 
9544         return true;
9545     }
9546 
getDesiredHeight()9547     private int getDesiredHeight() {
9548         return Math.max(
9549                 getDesiredHeight(mLayout, true),
9550                 getDesiredHeight(mHintLayout, mEllipsize != null));
9551     }
9552 
getDesiredHeight(Layout layout, boolean cap)9553     private int getDesiredHeight(Layout layout, boolean cap) {
9554         if (layout == null) {
9555             return 0;
9556         }
9557 
9558         /*
9559         * Don't cap the hint to a certain number of lines.
9560         * (Do cap it, though, if we have a maximum pixel height.)
9561         */
9562         int desired = layout.getHeight(cap);
9563 
9564         final Drawables dr = mDrawables;
9565         if (dr != null) {
9566             desired = Math.max(desired, dr.mDrawableHeightLeft);
9567             desired = Math.max(desired, dr.mDrawableHeightRight);
9568         }
9569 
9570         int linecount = layout.getLineCount();
9571         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
9572         desired += padding;
9573 
9574         if (mMaxMode != LINES) {
9575             desired = Math.min(desired, mMaximum);
9576         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
9577                 || layout instanceof BoringLayout)) {
9578             desired = layout.getLineTop(mMaximum);
9579 
9580             if (dr != null) {
9581                 desired = Math.max(desired, dr.mDrawableHeightLeft);
9582                 desired = Math.max(desired, dr.mDrawableHeightRight);
9583             }
9584 
9585             desired += padding;
9586             linecount = mMaximum;
9587         }
9588 
9589         if (mMinMode == LINES) {
9590             if (linecount < mMinimum) {
9591                 desired += getLineHeight() * (mMinimum - linecount);
9592             }
9593         } else {
9594             desired = Math.max(desired, mMinimum);
9595         }
9596 
9597         // Check against our minimum height
9598         desired = Math.max(desired, getSuggestedMinimumHeight());
9599 
9600         return desired;
9601     }
9602 
9603     /**
9604      * Check whether a change to the existing text layout requires a
9605      * new view layout.
9606      */
checkForResize()9607     private void checkForResize() {
9608         boolean sizeChanged = false;
9609 
9610         if (mLayout != null) {
9611             // Check if our width changed
9612             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
9613                 sizeChanged = true;
9614                 invalidate();
9615             }
9616 
9617             // Check if our height changed
9618             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
9619                 int desiredHeight = getDesiredHeight();
9620 
9621                 if (desiredHeight != this.getHeight()) {
9622                     sizeChanged = true;
9623                 }
9624             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
9625                 if (mDesiredHeightAtMeasure >= 0) {
9626                     int desiredHeight = getDesiredHeight();
9627 
9628                     if (desiredHeight != mDesiredHeightAtMeasure) {
9629                         sizeChanged = true;
9630                     }
9631                 }
9632             }
9633         }
9634 
9635         if (sizeChanged) {
9636             requestLayout();
9637             // caller will have already invalidated
9638         }
9639     }
9640 
9641     /**
9642      * Check whether entirely new text requires a new view layout
9643      * or merely a new text layout.
9644      */
9645     @UnsupportedAppUsage
checkForRelayout()9646     private void checkForRelayout() {
9647         // If we have a fixed width, we can just swap in a new text layout
9648         // if the text height stays the same or if the view height is fixed.
9649 
9650         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
9651                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
9652                 && (mHint == null || mHintLayout != null)
9653                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
9654             // Static width, so try making a new text layout.
9655 
9656             int oldht = mLayout.getHeight();
9657             int want = mLayout.getWidth();
9658             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
9659 
9660             /*
9661              * No need to bring the text into view, since the size is not
9662              * changing (unless we do the requestLayout(), in which case it
9663              * will happen at measure).
9664              */
9665             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
9666                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9667                           false);
9668 
9669             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
9670                 // In a fixed-height view, so use our new text layout.
9671                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
9672                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
9673                     autoSizeText();
9674                     invalidate();
9675                     return;
9676                 }
9677 
9678                 // Dynamic height, but height has stayed the same,
9679                 // so use our new text layout.
9680                 if (mLayout.getHeight() == oldht
9681                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
9682                     autoSizeText();
9683                     invalidate();
9684                     return;
9685                 }
9686             }
9687 
9688             // We lose: the height has changed and we have a dynamic height.
9689             // Request a new view layout using our new text layout.
9690             requestLayout();
9691             invalidate();
9692         } else {
9693             // Dynamic width, so we have no choice but to request a new
9694             // view layout with a new text layout.
9695             nullLayouts();
9696             requestLayout();
9697             invalidate();
9698         }
9699     }
9700 
9701     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)9702     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
9703         super.onLayout(changed, left, top, right, bottom);
9704         if (mDeferScroll >= 0) {
9705             int curs = mDeferScroll;
9706             mDeferScroll = -1;
9707             bringPointIntoView(Math.min(curs, mText.length()));
9708         }
9709         // Call auto-size after the width and height have been calculated.
9710         autoSizeText();
9711     }
9712 
isShowingHint()9713     private boolean isShowingHint() {
9714         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
9715     }
9716 
9717     /**
9718      * Returns true if anything changed.
9719      */
9720     @UnsupportedAppUsage
bringTextIntoView()9721     private boolean bringTextIntoView() {
9722         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9723         int line = 0;
9724         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9725             line = layout.getLineCount() - 1;
9726         }
9727 
9728         Layout.Alignment a = layout.getParagraphAlignment(line);
9729         int dir = layout.getParagraphDirection(line);
9730         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9731         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9732         int ht = layout.getHeight();
9733 
9734         int scrollx, scrolly;
9735 
9736         // Convert to left, center, or right alignment.
9737         if (a == Layout.Alignment.ALIGN_NORMAL) {
9738             a = dir == Layout.DIR_LEFT_TO_RIGHT
9739                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
9740         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
9741             a = dir == Layout.DIR_LEFT_TO_RIGHT
9742                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
9743         }
9744 
9745         if (a == Layout.Alignment.ALIGN_CENTER) {
9746             /*
9747              * Keep centered if possible, or, if it is too wide to fit,
9748              * keep leading edge in view.
9749              */
9750 
9751             int left = (int) Math.floor(layout.getLineLeft(line));
9752             int right = (int) Math.ceil(layout.getLineRight(line));
9753 
9754             if (right - left < hspace) {
9755                 scrollx = (right + left) / 2 - hspace / 2;
9756             } else {
9757                 if (dir < 0) {
9758                     scrollx = right - hspace;
9759                 } else {
9760                     scrollx = left;
9761                 }
9762             }
9763         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
9764             int right = (int) Math.ceil(layout.getLineRight(line));
9765             scrollx = right - hspace;
9766         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
9767             scrollx = (int) Math.floor(layout.getLineLeft(line));
9768         }
9769 
9770         if (ht < vspace) {
9771             scrolly = 0;
9772         } else {
9773             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9774                 scrolly = ht - vspace;
9775             } else {
9776                 scrolly = 0;
9777             }
9778         }
9779 
9780         if (scrollx != mScrollX || scrolly != mScrollY) {
9781             scrollTo(scrollx, scrolly);
9782             return true;
9783         } else {
9784             return false;
9785         }
9786     }
9787 
9788     /**
9789      * Move the point, specified by the offset, into the view if it is needed.
9790      * This has to be called after layout. Returns true if anything changed.
9791      */
bringPointIntoView(int offset)9792     public boolean bringPointIntoView(int offset) {
9793         if (isLayoutRequested()) {
9794             mDeferScroll = offset;
9795             return false;
9796         }
9797         boolean changed = false;
9798 
9799         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9800 
9801         if (layout == null) return changed;
9802 
9803         int line = layout.getLineForOffset(offset);
9804 
9805         int grav;
9806 
9807         switch (layout.getParagraphAlignment(line)) {
9808             case ALIGN_LEFT:
9809                 grav = 1;
9810                 break;
9811             case ALIGN_RIGHT:
9812                 grav = -1;
9813                 break;
9814             case ALIGN_NORMAL:
9815                 grav = layout.getParagraphDirection(line);
9816                 break;
9817             case ALIGN_OPPOSITE:
9818                 grav = -layout.getParagraphDirection(line);
9819                 break;
9820             case ALIGN_CENTER:
9821             default:
9822                 grav = 0;
9823                 break;
9824         }
9825 
9826         // We only want to clamp the cursor to fit within the layout width
9827         // in left-to-right modes, because in a right to left alignment,
9828         // we want to scroll to keep the line-right on the screen, as other
9829         // lines are likely to have text flush with the right margin, which
9830         // we want to keep visible.
9831         // A better long-term solution would probably be to measure both
9832         // the full line and a blank-trimmed version, and, for example, use
9833         // the latter measurement for centering and right alignment, but for
9834         // the time being we only implement the cursor clamping in left to
9835         // right where it is most likely to be annoying.
9836         final boolean clamped = grav > 0;
9837         // FIXME: Is it okay to truncate this, or should we round?
9838         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
9839         final int top = layout.getLineTop(line);
9840         final int bottom = layout.getLineTop(line + 1);
9841 
9842         int left = (int) Math.floor(layout.getLineLeft(line));
9843         int right = (int) Math.ceil(layout.getLineRight(line));
9844         int ht = layout.getHeight();
9845 
9846         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9847         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9848         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
9849             // If cursor has been clamped, make sure we don't scroll.
9850             right = Math.max(x, left + hspace);
9851         }
9852 
9853         int hslack = (bottom - top) / 2;
9854         int vslack = hslack;
9855 
9856         if (vslack > vspace / 4) {
9857             vslack = vspace / 4;
9858         }
9859         if (hslack > hspace / 4) {
9860             hslack = hspace / 4;
9861         }
9862 
9863         int hs = mScrollX;
9864         int vs = mScrollY;
9865 
9866         if (top - vs < vslack) {
9867             vs = top - vslack;
9868         }
9869         if (bottom - vs > vspace - vslack) {
9870             vs = bottom - (vspace - vslack);
9871         }
9872         if (ht - vs < vspace) {
9873             vs = ht - vspace;
9874         }
9875         if (0 - vs > 0) {
9876             vs = 0;
9877         }
9878 
9879         if (grav != 0) {
9880             if (x - hs < hslack) {
9881                 hs = x - hslack;
9882             }
9883             if (x - hs > hspace - hslack) {
9884                 hs = x - (hspace - hslack);
9885             }
9886         }
9887 
9888         if (grav < 0) {
9889             if (left - hs > 0) {
9890                 hs = left;
9891             }
9892             if (right - hs < hspace) {
9893                 hs = right - hspace;
9894             }
9895         } else if (grav > 0) {
9896             if (right - hs < hspace) {
9897                 hs = right - hspace;
9898             }
9899             if (left - hs > 0) {
9900                 hs = left;
9901             }
9902         } else /* grav == 0 */ {
9903             if (right - left <= hspace) {
9904                 /*
9905                  * If the entire text fits, center it exactly.
9906                  */
9907                 hs = left - (hspace - (right - left)) / 2;
9908             } else if (x > right - hslack) {
9909                 /*
9910                  * If we are near the right edge, keep the right edge
9911                  * at the edge of the view.
9912                  */
9913                 hs = right - hspace;
9914             } else if (x < left + hslack) {
9915                 /*
9916                  * If we are near the left edge, keep the left edge
9917                  * at the edge of the view.
9918                  */
9919                 hs = left;
9920             } else if (left > hs) {
9921                 /*
9922                  * Is there whitespace visible at the left?  Fix it if so.
9923                  */
9924                 hs = left;
9925             } else if (right < hs + hspace) {
9926                 /*
9927                  * Is there whitespace visible at the right?  Fix it if so.
9928                  */
9929                 hs = right - hspace;
9930             } else {
9931                 /*
9932                  * Otherwise, float as needed.
9933                  */
9934                 if (x - hs < hslack) {
9935                     hs = x - hslack;
9936                 }
9937                 if (x - hs > hspace - hslack) {
9938                     hs = x - (hspace - hslack);
9939                 }
9940             }
9941         }
9942 
9943         if (hs != mScrollX || vs != mScrollY) {
9944             if (mScroller == null) {
9945                 scrollTo(hs, vs);
9946             } else {
9947                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
9948                 int dx = hs - mScrollX;
9949                 int dy = vs - mScrollY;
9950 
9951                 if (duration > ANIMATED_SCROLL_GAP) {
9952                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
9953                     awakenScrollBars(mScroller.getDuration());
9954                     invalidate();
9955                 } else {
9956                     if (!mScroller.isFinished()) {
9957                         mScroller.abortAnimation();
9958                     }
9959 
9960                     scrollBy(dx, dy);
9961                 }
9962 
9963                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
9964             }
9965 
9966             changed = true;
9967         }
9968 
9969         if (isFocused()) {
9970             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
9971             // requestRectangleOnScreen() is in terms of content coordinates.
9972 
9973             // The offsets here are to ensure the rectangle we are using is
9974             // within our view bounds, in case the cursor is on the far left
9975             // or right.  If it isn't withing the bounds, then this request
9976             // will be ignored.
9977             if (mTempRect == null) mTempRect = new Rect();
9978             mTempRect.set(x - 2, top, x + 2, bottom);
9979             getInterestingRect(mTempRect, line);
9980             mTempRect.offset(mScrollX, mScrollY);
9981 
9982             if (requestRectangleOnScreen(mTempRect)) {
9983                 changed = true;
9984             }
9985         }
9986 
9987         return changed;
9988     }
9989 
9990     /**
9991      * Move the cursor, if needed, so that it is at an offset that is visible
9992      * to the user.  This will not move the cursor if it represents more than
9993      * one character (a selection range).  This will only work if the
9994      * TextView contains spannable text; otherwise it will do nothing.
9995      *
9996      * @return True if the cursor was actually moved, false otherwise.
9997      */
moveCursorToVisibleOffset()9998     public boolean moveCursorToVisibleOffset() {
9999         if (!(mText instanceof Spannable)) {
10000             return false;
10001         }
10002         int start = getSelectionStart();
10003         int end = getSelectionEnd();
10004         if (start != end) {
10005             return false;
10006         }
10007 
10008         // First: make sure the line is visible on screen:
10009 
10010         int line = mLayout.getLineForOffset(start);
10011 
10012         final int top = mLayout.getLineTop(line);
10013         final int bottom = mLayout.getLineTop(line + 1);
10014         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10015         int vslack = (bottom - top) / 2;
10016         if (vslack > vspace / 4) {
10017             vslack = vspace / 4;
10018         }
10019         final int vs = mScrollY;
10020 
10021         if (top < (vs + vslack)) {
10022             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
10023         } else if (bottom > (vspace + vs - vslack)) {
10024             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
10025         }
10026 
10027         // Next: make sure the character is visible on screen:
10028 
10029         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10030         final int hs = mScrollX;
10031         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
10032         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
10033 
10034         // line might contain bidirectional text
10035         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
10036         final int highChar = leftChar > rightChar ? leftChar : rightChar;
10037 
10038         int newStart = start;
10039         if (newStart < lowChar) {
10040             newStart = lowChar;
10041         } else if (newStart > highChar) {
10042             newStart = highChar;
10043         }
10044 
10045         if (newStart != start) {
10046             Selection.setSelection(mSpannable, newStart);
10047             return true;
10048         }
10049 
10050         return false;
10051     }
10052 
10053     @Override
computeScroll()10054     public void computeScroll() {
10055         if (mScroller != null) {
10056             if (mScroller.computeScrollOffset()) {
10057                 mScrollX = mScroller.getCurrX();
10058                 mScrollY = mScroller.getCurrY();
10059                 invalidateParentCaches();
10060                 postInvalidate();  // So we draw again
10061             }
10062         }
10063     }
10064 
getInterestingRect(Rect r, int line)10065     private void getInterestingRect(Rect r, int line) {
10066         convertFromViewportToContentCoordinates(r);
10067 
10068         // Rectangle can can be expanded on first and last line to take
10069         // padding into account.
10070         // TODO Take left/right padding into account too?
10071         if (line == 0) r.top -= getExtendedPaddingTop();
10072         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
10073     }
10074 
convertFromViewportToContentCoordinates(Rect r)10075     private void convertFromViewportToContentCoordinates(Rect r) {
10076         final int horizontalOffset = viewportToContentHorizontalOffset();
10077         r.left += horizontalOffset;
10078         r.right += horizontalOffset;
10079 
10080         final int verticalOffset = viewportToContentVerticalOffset();
10081         r.top += verticalOffset;
10082         r.bottom += verticalOffset;
10083     }
10084 
viewportToContentHorizontalOffset()10085     int viewportToContentHorizontalOffset() {
10086         return getCompoundPaddingLeft() - mScrollX;
10087     }
10088 
10089     @UnsupportedAppUsage
viewportToContentVerticalOffset()10090     int viewportToContentVerticalOffset() {
10091         int offset = getExtendedPaddingTop() - mScrollY;
10092         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
10093             offset += getVerticalOffset(false);
10094         }
10095         return offset;
10096     }
10097 
10098     @Override
debug(int depth)10099     public void debug(int depth) {
10100         super.debug(depth);
10101 
10102         String output = debugIndent(depth);
10103         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
10104                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
10105                 + "} ";
10106 
10107         if (mText != null) {
10108 
10109             output += "mText=\"" + mText + "\" ";
10110             if (mLayout != null) {
10111                 output += "mLayout width=" + mLayout.getWidth()
10112                         + " height=" + mLayout.getHeight();
10113             }
10114         } else {
10115             output += "mText=NULL";
10116         }
10117         Log.d(VIEW_LOG_TAG, output);
10118     }
10119 
10120     /**
10121      * Convenience for {@link Selection#getSelectionStart}.
10122      */
10123     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()10124     public int getSelectionStart() {
10125         return Selection.getSelectionStart(getText());
10126     }
10127 
10128     /**
10129      * Convenience for {@link Selection#getSelectionEnd}.
10130      */
10131     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()10132     public int getSelectionEnd() {
10133         return Selection.getSelectionEnd(getText());
10134     }
10135 
10136     /**
10137      * Return true iff there is a selection of nonzero length inside this text view.
10138      */
hasSelection()10139     public boolean hasSelection() {
10140         final int selectionStart = getSelectionStart();
10141         final int selectionEnd = getSelectionEnd();
10142 
10143         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
10144     }
10145 
getSelectedText()10146     String getSelectedText() {
10147         if (!hasSelection()) {
10148             return null;
10149         }
10150 
10151         final int start = getSelectionStart();
10152         final int end = getSelectionEnd();
10153         return String.valueOf(
10154                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
10155     }
10156 
10157     /**
10158      * Sets the properties of this field (lines, horizontally scrolling,
10159      * transformation method) to be for a single-line input.
10160      *
10161      * @attr ref android.R.styleable#TextView_singleLine
10162      */
setSingleLine()10163     public void setSingleLine() {
10164         setSingleLine(true);
10165     }
10166 
10167     /**
10168      * Sets the properties of this field to transform input to ALL CAPS
10169      * display. This may use a "small caps" formatting if available.
10170      * This setting will be ignored if this field is editable or selectable.
10171      *
10172      * This call replaces the current transformation method. Disabling this
10173      * will not necessarily restore the previous behavior from before this
10174      * was enabled.
10175      *
10176      * @see #setTransformationMethod(TransformationMethod)
10177      * @attr ref android.R.styleable#TextView_textAllCaps
10178      */
setAllCaps(boolean allCaps)10179     public void setAllCaps(boolean allCaps) {
10180         if (allCaps) {
10181             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
10182         } else {
10183             setTransformationMethod(null);
10184         }
10185     }
10186 
10187     /**
10188      *
10189      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
10190      * @return Whether the current transformation method is for ALL CAPS.
10191      *
10192      * @see #setAllCaps(boolean)
10193      * @see #setTransformationMethod(TransformationMethod)
10194      */
10195     @InspectableProperty(name = "textAllCaps")
isAllCaps()10196     public boolean isAllCaps() {
10197         final TransformationMethod method = getTransformationMethod();
10198         return method != null && method instanceof AllCapsTransformationMethod;
10199     }
10200 
10201     /**
10202      * If true, sets the properties of this field (number of lines, horizontally scrolling,
10203      * transformation method) to be for a single-line input; if false, restores these to the default
10204      * conditions.
10205      *
10206      * Note that the default conditions are not necessarily those that were in effect prior this
10207      * method, and you may want to reset these properties to your custom values.
10208      *
10209      * @attr ref android.R.styleable#TextView_singleLine
10210      */
10211     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)10212     public void setSingleLine(boolean singleLine) {
10213         // Could be used, but may break backward compatibility.
10214         // if (mSingleLine == singleLine) return;
10215         setInputTypeSingleLine(singleLine);
10216         applySingleLine(singleLine, true, true);
10217     }
10218 
10219     /**
10220      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
10221      * @param singleLine
10222      */
setInputTypeSingleLine(boolean singleLine)10223     private void setInputTypeSingleLine(boolean singleLine) {
10224         if (mEditor != null
10225                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
10226                         == EditorInfo.TYPE_CLASS_TEXT) {
10227             if (singleLine) {
10228                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10229             } else {
10230                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10231             }
10232         }
10233     }
10234 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)10235     private void applySingleLine(boolean singleLine, boolean applyTransformation,
10236             boolean changeMaxLines) {
10237         mSingleLine = singleLine;
10238         if (singleLine) {
10239             setLines(1);
10240             setHorizontallyScrolling(true);
10241             if (applyTransformation) {
10242                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
10243             }
10244         } else {
10245             if (changeMaxLines) {
10246                 setMaxLines(Integer.MAX_VALUE);
10247             }
10248             setHorizontallyScrolling(false);
10249             if (applyTransformation) {
10250                 setTransformationMethod(null);
10251             }
10252         }
10253     }
10254 
10255     /**
10256      * Causes words in the text that are longer than the view's width
10257      * to be ellipsized instead of broken in the middle.  You may also
10258      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
10259      * to constrain the text to a single line.  Use <code>null</code>
10260      * to turn off ellipsizing.
10261      *
10262      * If {@link #setMaxLines} has been used to set two or more lines,
10263      * only {@link android.text.TextUtils.TruncateAt#END} and
10264      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
10265      * (other ellipsizing types will not do anything).
10266      *
10267      * @attr ref android.R.styleable#TextView_ellipsize
10268      */
setEllipsize(TextUtils.TruncateAt where)10269     public void setEllipsize(TextUtils.TruncateAt where) {
10270         // TruncateAt is an enum. != comparison is ok between these singleton objects.
10271         if (mEllipsize != where) {
10272             mEllipsize = where;
10273 
10274             if (mLayout != null) {
10275                 nullLayouts();
10276                 requestLayout();
10277                 invalidate();
10278             }
10279         }
10280     }
10281 
10282     /**
10283      * Sets how many times to repeat the marquee animation. Only applied if the
10284      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
10285      *
10286      * @see #getMarqueeRepeatLimit()
10287      *
10288      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10289      */
setMarqueeRepeatLimit(int marqueeLimit)10290     public void setMarqueeRepeatLimit(int marqueeLimit) {
10291         mMarqueeRepeatLimit = marqueeLimit;
10292     }
10293 
10294     /**
10295      * Gets the number of times the marquee animation is repeated. Only meaningful if the
10296      * TextView has marquee enabled.
10297      *
10298      * @return the number of times the marquee animation is repeated. -1 if the animation
10299      * repeats indefinitely
10300      *
10301      * @see #setMarqueeRepeatLimit(int)
10302      *
10303      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10304      */
10305     @InspectableProperty
getMarqueeRepeatLimit()10306     public int getMarqueeRepeatLimit() {
10307         return mMarqueeRepeatLimit;
10308     }
10309 
10310     /**
10311      * Returns where, if anywhere, words that are longer than the view
10312      * is wide should be ellipsized.
10313      */
10314     @InspectableProperty
10315     @ViewDebug.ExportedProperty
getEllipsize()10316     public TextUtils.TruncateAt getEllipsize() {
10317         return mEllipsize;
10318     }
10319 
10320     /**
10321      * Set the TextView so that when it takes focus, all the text is
10322      * selected.
10323      *
10324      * @attr ref android.R.styleable#TextView_selectAllOnFocus
10325      */
10326     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)10327     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
10328         createEditorIfNeeded();
10329         mEditor.mSelectAllOnFocus = selectAllOnFocus;
10330 
10331         if (selectAllOnFocus && !(mText instanceof Spannable)) {
10332             setText(mText, BufferType.SPANNABLE);
10333         }
10334     }
10335 
10336     /**
10337      * Set whether the cursor is visible. The default is true. Note that this property only
10338      * makes sense for editable TextView.
10339      *
10340      * @see #isCursorVisible()
10341      *
10342      * @attr ref android.R.styleable#TextView_cursorVisible
10343      */
10344     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)10345     public void setCursorVisible(boolean visible) {
10346         if (visible && mEditor == null) return; // visible is the default value with no edit data
10347         createEditorIfNeeded();
10348         if (mEditor.mCursorVisible != visible) {
10349             mEditor.mCursorVisible = visible;
10350             invalidate();
10351 
10352             mEditor.makeBlink();
10353 
10354             // InsertionPointCursorController depends on mCursorVisible
10355             mEditor.prepareCursorControllers();
10356         }
10357     }
10358 
10359     /**
10360      * @return whether or not the cursor is visible (assuming this TextView is editable)
10361      *
10362      * @see #setCursorVisible(boolean)
10363      *
10364      * @attr ref android.R.styleable#TextView_cursorVisible
10365      */
10366     @InspectableProperty
isCursorVisible()10367     public boolean isCursorVisible() {
10368         // true is the default value
10369         return mEditor == null ? true : mEditor.mCursorVisible;
10370     }
10371 
canMarquee()10372     private boolean canMarquee() {
10373         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10374         return width > 0 && (mLayout.getLineWidth(0) > width
10375                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
10376                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
10377     }
10378 
10379     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()10380     private void startMarquee() {
10381         // Do not ellipsize EditText
10382         if (getKeyListener() != null) return;
10383 
10384         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
10385             return;
10386         }
10387 
10388         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
10389                 && getLineCount() == 1 && canMarquee()) {
10390 
10391             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10392                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
10393                 final Layout tmp = mLayout;
10394                 mLayout = mSavedMarqueeModeLayout;
10395                 mSavedMarqueeModeLayout = tmp;
10396                 setHorizontalFadingEdgeEnabled(true);
10397                 requestLayout();
10398                 invalidate();
10399             }
10400 
10401             if (mMarquee == null) mMarquee = new Marquee(this);
10402             mMarquee.start(mMarqueeRepeatLimit);
10403         }
10404     }
10405 
stopMarquee()10406     private void stopMarquee() {
10407         if (mMarquee != null && !mMarquee.isStopped()) {
10408             mMarquee.stop();
10409         }
10410 
10411         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
10412             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10413             final Layout tmp = mSavedMarqueeModeLayout;
10414             mSavedMarqueeModeLayout = mLayout;
10415             mLayout = tmp;
10416             setHorizontalFadingEdgeEnabled(false);
10417             requestLayout();
10418             invalidate();
10419         }
10420     }
10421 
10422     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)10423     private void startStopMarquee(boolean start) {
10424         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10425             if (start) {
10426                 startMarquee();
10427             } else {
10428                 stopMarquee();
10429             }
10430         }
10431     }
10432 
10433     /**
10434      * This method is called when the text is changed, in case any subclasses
10435      * would like to know.
10436      *
10437      * Within <code>text</code>, the <code>lengthAfter</code> characters
10438      * beginning at <code>start</code> have just replaced old text that had
10439      * length <code>lengthBefore</code>. It is an error to attempt to make
10440      * changes to <code>text</code> from this callback.
10441      *
10442      * @param text The text the TextView is displaying
10443      * @param start The offset of the start of the range of the text that was
10444      * modified
10445      * @param lengthBefore The length of the former text that has been replaced
10446      * @param lengthAfter The length of the replacement modified text
10447      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10448     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
10449         // intentionally empty, template pattern method can be overridden by subclasses
10450     }
10451 
10452     /**
10453      * This method is called when the selection has changed, in case any
10454      * subclasses would like to know.
10455      *
10456      * @param selStart The new selection start location.
10457      * @param selEnd The new selection end location.
10458      */
onSelectionChanged(int selStart, int selEnd)10459     protected void onSelectionChanged(int selStart, int selEnd) {
10460         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
10461     }
10462 
10463     /**
10464      * Adds a TextWatcher to the list of those whose methods are called
10465      * whenever this TextView's text changes.
10466      * <p>
10467      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
10468      * not called after {@link #setText} calls.  Now, doing {@link #setText}
10469      * if there are any text changed listeners forces the buffer type to
10470      * Editable if it would not otherwise be and does call this method.
10471      */
addTextChangedListener(TextWatcher watcher)10472     public void addTextChangedListener(TextWatcher watcher) {
10473         if (mListeners == null) {
10474             mListeners = new ArrayList<TextWatcher>();
10475         }
10476 
10477         mListeners.add(watcher);
10478     }
10479 
10480     /**
10481      * Removes the specified TextWatcher from the list of those whose
10482      * methods are called
10483      * whenever this TextView's text changes.
10484      */
removeTextChangedListener(TextWatcher watcher)10485     public void removeTextChangedListener(TextWatcher watcher) {
10486         if (mListeners != null) {
10487             int i = mListeners.indexOf(watcher);
10488 
10489             if (i >= 0) {
10490                 mListeners.remove(i);
10491             }
10492         }
10493     }
10494 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)10495     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
10496         if (mListeners != null) {
10497             final ArrayList<TextWatcher> list = mListeners;
10498             final int count = list.size();
10499             for (int i = 0; i < count; i++) {
10500                 list.get(i).beforeTextChanged(text, start, before, after);
10501             }
10502         }
10503 
10504         // The spans that are inside or intersect the modified region no longer make sense
10505         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
10506         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
10507     }
10508 
10509     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10510     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
10511         if (!(mText instanceof Editable)) return;
10512         Editable text = (Editable) mText;
10513 
10514         T[] spans = text.getSpans(start, end, type);
10515         final int length = spans.length;
10516         for (int i = 0; i < length; i++) {
10517             final int spanStart = text.getSpanStart(spans[i]);
10518             final int spanEnd = text.getSpanEnd(spans[i]);
10519             if (spanEnd == start || spanStart == end) break;
10520             text.removeSpan(spans[i]);
10521         }
10522     }
10523 
removeAdjacentSuggestionSpans(final int pos)10524     void removeAdjacentSuggestionSpans(final int pos) {
10525         if (!(mText instanceof Editable)) return;
10526         final Editable text = (Editable) mText;
10527 
10528         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
10529         final int length = spans.length;
10530         for (int i = 0; i < length; i++) {
10531             final int spanStart = text.getSpanStart(spans[i]);
10532             final int spanEnd = text.getSpanEnd(spans[i]);
10533             if (spanEnd == pos || spanStart == pos) {
10534                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
10535                     text.removeSpan(spans[i]);
10536                 }
10537             }
10538         }
10539     }
10540 
10541     /**
10542      * Not private so it can be called from an inner class without going
10543      * through a thunk.
10544      */
sendOnTextChanged(CharSequence text, int start, int before, int after)10545     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
10546         if (mListeners != null) {
10547             final ArrayList<TextWatcher> list = mListeners;
10548             final int count = list.size();
10549             for (int i = 0; i < count; i++) {
10550                 list.get(i).onTextChanged(text, start, before, after);
10551             }
10552         }
10553 
10554         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
10555     }
10556 
10557     /**
10558      * Not private so it can be called from an inner class without going
10559      * through a thunk.
10560      */
sendAfterTextChanged(Editable text)10561     void sendAfterTextChanged(Editable text) {
10562         if (mListeners != null) {
10563             final ArrayList<TextWatcher> list = mListeners;
10564             final int count = list.size();
10565             for (int i = 0; i < count; i++) {
10566                 list.get(i).afterTextChanged(text);
10567             }
10568         }
10569 
10570         notifyListeningManagersAfterTextChanged();
10571 
10572         hideErrorIfUnchanged();
10573     }
10574 
10575     /**
10576      * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
10577      * interested on text changes.
10578      */
notifyListeningManagersAfterTextChanged()10579     private void notifyListeningManagersAfterTextChanged() {
10580 
10581         // Autofill
10582         if (isAutofillable()) {
10583             // It is important to not check whether the view is important for autofill
10584             // since the user can trigger autofill manually on not important views.
10585             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10586             if (afm != null) {
10587                 if (android.view.autofill.Helper.sVerbose) {
10588                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
10589                 }
10590                 afm.notifyValueChanged(TextView.this);
10591             }
10592         }
10593 
10594         // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
10595         // of using isLaidout(), so it's not called in cases where it's laid out but a
10596         // notifyAppeared was not sent.
10597 
10598         // ContentCapture
10599         if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) {
10600             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
10601             if (cm != null && cm.isContentCaptureEnabled()) {
10602                 final ContentCaptureSession session = getContentCaptureSession();
10603                 if (session != null) {
10604                     // TODO(b/111276913): pass flags when edited by user / add CTS test
10605                     session.notifyViewTextChanged(getAutofillId(), getText());
10606                 }
10607             }
10608         }
10609     }
10610 
isAutofillable()10611     private boolean isAutofillable() {
10612         // It is important to not check whether the view is important for autofill
10613         // since the user can trigger autofill manually on not important views.
10614         return getAutofillType() != AUTOFILL_TYPE_NONE;
10615     }
10616 
updateAfterEdit()10617     void updateAfterEdit() {
10618         invalidate();
10619         int curs = getSelectionStart();
10620 
10621         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
10622             registerForPreDraw();
10623         }
10624 
10625         checkForResize();
10626 
10627         if (curs >= 0) {
10628             mHighlightPathBogus = true;
10629             if (mEditor != null) mEditor.makeBlink();
10630             bringPointIntoView(curs);
10631         }
10632     }
10633 
10634     /**
10635      * Not private so it can be called from an inner class without going
10636      * through a thunk.
10637      */
handleTextChanged(CharSequence buffer, int start, int before, int after)10638     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
10639         sLastCutCopyOrTextChangedTime = 0;
10640 
10641         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10642         if (ims == null || ims.mBatchEditNesting == 0) {
10643             updateAfterEdit();
10644         }
10645         if (ims != null) {
10646             ims.mContentChanged = true;
10647             if (ims.mChangedStart < 0) {
10648                 ims.mChangedStart = start;
10649                 ims.mChangedEnd = start + before;
10650             } else {
10651                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
10652                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
10653             }
10654             ims.mChangedDelta += after - before;
10655         }
10656         resetErrorChangedFlag();
10657         sendOnTextChanged(buffer, start, before, after);
10658         onTextChanged(buffer, start, before, after);
10659     }
10660 
10661     /**
10662      * Not private so it can be called from an inner class without going
10663      * through a thunk.
10664      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10665     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
10666         // XXX Make the start and end move together if this ends up
10667         // spending too much time invalidating.
10668 
10669         boolean selChanged = false;
10670         int newSelStart = -1, newSelEnd = -1;
10671 
10672         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10673 
10674         if (what == Selection.SELECTION_END) {
10675             selChanged = true;
10676             newSelEnd = newStart;
10677 
10678             if (oldStart >= 0 || newStart >= 0) {
10679                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
10680                 checkForResize();
10681                 registerForPreDraw();
10682                 if (mEditor != null) mEditor.makeBlink();
10683             }
10684         }
10685 
10686         if (what == Selection.SELECTION_START) {
10687             selChanged = true;
10688             newSelStart = newStart;
10689 
10690             if (oldStart >= 0 || newStart >= 0) {
10691                 int end = Selection.getSelectionEnd(buf);
10692                 invalidateCursor(end, oldStart, newStart);
10693             }
10694         }
10695 
10696         if (selChanged) {
10697             mHighlightPathBogus = true;
10698             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
10699 
10700             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
10701                 if (newSelStart < 0) {
10702                     newSelStart = Selection.getSelectionStart(buf);
10703                 }
10704                 if (newSelEnd < 0) {
10705                     newSelEnd = Selection.getSelectionEnd(buf);
10706                 }
10707 
10708                 if (mEditor != null) {
10709                     mEditor.refreshTextActionMode();
10710                     if (!hasSelection()
10711                             && mEditor.getTextActionMode() == null && hasTransientState()) {
10712                         // User generated selection has been removed.
10713                         setHasTransientState(false);
10714                     }
10715                 }
10716                 onSelectionChanged(newSelStart, newSelEnd);
10717             }
10718         }
10719 
10720         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
10721                 || what instanceof CharacterStyle) {
10722             if (ims == null || ims.mBatchEditNesting == 0) {
10723                 invalidate();
10724                 mHighlightPathBogus = true;
10725                 checkForResize();
10726             } else {
10727                 ims.mContentChanged = true;
10728             }
10729             if (mEditor != null) {
10730                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
10731                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
10732                 mEditor.invalidateHandlesAndActionMode();
10733             }
10734         }
10735 
10736         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
10737             mHighlightPathBogus = true;
10738             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
10739                 ims.mSelectionModeChanged = true;
10740             }
10741 
10742             if (Selection.getSelectionStart(buf) >= 0) {
10743                 if (ims == null || ims.mBatchEditNesting == 0) {
10744                     invalidateCursor();
10745                 } else {
10746                     ims.mCursorChanged = true;
10747                 }
10748             }
10749         }
10750 
10751         if (what instanceof ParcelableSpan) {
10752             // If this is a span that can be sent to a remote process,
10753             // the current extract editor would be interested in it.
10754             if (ims != null && ims.mExtractedTextRequest != null) {
10755                 if (ims.mBatchEditNesting != 0) {
10756                     if (oldStart >= 0) {
10757                         if (ims.mChangedStart > oldStart) {
10758                             ims.mChangedStart = oldStart;
10759                         }
10760                         if (ims.mChangedStart > oldEnd) {
10761                             ims.mChangedStart = oldEnd;
10762                         }
10763                     }
10764                     if (newStart >= 0) {
10765                         if (ims.mChangedStart > newStart) {
10766                             ims.mChangedStart = newStart;
10767                         }
10768                         if (ims.mChangedStart > newEnd) {
10769                             ims.mChangedStart = newEnd;
10770                         }
10771                     }
10772                 } else {
10773                     if (DEBUG_EXTRACT) {
10774                         Log.v(LOG_TAG, "Span change outside of batch: "
10775                                 + oldStart + "-" + oldEnd + ","
10776                                 + newStart + "-" + newEnd + " " + what);
10777                     }
10778                     ims.mContentChanged = true;
10779                 }
10780             }
10781         }
10782 
10783         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
10784                 && what instanceof SpellCheckSpan) {
10785             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
10786         }
10787     }
10788 
10789     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10790     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
10791         if (isTemporarilyDetached()) {
10792             // If we are temporarily in the detach state, then do nothing.
10793             super.onFocusChanged(focused, direction, previouslyFocusedRect);
10794             return;
10795         }
10796 
10797         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
10798 
10799         if (focused) {
10800             if (mSpannable != null) {
10801                 MetaKeyKeyListener.resetMetaState(mSpannable);
10802             }
10803         }
10804 
10805         startStopMarquee(focused);
10806 
10807         if (mTransformation != null) {
10808             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
10809         }
10810 
10811         super.onFocusChanged(focused, direction, previouslyFocusedRect);
10812     }
10813 
10814     @Override
onWindowFocusChanged(boolean hasWindowFocus)10815     public void onWindowFocusChanged(boolean hasWindowFocus) {
10816         super.onWindowFocusChanged(hasWindowFocus);
10817 
10818         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
10819 
10820         startStopMarquee(hasWindowFocus);
10821     }
10822 
10823     @Override
onVisibilityChanged(View changedView, int visibility)10824     protected void onVisibilityChanged(View changedView, int visibility) {
10825         super.onVisibilityChanged(changedView, visibility);
10826         if (mEditor != null && visibility != VISIBLE) {
10827             mEditor.hideCursorAndSpanControllers();
10828             stopTextActionMode();
10829         }
10830     }
10831 
10832     /**
10833      * Use {@link BaseInputConnection#removeComposingSpans
10834      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
10835      * state from this text view.
10836      */
clearComposingText()10837     public void clearComposingText() {
10838         if (mText instanceof Spannable) {
10839             BaseInputConnection.removeComposingSpans(mSpannable);
10840         }
10841     }
10842 
10843     @Override
setSelected(boolean selected)10844     public void setSelected(boolean selected) {
10845         boolean wasSelected = isSelected();
10846 
10847         super.setSelected(selected);
10848 
10849         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10850             if (selected) {
10851                 startMarquee();
10852             } else {
10853                 stopMarquee();
10854             }
10855         }
10856     }
10857 
10858     @Override
onTouchEvent(MotionEvent event)10859     public boolean onTouchEvent(MotionEvent event) {
10860         final int action = event.getActionMasked();
10861         if (mEditor != null) {
10862             mEditor.onTouchEvent(event);
10863 
10864             if (mEditor.mSelectionModifierCursorController != null
10865                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
10866                 return true;
10867             }
10868         }
10869 
10870         final boolean superResult = super.onTouchEvent(event);
10871 
10872         /*
10873          * Don't handle the release after a long press, because it will move the selection away from
10874          * whatever the menu action was trying to affect. If the long press should have triggered an
10875          * insertion action mode, we can now actually show it.
10876          */
10877         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
10878             mEditor.mDiscardNextActionUp = false;
10879 
10880             if (mEditor.mIsInsertionActionModeStartPending) {
10881                 mEditor.startInsertionActionMode();
10882                 mEditor.mIsInsertionActionModeStartPending = false;
10883             }
10884             return superResult;
10885         }
10886 
10887         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
10888                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
10889 
10890         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
10891                 && mText instanceof Spannable && mLayout != null) {
10892             boolean handled = false;
10893 
10894             if (mMovement != null) {
10895                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
10896             }
10897 
10898             final boolean textIsSelectable = isTextSelectable();
10899             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
10900                 // The LinkMovementMethod which should handle taps on links has not been installed
10901                 // on non editable text that support text selection.
10902                 // We reproduce its behavior here to open links for these.
10903                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
10904                     getSelectionEnd(), ClickableSpan.class);
10905 
10906                 if (links.length > 0) {
10907                     links[0].onClick(this);
10908                     handled = true;
10909                 }
10910             }
10911 
10912             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
10913                 // Show the IME, except when selecting in read-only text.
10914                 final InputMethodManager imm = getInputMethodManager();
10915                 viewClicked(imm);
10916                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10917                     imm.showSoftInput(this, 0);
10918                 }
10919 
10920                 // The above condition ensures that the mEditor is not null
10921                 mEditor.onTouchUpEvent(event);
10922 
10923                 handled = true;
10924             }
10925 
10926             if (handled) {
10927                 return true;
10928             }
10929         }
10930 
10931         return superResult;
10932     }
10933 
10934     @Override
onGenericMotionEvent(MotionEvent event)10935     public boolean onGenericMotionEvent(MotionEvent event) {
10936         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
10937             try {
10938                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
10939                     return true;
10940                 }
10941             } catch (AbstractMethodError ex) {
10942                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
10943                 // Ignore its absence in case third party applications implemented the
10944                 // interface directly.
10945             }
10946         }
10947         return super.onGenericMotionEvent(event);
10948     }
10949 
10950     @Override
onCreateContextMenu(ContextMenu menu)10951     protected void onCreateContextMenu(ContextMenu menu) {
10952         if (mEditor != null) {
10953             mEditor.onCreateContextMenu(menu);
10954         }
10955     }
10956 
10957     @Override
showContextMenu()10958     public boolean showContextMenu() {
10959         if (mEditor != null) {
10960             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
10961         }
10962         return super.showContextMenu();
10963     }
10964 
10965     @Override
showContextMenu(float x, float y)10966     public boolean showContextMenu(float x, float y) {
10967         if (mEditor != null) {
10968             mEditor.setContextMenuAnchor(x, y);
10969         }
10970         return super.showContextMenu(x, y);
10971     }
10972 
10973     /**
10974      * @return True iff this TextView contains a text that can be edited, or if this is
10975      * a selectable TextView.
10976      */
10977     @UnsupportedAppUsage
isTextEditable()10978     boolean isTextEditable() {
10979         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
10980     }
10981 
10982     /**
10983      * Returns true, only while processing a touch gesture, if the initial
10984      * touch down event caused focus to move to the text view and as a result
10985      * its selection changed.  Only valid while processing the touch gesture
10986      * of interest, in an editable text view.
10987      */
didTouchFocusSelect()10988     public boolean didTouchFocusSelect() {
10989         return mEditor != null && mEditor.mTouchFocusSelected;
10990     }
10991 
10992     @Override
cancelLongPress()10993     public void cancelLongPress() {
10994         super.cancelLongPress();
10995         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
10996     }
10997 
10998     @Override
onTrackballEvent(MotionEvent event)10999     public boolean onTrackballEvent(MotionEvent event) {
11000         if (mMovement != null && mSpannable != null && mLayout != null) {
11001             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
11002                 return true;
11003             }
11004         }
11005 
11006         return super.onTrackballEvent(event);
11007     }
11008 
11009     /**
11010      * Sets the Scroller used for producing a scrolling animation
11011      *
11012      * @param s A Scroller instance
11013      */
setScroller(Scroller s)11014     public void setScroller(Scroller s) {
11015         mScroller = s;
11016     }
11017 
11018     @Override
getLeftFadingEdgeStrength()11019     protected float getLeftFadingEdgeStrength() {
11020         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11021             final Marquee marquee = mMarquee;
11022             if (marquee.shouldDrawLeftFade()) {
11023                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
11024             } else {
11025                 return 0.0f;
11026             }
11027         } else if (getLineCount() == 1) {
11028             final float lineLeft = getLayout().getLineLeft(0);
11029             if (lineLeft > mScrollX) return 0.0f;
11030             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
11031         }
11032         return super.getLeftFadingEdgeStrength();
11033     }
11034 
11035     @Override
getRightFadingEdgeStrength()11036     protected float getRightFadingEdgeStrength() {
11037         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11038             final Marquee marquee = mMarquee;
11039             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
11040         } else if (getLineCount() == 1) {
11041             final float rightEdge = mScrollX +
11042                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
11043             final float lineRight = getLayout().getLineRight(0);
11044             if (lineRight < rightEdge) return 0.0f;
11045             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
11046         }
11047         return super.getRightFadingEdgeStrength();
11048     }
11049 
11050     /**
11051      * Calculates the fading edge strength as the ratio of the distance between two
11052      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
11053      * value for the distance calculation.
11054      *
11055      * @param position1 A horizontal position.
11056      * @param position2 A horizontal position.
11057      * @return Fading edge strength between [0.0f, 1.0f].
11058      */
11059     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)11060     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
11061         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
11062         if (horizontalFadingEdgeLength == 0) return 0.0f;
11063         final float diff = Math.abs(position1 - position2);
11064         if (diff > horizontalFadingEdgeLength) return 1.0f;
11065         return diff / horizontalFadingEdgeLength;
11066     }
11067 
isMarqueeFadeEnabled()11068     private boolean isMarqueeFadeEnabled() {
11069         return mEllipsize == TextUtils.TruncateAt.MARQUEE
11070                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
11071     }
11072 
11073     @Override
computeHorizontalScrollRange()11074     protected int computeHorizontalScrollRange() {
11075         if (mLayout != null) {
11076             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
11077                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
11078         }
11079 
11080         return super.computeHorizontalScrollRange();
11081     }
11082 
11083     @Override
computeVerticalScrollRange()11084     protected int computeVerticalScrollRange() {
11085         if (mLayout != null) {
11086             return mLayout.getHeight();
11087         }
11088         return super.computeVerticalScrollRange();
11089     }
11090 
11091     @Override
computeVerticalScrollExtent()11092     protected int computeVerticalScrollExtent() {
11093         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
11094     }
11095 
11096     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11097     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
11098         super.findViewsWithText(outViews, searched, flags);
11099         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
11100                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
11101             String searchedLowerCase = searched.toString().toLowerCase();
11102             String textLowerCase = mText.toString().toLowerCase();
11103             if (textLowerCase.contains(searchedLowerCase)) {
11104                 outViews.add(this);
11105             }
11106         }
11107     }
11108 
11109     /**
11110      * Type of the text buffer that defines the characteristics of the text such as static,
11111      * styleable, or editable.
11112      */
11113     public enum BufferType {
11114         NORMAL, SPANNABLE, EDITABLE
11115     }
11116 
11117     /**
11118      * Returns the TextView_textColor attribute from the TypedArray, if set, or
11119      * the TextAppearance_textColor from the TextView_textAppearance attribute,
11120      * if TextView_textColor was not set directly.
11121      *
11122      * @removed
11123      */
getTextColors(Context context, TypedArray attrs)11124     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
11125         if (attrs == null) {
11126             // Preserve behavior prior to removal of this API.
11127             throw new NullPointerException();
11128         }
11129 
11130         // It's not safe to use this method from apps. The parameter 'attrs'
11131         // must have been obtained using the TextView filter array which is not
11132         // available to the SDK. As such, we grab a default TypedArray with the
11133         // right filter instead here.
11134         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
11135         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
11136         if (colors == null) {
11137             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
11138             if (ap != 0) {
11139                 final TypedArray appearance = context.obtainStyledAttributes(
11140                         ap, R.styleable.TextAppearance);
11141                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
11142                 appearance.recycle();
11143             }
11144         }
11145         a.recycle();
11146 
11147         return colors;
11148     }
11149 
11150     /**
11151      * Returns the default color from the TextView_textColor attribute from the
11152      * AttributeSet, if set, or the default color from the
11153      * TextAppearance_textColor from the TextView_textAppearance attribute, if
11154      * TextView_textColor was not set directly.
11155      *
11156      * @removed
11157      */
getTextColor(Context context, TypedArray attrs, int def)11158     public static int getTextColor(Context context, TypedArray attrs, int def) {
11159         final ColorStateList colors = getTextColors(context, attrs);
11160         if (colors == null) {
11161             return def;
11162         } else {
11163             return colors.getDefaultColor();
11164         }
11165     }
11166 
11167     @Override
onKeyShortcut(int keyCode, KeyEvent event)11168     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
11169         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
11170             // Handle Ctrl-only shortcuts.
11171             switch (keyCode) {
11172                 case KeyEvent.KEYCODE_A:
11173                     if (canSelectText()) {
11174                         return onTextContextMenuItem(ID_SELECT_ALL);
11175                     }
11176                     break;
11177                 case KeyEvent.KEYCODE_Z:
11178                     if (canUndo()) {
11179                         return onTextContextMenuItem(ID_UNDO);
11180                     }
11181                     break;
11182                 case KeyEvent.KEYCODE_X:
11183                     if (canCut()) {
11184                         return onTextContextMenuItem(ID_CUT);
11185                     }
11186                     break;
11187                 case KeyEvent.KEYCODE_C:
11188                     if (canCopy()) {
11189                         return onTextContextMenuItem(ID_COPY);
11190                     }
11191                     break;
11192                 case KeyEvent.KEYCODE_V:
11193                     if (canPaste()) {
11194                         return onTextContextMenuItem(ID_PASTE);
11195                     }
11196                     break;
11197             }
11198         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
11199             // Handle Ctrl-Shift shortcuts.
11200             switch (keyCode) {
11201                 case KeyEvent.KEYCODE_Z:
11202                     if (canRedo()) {
11203                         return onTextContextMenuItem(ID_REDO);
11204                     }
11205                     break;
11206                 case KeyEvent.KEYCODE_V:
11207                     if (canPaste()) {
11208                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
11209                     }
11210             }
11211         }
11212         return super.onKeyShortcut(keyCode, event);
11213     }
11214 
11215     /**
11216      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
11217      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
11218      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
11219      * sufficient.
11220      */
canSelectText()11221     boolean canSelectText() {
11222         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
11223     }
11224 
11225     /**
11226      * Test based on the <i>intrinsic</i> charateristics of the TextView.
11227      * The text must be spannable and the movement method must allow for arbitary selection.
11228      *
11229      * See also {@link #canSelectText()}.
11230      */
textCanBeSelected()11231     boolean textCanBeSelected() {
11232         // prepareCursorController() relies on this method.
11233         // If you change this condition, make sure prepareCursorController is called anywhere
11234         // the value of this condition might be changed.
11235         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
11236         return isTextEditable()
11237                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
11238     }
11239 
11240     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)11241     private Locale getTextServicesLocale(boolean allowNullLocale) {
11242         // Start fetching the text services locale asynchronously.
11243         updateTextServicesLocaleAsync();
11244         // If !allowNullLocale and there is no cached text services locale, just return the default
11245         // locale.
11246         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
11247                 : mCurrentSpellCheckerLocaleCache;
11248     }
11249 
11250     /**
11251      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
11252      * this {@link TextView}.
11253      *
11254      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
11255      * other apps may need to set this so that the system can user right user's resources and
11256      * services such as input methods and spell checkers.</p>
11257      *
11258      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
11259      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
11260      * @hide
11261      */
11262     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)11263     public final void setTextOperationUser(@Nullable UserHandle user) {
11264         if (Objects.equals(mTextOperationUser, user)) {
11265             return;
11266         }
11267         if (user != null && !Process.myUserHandle().equals(user)) {
11268             // Just for preventing people from accidentally using this hidden API without
11269             // the required permission.  The same permission is also checked in the system server.
11270             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
11271                     != PackageManager.PERMISSION_GRANTED) {
11272                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
11273                         + " userId=" + user.getIdentifier()
11274                         + " callingUserId" + UserHandle.myUserId());
11275             }
11276         }
11277         mTextOperationUser = user;
11278         // Invalidate some resources
11279         mCurrentSpellCheckerLocaleCache = null;
11280         if (mEditor != null) {
11281             mEditor.onTextOperationUserChanged();
11282         }
11283     }
11284 
11285     @Nullable
getTextServicesManagerForUser()11286     final TextServicesManager getTextServicesManagerForUser() {
11287         return getServiceManagerForUser("android", TextServicesManager.class);
11288     }
11289 
11290     @Nullable
getClipboardManagerForUser()11291     final ClipboardManager getClipboardManagerForUser() {
11292         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
11293     }
11294 
11295     @Nullable
getTextClassificationManagerForUser()11296     final TextClassificationManager getTextClassificationManagerForUser() {
11297         return getServiceManagerForUser(
11298                 getContext().getPackageName(), TextClassificationManager.class);
11299     }
11300 
11301     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)11302     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
11303         if (mTextOperationUser == null) {
11304             return getContext().getSystemService(managerClazz);
11305         }
11306         try {
11307             Context context = getContext().createPackageContextAsUser(
11308                     packageName, 0 /* flags */, mTextOperationUser);
11309             return context.getSystemService(managerClazz);
11310         } catch (PackageManager.NameNotFoundException e) {
11311             return null;
11312         }
11313     }
11314 
11315     /**
11316      * Starts {@link Activity} as a text-operation user if it is specified with
11317      * {@link #setTextOperationUser(UserHandle)}.
11318      *
11319      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
11320      *
11321      * @param intent The description of the activity to start.
11322      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11323     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
11324         if (mTextOperationUser != null) {
11325             getContext().startActivityAsUser(intent, mTextOperationUser);
11326         } else {
11327             getContext().startActivity(intent);
11328         }
11329     }
11330 
11331     /**
11332      * This is a temporary method. Future versions may support multi-locale text.
11333      * Caveat: This method may not return the latest text services locale, but this should be
11334      * acceptable and it's more important to make this method asynchronous.
11335      *
11336      * @return The locale that should be used for a word iterator
11337      * in this TextView, based on the current spell checker settings,
11338      * the current IME's locale, or the system default locale.
11339      * Please note that a word iterator in this TextView is different from another word iterator
11340      * used by SpellChecker.java of TextView. This method should be used for the former.
11341      * @hide
11342      */
11343     // TODO: Support multi-locale
11344     // TODO: Update the text services locale immediately after the keyboard locale is switched
11345     // by catching intent of keyboard switch event
getTextServicesLocale()11346     public Locale getTextServicesLocale() {
11347         return getTextServicesLocale(false /* allowNullLocale */);
11348     }
11349 
11350     /**
11351      * @return {@code true} if this TextView is specialized for showing and interacting with the
11352      * extracted text in a full-screen input method.
11353      * @hide
11354      */
isInExtractedMode()11355     public boolean isInExtractedMode() {
11356         return false;
11357     }
11358 
11359     /**
11360      * @return {@code true} if this widget supports auto-sizing text and has been configured to
11361      * auto-size.
11362      */
isAutoSizeEnabled()11363     private boolean isAutoSizeEnabled() {
11364         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
11365     }
11366 
11367     /**
11368      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
11369      * @hide
11370      */
supportsAutoSizeText()11371     protected boolean supportsAutoSizeText() {
11372         return true;
11373     }
11374 
11375     /**
11376      * This is a temporary method. Future versions may support multi-locale text.
11377      * Caveat: This method may not return the latest spell checker locale, but this should be
11378      * acceptable and it's more important to make this method asynchronous.
11379      *
11380      * @return The locale that should be used for a spell checker in this TextView,
11381      * based on the current spell checker settings, the current IME's locale, or the system default
11382      * locale.
11383      * @hide
11384      */
getSpellCheckerLocale()11385     public Locale getSpellCheckerLocale() {
11386         return getTextServicesLocale(true /* allowNullLocale */);
11387     }
11388 
updateTextServicesLocaleAsync()11389     private void updateTextServicesLocaleAsync() {
11390         // AsyncTask.execute() uses a serial executor which means we don't have
11391         // to lock around updateTextServicesLocaleLocked() to prevent it from
11392         // being executed n times in parallel.
11393         AsyncTask.execute(new Runnable() {
11394             @Override
11395             public void run() {
11396                 updateTextServicesLocaleLocked();
11397             }
11398         });
11399     }
11400 
11401     @UnsupportedAppUsage
updateTextServicesLocaleLocked()11402     private void updateTextServicesLocaleLocked() {
11403         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
11404         if (textServicesManager == null) {
11405             return;
11406         }
11407         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
11408         final Locale locale;
11409         if (subtype != null) {
11410             locale = subtype.getLocaleObject();
11411         } else {
11412             locale = null;
11413         }
11414         mCurrentSpellCheckerLocaleCache = locale;
11415     }
11416 
onLocaleChanged()11417     void onLocaleChanged() {
11418         mEditor.onLocaleChanged();
11419     }
11420 
11421     /**
11422      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
11423      * Made available to achieve a consistent behavior.
11424      * @hide
11425      */
getWordIterator()11426     public WordIterator getWordIterator() {
11427         if (mEditor != null) {
11428             return mEditor.getWordIterator();
11429         } else {
11430             return null;
11431         }
11432     }
11433 
11434     /** @hide */
11435     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)11436     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
11437         super.onPopulateAccessibilityEventInternal(event);
11438 
11439         final CharSequence text = getTextForAccessibility();
11440         if (!TextUtils.isEmpty(text)) {
11441             event.getText().add(text);
11442         }
11443     }
11444 
11445     @Override
getAccessibilityClassName()11446     public CharSequence getAccessibilityClassName() {
11447         return TextView.class.getName();
11448     }
11449 
11450     /** @hide */
11451     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11452     protected void onProvideStructure(@NonNull ViewStructure structure,
11453             @ViewStructureType int viewFor, int flags) {
11454         super.onProvideStructure(structure, viewFor, flags);
11455 
11456         final boolean isPassword = hasPasswordTransformationMethod()
11457                 || isPasswordInputType(getInputType());
11458         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11459                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11460             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11461                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
11462             }
11463             if (mTextId != Resources.ID_NULL) {
11464                 try {
11465                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
11466                 } catch (Resources.NotFoundException e) {
11467                     if (android.view.autofill.Helper.sVerbose) {
11468                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
11469                                 + mTextId + ": " + e.getMessage());
11470                     }
11471                 }
11472             }
11473         }
11474 
11475         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11476                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11477             if (mLayout == null) {
11478                 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11479                     Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
11480                 }
11481                 assumeLayout();
11482             }
11483             Layout layout = mLayout;
11484             final int lineCount = layout.getLineCount();
11485             if (lineCount <= 1) {
11486                 // Simple case: this is a single line.
11487                 final CharSequence text = getText();
11488                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11489                     structure.setText(text);
11490                 } else {
11491                     structure.setText(text, getSelectionStart(), getSelectionEnd());
11492                 }
11493             } else {
11494                 // Complex case: multi-line, could be scrolled or within a scroll container
11495                 // so some lines are not visible.
11496                 final int[] tmpCords = new int[2];
11497                 getLocationInWindow(tmpCords);
11498                 final int topWindowLocation = tmpCords[1];
11499                 View root = this;
11500                 ViewParent viewParent = getParent();
11501                 while (viewParent instanceof View) {
11502                     root = (View) viewParent;
11503                     viewParent = root.getParent();
11504                 }
11505                 final int windowHeight = root.getHeight();
11506                 final int topLine;
11507                 final int bottomLine;
11508                 if (topWindowLocation >= 0) {
11509                     // The top of the view is fully within its window; start text at line 0.
11510                     topLine = getLineAtCoordinateUnclamped(0);
11511                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
11512                 } else {
11513                     // The top of hte window has scrolled off the top of the window; figure out
11514                     // the starting line for this.
11515                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
11516                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
11517                 }
11518                 // We want to return some contextual lines above/below the lines that are
11519                 // actually visible.
11520                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
11521                 if (expandedTopLine < 0) {
11522                     expandedTopLine = 0;
11523                 }
11524                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
11525                 if (expandedBottomLine >= lineCount) {
11526                     expandedBottomLine = lineCount - 1;
11527                 }
11528 
11529                 // Convert lines into character offsets.
11530                 int expandedTopChar = layout.getLineStart(expandedTopLine);
11531                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
11532 
11533                 // Take into account selection -- if there is a selection, we need to expand
11534                 // the text we are returning to include that selection.
11535                 final int selStart = getSelectionStart();
11536                 final int selEnd = getSelectionEnd();
11537                 if (selStart < selEnd) {
11538                     if (selStart < expandedTopChar) {
11539                         expandedTopChar = selStart;
11540                     }
11541                     if (selEnd > expandedBottomChar) {
11542                         expandedBottomChar = selEnd;
11543                     }
11544                 }
11545 
11546                 // Get the text and trim it to the range we are reporting.
11547                 CharSequence text = getText();
11548                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
11549                     text = text.subSequence(expandedTopChar, expandedBottomChar);
11550                 }
11551 
11552                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11553                     structure.setText(text);
11554                 } else {
11555                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
11556 
11557                     final int[] lineOffsets = new int[bottomLine - topLine + 1];
11558                     final int[] lineBaselines = new int[bottomLine - topLine + 1];
11559                     final int baselineOffset = getBaselineOffset();
11560                     for (int i = topLine; i <= bottomLine; i++) {
11561                         lineOffsets[i - topLine] = layout.getLineStart(i);
11562                         lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
11563                     }
11564                     structure.setTextLines(lineOffsets, lineBaselines);
11565                 }
11566             }
11567 
11568             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
11569                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11570                 // Extract style information that applies to the TextView as a whole.
11571                 int style = 0;
11572                 int typefaceStyle = getTypefaceStyle();
11573                 if ((typefaceStyle & Typeface.BOLD) != 0) {
11574                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11575                 }
11576                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
11577                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
11578                 }
11579 
11580                 // Global styles can also be set via TextView.setPaintFlags().
11581                 int paintFlags = mTextPaint.getFlags();
11582                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
11583                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11584                 }
11585                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
11586                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
11587                 }
11588                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
11589                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
11590                 }
11591 
11592                 // TextView does not have its own text background color. A background is either part
11593                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
11594                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
11595                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
11596             }
11597             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11598                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11599                 structure.setMinTextEms(getMinEms());
11600                 structure.setMaxTextEms(getMaxEms());
11601                 int maxLength = -1;
11602                 for (InputFilter filter: getFilters()) {
11603                     if (filter instanceof InputFilter.LengthFilter) {
11604                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
11605                         break;
11606                     }
11607                 }
11608                 structure.setMaxTextLength(maxLength);
11609             }
11610         }
11611         structure.setHint(getHint());
11612         structure.setInputType(getInputType());
11613     }
11614 
canRequestAutofill()11615     boolean canRequestAutofill() {
11616         if (!isAutofillable()) {
11617             return false;
11618         }
11619         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11620         if (afm != null) {
11621             return afm.isEnabled();
11622         }
11623         return false;
11624     }
11625 
requestAutofill()11626     private void requestAutofill() {
11627         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11628         if (afm != null) {
11629             afm.requestAutofill(this);
11630         }
11631     }
11632 
11633     @Override
autofill(AutofillValue value)11634     public void autofill(AutofillValue value) {
11635         if (!value.isText() || !isTextEditable()) {
11636             Log.w(LOG_TAG, value + " could not be autofilled into " + this);
11637             return;
11638         }
11639 
11640         final CharSequence autofilledValue = value.getTextValue();
11641 
11642         // First autofill it...
11643         setText(autofilledValue, mBufferType, true, 0);
11644 
11645         // ...then move cursor to the end.
11646         final CharSequence text = getText();
11647         if ((text instanceof Spannable)) {
11648             Selection.setSelection((Spannable) text, text.length());
11649         }
11650     }
11651 
11652     @Override
getAutofillType()11653     public @AutofillType int getAutofillType() {
11654         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
11655     }
11656 
11657     /**
11658      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
11659      * {@code char}s if longer.
11660      *
11661      * @return current text, {@code null} if the text is not editable
11662      *
11663      * @see View#getAutofillValue()
11664      */
11665     @Override
11666     @Nullable
getAutofillValue()11667     public AutofillValue getAutofillValue() {
11668         if (isTextEditable()) {
11669             final CharSequence text = TextUtils.trimToParcelableSize(getText());
11670             return AutofillValue.forText(text);
11671         }
11672         return null;
11673     }
11674 
11675     /** @hide */
11676     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)11677     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
11678         super.onInitializeAccessibilityEventInternal(event);
11679 
11680         final boolean isPassword = hasPasswordTransformationMethod();
11681         event.setPassword(isPassword);
11682 
11683         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
11684             event.setFromIndex(Selection.getSelectionStart(mText));
11685             event.setToIndex(Selection.getSelectionEnd(mText));
11686             event.setItemCount(mText.length());
11687         }
11688     }
11689 
11690     /** @hide */
11691     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11692     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
11693         super.onInitializeAccessibilityNodeInfoInternal(info);
11694 
11695         final boolean isPassword = hasPasswordTransformationMethod();
11696         info.setPassword(isPassword);
11697         info.setText(getTextForAccessibility());
11698         info.setHintText(mHint);
11699         info.setShowingHintText(isShowingHint());
11700 
11701         if (mBufferType == BufferType.EDITABLE) {
11702             info.setEditable(true);
11703             if (isEnabled()) {
11704                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
11705             }
11706         }
11707 
11708         if (mEditor != null) {
11709             info.setInputType(mEditor.mInputType);
11710 
11711             if (mEditor.mError != null) {
11712                 info.setContentInvalid(true);
11713                 info.setError(mEditor.mError);
11714             }
11715         }
11716 
11717         if (!TextUtils.isEmpty(mText)) {
11718             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
11719             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
11720             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
11721                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
11722                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
11723                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
11724                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
11725             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
11726             info.setAvailableExtraData(
11727                     Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
11728         }
11729 
11730         if (isFocused()) {
11731             if (canCopy()) {
11732                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
11733             }
11734             if (canPaste()) {
11735                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
11736             }
11737             if (canCut()) {
11738                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
11739             }
11740             if (canShare()) {
11741                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
11742                         ACCESSIBILITY_ACTION_SHARE,
11743                         getResources().getString(com.android.internal.R.string.share)));
11744             }
11745             if (canProcessText()) {  // also implies mEditor is not null.
11746                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
11747             }
11748         }
11749 
11750         // Check for known input filter types.
11751         final int numFilters = mFilters.length;
11752         for (int i = 0; i < numFilters; i++) {
11753             final InputFilter filter = mFilters[i];
11754             if (filter instanceof InputFilter.LengthFilter) {
11755                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
11756             }
11757         }
11758 
11759         if (!isSingleLine()) {
11760             info.setMultiLine(true);
11761         }
11762     }
11763 
11764     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11765     public void addExtraDataToAccessibilityNodeInfo(
11766             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
11767         // The only extra data we support requires arguments.
11768         if (arguments == null) {
11769             return;
11770         }
11771         if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
11772             int positionInfoStartIndex = arguments.getInt(
11773                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
11774             int positionInfoLength = arguments.getInt(
11775                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
11776             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
11777                     || (positionInfoStartIndex >= mText.length())) {
11778                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
11779                 return;
11780             }
11781             RectF[] boundingRects = new RectF[positionInfoLength];
11782             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
11783             populateCharacterBounds(builder, positionInfoStartIndex,
11784                     positionInfoStartIndex + positionInfoLength,
11785                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
11786             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
11787             for (int i = 0; i < positionInfoLength; i++) {
11788                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
11789                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
11790                     RectF bounds = cursorAnchorInfo
11791                             .getCharacterBounds(positionInfoStartIndex + i);
11792                     if (bounds != null) {
11793                         mapRectFromViewToScreenCoords(bounds, true);
11794                         boundingRects[i] = bounds;
11795                     }
11796                 }
11797             }
11798             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
11799         }
11800     }
11801 
11802     /**
11803      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
11804      *
11805      * @param builder The builder to populate
11806      * @param startIndex The starting character index to populate
11807      * @param endIndex The ending character index to populate
11808      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
11809      * content
11810      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
11811      * @hide
11812      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)11813     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
11814             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
11815             float viewportToContentVerticalOffset) {
11816         final int minLine = mLayout.getLineForOffset(startIndex);
11817         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
11818         for (int line = minLine; line <= maxLine; ++line) {
11819             final int lineStart = mLayout.getLineStart(line);
11820             final int lineEnd = mLayout.getLineEnd(line);
11821             final int offsetStart = Math.max(lineStart, startIndex);
11822             final int offsetEnd = Math.min(lineEnd, endIndex);
11823             final boolean ltrLine =
11824                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
11825             final float[] widths = new float[offsetEnd - offsetStart];
11826             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
11827             final float top = mLayout.getLineTop(line);
11828             final float bottom = mLayout.getLineBottom(line);
11829             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
11830                 final float charWidth = widths[offset - offsetStart];
11831                 final boolean isRtl = mLayout.isRtlCharAt(offset);
11832                 final float primary = mLayout.getPrimaryHorizontal(offset);
11833                 final float secondary = mLayout.getSecondaryHorizontal(offset);
11834                 // TODO: This doesn't work perfectly for text with custom styles and
11835                 // TAB chars.
11836                 final float left;
11837                 final float right;
11838                 if (ltrLine) {
11839                     if (isRtl) {
11840                         left = secondary - charWidth;
11841                         right = secondary;
11842                     } else {
11843                         left = primary;
11844                         right = primary + charWidth;
11845                     }
11846                 } else {
11847                     if (!isRtl) {
11848                         left = secondary;
11849                         right = secondary + charWidth;
11850                     } else {
11851                         left = primary - charWidth;
11852                         right = primary;
11853                     }
11854                 }
11855                 // TODO: Check top-right and bottom-left as well.
11856                 final float localLeft = left + viewportToContentHorizontalOffset;
11857                 final float localRight = right + viewportToContentHorizontalOffset;
11858                 final float localTop = top + viewportToContentVerticalOffset;
11859                 final float localBottom = bottom + viewportToContentVerticalOffset;
11860                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
11861                 final boolean isBottomRightVisible =
11862                         isPositionVisible(localRight, localBottom);
11863                 int characterBoundsFlags = 0;
11864                 if (isTopLeftVisible || isBottomRightVisible) {
11865                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
11866                 }
11867                 if (!isTopLeftVisible || !isBottomRightVisible) {
11868                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
11869                 }
11870                 if (isRtl) {
11871                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
11872                 }
11873                 // Here offset is the index in Java chars.
11874                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
11875                         localBottom, characterBoundsFlags);
11876             }
11877         }
11878     }
11879 
11880     /**
11881      * @hide
11882      */
isPositionVisible(final float positionX, final float positionY)11883     public boolean isPositionVisible(final float positionX, final float positionY) {
11884         synchronized (TEMP_POSITION) {
11885             final float[] position = TEMP_POSITION;
11886             position[0] = positionX;
11887             position[1] = positionY;
11888             View view = this;
11889 
11890             while (view != null) {
11891                 if (view != this) {
11892                     // Local scroll is already taken into account in positionX/Y
11893                     position[0] -= view.getScrollX();
11894                     position[1] -= view.getScrollY();
11895                 }
11896 
11897                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
11898                         || position[1] > view.getHeight()) {
11899                     return false;
11900                 }
11901 
11902                 if (!view.getMatrix().isIdentity()) {
11903                     view.getMatrix().mapPoints(position);
11904                 }
11905 
11906                 position[0] += view.getLeft();
11907                 position[1] += view.getTop();
11908 
11909                 final ViewParent parent = view.getParent();
11910                 if (parent instanceof View) {
11911                     view = (View) parent;
11912                 } else {
11913                     // We've reached the ViewRoot, stop iterating
11914                     view = null;
11915                 }
11916             }
11917         }
11918 
11919         // We've been able to walk up the view hierarchy and the position was never clipped
11920         return true;
11921     }
11922 
11923     /**
11924      * Performs an accessibility action after it has been offered to the
11925      * delegate.
11926      *
11927      * @hide
11928      */
11929     @Override
performAccessibilityActionInternal(int action, Bundle arguments)11930     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
11931         if (mEditor != null
11932                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
11933             return true;
11934         }
11935         switch (action) {
11936             case AccessibilityNodeInfo.ACTION_CLICK: {
11937                 return performAccessibilityActionClick(arguments);
11938             }
11939             case AccessibilityNodeInfo.ACTION_COPY: {
11940                 if (isFocused() && canCopy()) {
11941                     if (onTextContextMenuItem(ID_COPY)) {
11942                         return true;
11943                     }
11944                 }
11945             } return false;
11946             case AccessibilityNodeInfo.ACTION_PASTE: {
11947                 if (isFocused() && canPaste()) {
11948                     if (onTextContextMenuItem(ID_PASTE)) {
11949                         return true;
11950                     }
11951                 }
11952             } return false;
11953             case AccessibilityNodeInfo.ACTION_CUT: {
11954                 if (isFocused() && canCut()) {
11955                     if (onTextContextMenuItem(ID_CUT)) {
11956                         return true;
11957                     }
11958                 }
11959             } return false;
11960             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
11961                 ensureIterableTextForAccessibilitySelectable();
11962                 CharSequence text = getIterableTextForAccessibility();
11963                 if (text == null) {
11964                     return false;
11965                 }
11966                 final int start = (arguments != null) ? arguments.getInt(
11967                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
11968                 final int end = (arguments != null) ? arguments.getInt(
11969                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
11970                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
11971                     // No arguments clears the selection.
11972                     if (start == end && end == -1) {
11973                         Selection.removeSelection((Spannable) text);
11974                         return true;
11975                     }
11976                     if (start >= 0 && start <= end && end <= text.length()) {
11977                         Selection.setSelection((Spannable) text, start, end);
11978                         // Make sure selection mode is engaged.
11979                         if (mEditor != null) {
11980                             mEditor.startSelectionActionModeAsync(false);
11981                         }
11982                         return true;
11983                     }
11984                 }
11985             } return false;
11986             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
11987             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
11988                 ensureIterableTextForAccessibilitySelectable();
11989                 return super.performAccessibilityActionInternal(action, arguments);
11990             }
11991             case ACCESSIBILITY_ACTION_SHARE: {
11992                 if (isFocused() && canShare()) {
11993                     if (onTextContextMenuItem(ID_SHARE)) {
11994                         return true;
11995                     }
11996                 }
11997             } return false;
11998             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
11999                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
12000                     return false;
12001                 }
12002                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
12003                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
12004                 setText(text);
12005                 if (mText != null) {
12006                     int updatedTextLength = mText.length();
12007                     if (updatedTextLength > 0) {
12008                         Selection.setSelection(mSpannable, updatedTextLength);
12009                     }
12010                 }
12011             } return true;
12012             default: {
12013                 return super.performAccessibilityActionInternal(action, arguments);
12014             }
12015         }
12016     }
12017 
performAccessibilityActionClick(Bundle arguments)12018     private boolean performAccessibilityActionClick(Bundle arguments) {
12019         boolean handled = false;
12020 
12021         if (!isEnabled()) {
12022             return false;
12023         }
12024 
12025         if (isClickable() || isLongClickable()) {
12026             // Simulate View.onTouchEvent for an ACTION_UP event
12027             if (isFocusable() && !isFocused()) {
12028                 requestFocus();
12029             }
12030 
12031             performClick();
12032             handled = true;
12033         }
12034 
12035         // Show the IME, except when selecting in read-only text.
12036         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
12037                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
12038             final InputMethodManager imm = getInputMethodManager();
12039             viewClicked(imm);
12040             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
12041                 handled |= imm.showSoftInput(this, 0);
12042             }
12043         }
12044 
12045         return handled;
12046     }
12047 
hasSpannableText()12048     private boolean hasSpannableText() {
12049         return mText != null && mText instanceof Spannable;
12050     }
12051 
12052     /** @hide */
12053     @Override
sendAccessibilityEventInternal(int eventType)12054     public void sendAccessibilityEventInternal(int eventType) {
12055         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
12056             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
12057         }
12058 
12059         super.sendAccessibilityEventInternal(eventType);
12060     }
12061 
12062     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)12063     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
12064         // Do not send scroll events since first they are not interesting for
12065         // accessibility and second such events a generated too frequently.
12066         // For details see the implementation of bringTextIntoView().
12067         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
12068             return;
12069         }
12070         super.sendAccessibilityEventUnchecked(event);
12071     }
12072 
12073     /**
12074      * Returns the text that should be exposed to accessibility services.
12075      * <p>
12076      * This approximates what is displayed visually. If the user has specified
12077      * that accessibility services should speak passwords, this method will
12078      * bypass any password transformation method and return unobscured text.
12079      *
12080      * @return the text that should be exposed to accessibility services, may
12081      *         be {@code null} if no text is set
12082      */
12083     @Nullable
12084     @UnsupportedAppUsage
getTextForAccessibility()12085     private CharSequence getTextForAccessibility() {
12086         // If the text is empty, we must be showing the hint text.
12087         if (TextUtils.isEmpty(mText)) {
12088             return mHint;
12089         }
12090 
12091         // Otherwise, return whatever text is being displayed.
12092         return TextUtils.trimToParcelableSize(mTransformed);
12093     }
12094 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12095     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
12096             int fromIndex, int removedCount, int addedCount) {
12097         AccessibilityEvent event =
12098                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
12099         event.setFromIndex(fromIndex);
12100         event.setRemovedCount(removedCount);
12101         event.setAddedCount(addedCount);
12102         event.setBeforeText(beforeText);
12103         sendAccessibilityEventUnchecked(event);
12104     }
12105 
getInputMethodManager()12106     private InputMethodManager getInputMethodManager() {
12107         return getContext().getSystemService(InputMethodManager.class);
12108     }
12109 
12110     /**
12111      * Returns whether this text view is a current input method target.  The
12112      * default implementation just checks with {@link InputMethodManager}.
12113      * @return True if the TextView is a current input method target; false otherwise.
12114      */
isInputMethodTarget()12115     public boolean isInputMethodTarget() {
12116         InputMethodManager imm = getInputMethodManager();
12117         return imm != null && imm.isActive(this);
12118     }
12119 
12120     static final int ID_SELECT_ALL = android.R.id.selectAll;
12121     static final int ID_UNDO = android.R.id.undo;
12122     static final int ID_REDO = android.R.id.redo;
12123     static final int ID_CUT = android.R.id.cut;
12124     static final int ID_COPY = android.R.id.copy;
12125     static final int ID_PASTE = android.R.id.paste;
12126     static final int ID_SHARE = android.R.id.shareText;
12127     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
12128     static final int ID_REPLACE = android.R.id.replaceText;
12129     static final int ID_ASSIST = android.R.id.textAssist;
12130     static final int ID_AUTOFILL = android.R.id.autofill;
12131 
12132     /**
12133      * Called when a context menu option for the text view is selected.  Currently
12134      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
12135      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
12136      *
12137      * @return true if the context menu item action was performed.
12138      */
onTextContextMenuItem(int id)12139     public boolean onTextContextMenuItem(int id) {
12140         int min = 0;
12141         int max = mText.length();
12142 
12143         if (isFocused()) {
12144             final int selStart = getSelectionStart();
12145             final int selEnd = getSelectionEnd();
12146 
12147             min = Math.max(0, Math.min(selStart, selEnd));
12148             max = Math.max(0, Math.max(selStart, selEnd));
12149         }
12150 
12151         switch (id) {
12152             case ID_SELECT_ALL:
12153                 final boolean hadSelection = hasSelection();
12154                 selectAllText();
12155                 if (mEditor != null && hadSelection) {
12156                     mEditor.invalidateActionModeAsync();
12157                 }
12158                 return true;
12159 
12160             case ID_UNDO:
12161                 if (mEditor != null) {
12162                     mEditor.undo();
12163                 }
12164                 return true;  // Returns true even if nothing was undone.
12165 
12166             case ID_REDO:
12167                 if (mEditor != null) {
12168                     mEditor.redo();
12169                 }
12170                 return true;  // Returns true even if nothing was undone.
12171 
12172             case ID_PASTE:
12173                 paste(min, max, true /* withFormatting */);
12174                 return true;
12175 
12176             case ID_PASTE_AS_PLAIN_TEXT:
12177                 paste(min, max, false /* withFormatting */);
12178                 return true;
12179 
12180             case ID_CUT:
12181                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
12182                 if (setPrimaryClip(cutData)) {
12183                     deleteText_internal(min, max);
12184                 } else {
12185                     Toast.makeText(getContext(),
12186                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12187                             Toast.LENGTH_SHORT).show();
12188                 }
12189                 return true;
12190 
12191             case ID_COPY:
12192                 // For link action mode in a non-selectable/non-focusable TextView,
12193                 // make sure that we set the appropriate min/max.
12194                 final int selStart = getSelectionStart();
12195                 final int selEnd = getSelectionEnd();
12196                 min = Math.max(0, Math.min(selStart, selEnd));
12197                 max = Math.max(0, Math.max(selStart, selEnd));
12198                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
12199                 if (setPrimaryClip(copyData)) {
12200                     stopTextActionMode();
12201                 } else {
12202                     Toast.makeText(getContext(),
12203                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12204                             Toast.LENGTH_SHORT).show();
12205                 }
12206                 return true;
12207 
12208             case ID_REPLACE:
12209                 if (mEditor != null) {
12210                     mEditor.replace();
12211                 }
12212                 return true;
12213 
12214             case ID_SHARE:
12215                 shareSelectedText();
12216                 return true;
12217 
12218             case ID_AUTOFILL:
12219                 requestAutofill();
12220                 stopTextActionMode();
12221                 return true;
12222         }
12223         return false;
12224     }
12225 
12226     @UnsupportedAppUsage
getTransformedText(int start, int end)12227     CharSequence getTransformedText(int start, int end) {
12228         return removeSuggestionSpans(mTransformed.subSequence(start, end));
12229     }
12230 
12231     @Override
performLongClick()12232     public boolean performLongClick() {
12233         boolean handled = false;
12234         boolean performedHapticFeedback = false;
12235 
12236         if (mEditor != null) {
12237             mEditor.mIsBeingLongClicked = true;
12238         }
12239 
12240         if (super.performLongClick()) {
12241             handled = true;
12242             performedHapticFeedback = true;
12243         }
12244 
12245         if (mEditor != null) {
12246             handled |= mEditor.performLongClick(handled);
12247             mEditor.mIsBeingLongClicked = false;
12248         }
12249 
12250         if (handled) {
12251             if (!performedHapticFeedback) {
12252               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
12253             }
12254             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
12255         } else {
12256             MetricsLogger.action(
12257                     mContext,
12258                     MetricsEvent.TEXT_LONGPRESS,
12259                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
12260         }
12261 
12262         return handled;
12263     }
12264 
12265     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12266     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
12267         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
12268         if (mEditor != null) {
12269             mEditor.onScrollChanged();
12270         }
12271     }
12272 
12273     /**
12274      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
12275      * by the IME or by the spell checker as the user types. This is done by adding
12276      * {@link SuggestionSpan}s to the text.
12277      *
12278      * When suggestions are enabled (default), this list of suggestions will be displayed when the
12279      * user asks for them on these parts of the text. This value depends on the inputType of this
12280      * TextView.
12281      *
12282      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
12283      *
12284      * In addition, the type variation must be one of
12285      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
12286      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
12287      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
12288      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
12289      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
12290      *
12291      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
12292      *
12293      * @return true if the suggestions popup window is enabled, based on the inputType.
12294      */
isSuggestionsEnabled()12295     public boolean isSuggestionsEnabled() {
12296         if (mEditor == null) return false;
12297         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
12298             return false;
12299         }
12300         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
12301 
12302         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
12303         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
12304                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
12305                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
12306                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
12307                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
12308     }
12309 
12310     /**
12311      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12312      * selection is initiated in this View.
12313      *
12314      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
12315      * Paste, Replace and Share actions, depending on what this View supports.
12316      *
12317      * <p>A custom implementation can add new entries in the default menu in its
12318      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
12319      * method. The default actions can also be removed from the menu using
12320      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12321      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
12322      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
12323      *
12324      * <p>Returning false from
12325      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
12326      * will prevent the action mode from being started.
12327      *
12328      * <p>Action click events should be handled by the custom implementation of
12329      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
12330      * android.view.MenuItem)}.
12331      *
12332      * <p>Note that text selection mode is not started when a TextView receives focus and the
12333      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
12334      * that case, to allow for quick replacement.
12335      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12336     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
12337         createEditorIfNeeded();
12338         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
12339     }
12340 
12341     /**
12342      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
12343      *
12344      * @return The current custom selection callback.
12345      */
getCustomSelectionActionModeCallback()12346     public ActionMode.Callback getCustomSelectionActionModeCallback() {
12347         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
12348     }
12349 
12350     /**
12351      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12352      * insertion is initiated in this View.
12353      * The standard implementation populates the menu with a subset of Select All,
12354      * Paste and Replace actions, depending on what this View supports.
12355      *
12356      * <p>A custom implementation can add new entries in the default menu in its
12357      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
12358      * android.view.Menu)} method. The default actions can also be removed from the menu using
12359      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12360      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
12361      *
12362      * <p>Returning false from
12363      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
12364      * android.view.Menu)} will prevent the action mode from being started.</p>
12365      *
12366      * <p>Action click events should be handled by the custom implementation of
12367      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
12368      * android.view.MenuItem)}.</p>
12369      *
12370      * <p>Note that text insertion mode is not started when a TextView receives focus and the
12371      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
12372      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12373     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
12374         createEditorIfNeeded();
12375         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
12376     }
12377 
12378     /**
12379      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
12380      *
12381      * @return The current custom insertion callback.
12382      */
getCustomInsertionActionModeCallback()12383     public ActionMode.Callback getCustomInsertionActionModeCallback() {
12384         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
12385     }
12386 
12387     /**
12388      * Sets the {@link TextClassifier} for this TextView.
12389      */
setTextClassifier(@ullable TextClassifier textClassifier)12390     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
12391         mTextClassifier = textClassifier;
12392     }
12393 
12394     /**
12395      * Returns the {@link TextClassifier} used by this TextView.
12396      * If no TextClassifier has been set, this TextView uses the default set by the
12397      * {@link TextClassificationManager}.
12398      */
12399     @NonNull
getTextClassifier()12400     public TextClassifier getTextClassifier() {
12401         if (mTextClassifier == null) {
12402             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12403             if (tcm != null) {
12404                 return tcm.getTextClassifier();
12405             }
12406             return TextClassifier.NO_OP;
12407         }
12408         return mTextClassifier;
12409     }
12410 
12411     /**
12412      * Returns a session-aware text classifier.
12413      * This method creates one if none already exists or the current one is destroyed.
12414      */
12415     @NonNull
getTextClassificationSession()12416     TextClassifier getTextClassificationSession() {
12417         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
12418             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12419             if (tcm != null) {
12420                 final String widgetType;
12421                 if (isTextEditable()) {
12422                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
12423                 } else if (isTextSelectable()) {
12424                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
12425                 } else {
12426                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
12427                 }
12428                 mTextClassificationContext = new TextClassificationContext.Builder(
12429                         mContext.getPackageName(), widgetType)
12430                         .build();
12431                 if (mTextClassifier != null) {
12432                     mTextClassificationSession = tcm.createTextClassificationSession(
12433                             mTextClassificationContext, mTextClassifier);
12434                 } else {
12435                     mTextClassificationSession = tcm.createTextClassificationSession(
12436                             mTextClassificationContext);
12437                 }
12438             } else {
12439                 mTextClassificationSession = TextClassifier.NO_OP;
12440             }
12441         }
12442         return mTextClassificationSession;
12443     }
12444 
12445     /**
12446      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
12447      * @see #getTextClassificationSession()
12448      */
12449     @Nullable
getTextClassificationContext()12450     TextClassificationContext getTextClassificationContext() {
12451         return mTextClassificationContext;
12452     }
12453 
12454     /**
12455      * Returns true if this TextView uses a no-op TextClassifier.
12456      */
usesNoOpTextClassifier()12457     boolean usesNoOpTextClassifier() {
12458         return getTextClassifier() == TextClassifier.NO_OP;
12459     }
12460 
12461 
12462     /**
12463      * Starts an ActionMode for the specified TextLinkSpan.
12464      *
12465      * @return Whether or not we're attempting to start the action mode.
12466      * @hide
12467      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12468     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12469         Preconditions.checkNotNull(clickedSpan);
12470 
12471         if (!(mText instanceof Spanned)) {
12472             return false;
12473         }
12474 
12475         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
12476         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
12477 
12478         if (start < 0 || end > mText.length() || start >= end) {
12479             return false;
12480         }
12481 
12482         createEditorIfNeeded();
12483         mEditor.startLinkActionModeAsync(start, end);
12484         return true;
12485     }
12486 
12487     /**
12488      * Handles a click on the specified TextLinkSpan.
12489      *
12490      * @return Whether or not the click is being handled.
12491      * @hide
12492      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12493     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12494         Preconditions.checkNotNull(clickedSpan);
12495         if (mText instanceof Spanned) {
12496             final Spanned spanned = (Spanned) mText;
12497             final int start = spanned.getSpanStart(clickedSpan);
12498             final int end = spanned.getSpanEnd(clickedSpan);
12499             if (start >= 0 && end <= mText.length() && start < end) {
12500                 final TextClassification.Request request = new TextClassification.Request.Builder(
12501                         mText, start, end)
12502                         .setDefaultLocales(getTextLocales())
12503                         .build();
12504                 final Supplier<TextClassification> supplier = () ->
12505                         getTextClassifier().classifyText(request);
12506                 final Consumer<TextClassification> consumer = classification -> {
12507                     if (classification != null) {
12508                         if (!classification.getActions().isEmpty()) {
12509                             try {
12510                                 classification.getActions().get(0).getActionIntent().send();
12511                             } catch (PendingIntent.CanceledException e) {
12512                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
12513                             }
12514                         } else {
12515                             Log.d(LOG_TAG, "No link action to perform");
12516                         }
12517                     } else {
12518                         // classification == null
12519                         Log.d(LOG_TAG, "Timeout while classifying text");
12520                     }
12521                 };
12522                 CompletableFuture.supplyAsync(supplier)
12523                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
12524                         .thenAccept(consumer);
12525                 return true;
12526             }
12527         }
12528         return false;
12529     }
12530 
12531     /**
12532      * @hide
12533      */
12534     @UnsupportedAppUsage
stopTextActionMode()12535     protected void stopTextActionMode() {
12536         if (mEditor != null) {
12537             mEditor.stopTextActionMode();
12538         }
12539     }
12540 
12541     /** @hide */
hideFloatingToolbar(int durationMs)12542     public void hideFloatingToolbar(int durationMs) {
12543         if (mEditor != null) {
12544             mEditor.hideFloatingToolbar(durationMs);
12545         }
12546     }
12547 
canUndo()12548     boolean canUndo() {
12549         return mEditor != null && mEditor.canUndo();
12550     }
12551 
canRedo()12552     boolean canRedo() {
12553         return mEditor != null && mEditor.canRedo();
12554     }
12555 
canCut()12556     boolean canCut() {
12557         if (hasPasswordTransformationMethod()) {
12558             return false;
12559         }
12560 
12561         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
12562                 && mEditor.mKeyListener != null) {
12563             return true;
12564         }
12565 
12566         return false;
12567     }
12568 
canCopy()12569     boolean canCopy() {
12570         if (hasPasswordTransformationMethod()) {
12571             return false;
12572         }
12573 
12574         if (mText.length() > 0 && hasSelection() && mEditor != null) {
12575             return true;
12576         }
12577 
12578         return false;
12579     }
12580 
canShare()12581     boolean canShare() {
12582         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
12583             return false;
12584         }
12585         return canCopy();
12586     }
12587 
isDeviceProvisioned()12588     boolean isDeviceProvisioned() {
12589         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
12590             mDeviceProvisionedState = Settings.Global.getInt(
12591                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
12592                     ? DEVICE_PROVISIONED_YES
12593                     : DEVICE_PROVISIONED_NO;
12594         }
12595         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
12596     }
12597 
12598     @UnsupportedAppUsage
canPaste()12599     boolean canPaste() {
12600         return (mText instanceof Editable
12601                 && mEditor != null && mEditor.mKeyListener != null
12602                 && getSelectionStart() >= 0
12603                 && getSelectionEnd() >= 0
12604                 && getClipboardManagerForUser().hasPrimaryClip());
12605     }
12606 
canPasteAsPlainText()12607     boolean canPasteAsPlainText() {
12608         if (!canPaste()) {
12609             return false;
12610         }
12611 
12612         final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
12613         final ClipDescription description = clipData.getDescription();
12614         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
12615         final CharSequence text = clipData.getItemAt(0).getText();
12616         if (isPlainType && (text instanceof Spanned)) {
12617             Spanned spanned = (Spanned) text;
12618             if (TextUtils.hasStyleSpan(spanned)) {
12619                 return true;
12620             }
12621         }
12622         return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
12623     }
12624 
canProcessText()12625     boolean canProcessText() {
12626         if (getId() == View.NO_ID) {
12627             return false;
12628         }
12629         return canShare();
12630     }
12631 
canSelectAllText()12632     boolean canSelectAllText() {
12633         return canSelectText() && !hasPasswordTransformationMethod()
12634                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
12635     }
12636 
selectAllText()12637     boolean selectAllText() {
12638         if (mEditor != null) {
12639             // Hide the toolbar before changing the selection to avoid flickering.
12640             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
12641         }
12642         final int length = mText.length();
12643         Selection.setSelection(mSpannable, 0, length);
12644         return length > 0;
12645     }
12646 
replaceSelectionWithText(CharSequence text)12647     void replaceSelectionWithText(CharSequence text) {
12648         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
12649     }
12650 
12651     /**
12652      * Paste clipboard content between min and max positions.
12653      */
paste(int min, int max, boolean withFormatting)12654     private void paste(int min, int max, boolean withFormatting) {
12655         ClipboardManager clipboard = getClipboardManagerForUser();
12656         ClipData clip = clipboard.getPrimaryClip();
12657         if (clip != null) {
12658             boolean didFirst = false;
12659             for (int i = 0; i < clip.getItemCount(); i++) {
12660                 final CharSequence paste;
12661                 if (withFormatting) {
12662                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
12663                 } else {
12664                     // Get an item as text and remove all spans by toString().
12665                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
12666                     paste = (text instanceof Spanned) ? text.toString() : text;
12667                 }
12668                 if (paste != null) {
12669                     if (!didFirst) {
12670                         Selection.setSelection(mSpannable, max);
12671                         ((Editable) mText).replace(min, max, paste);
12672                         didFirst = true;
12673                     } else {
12674                         ((Editable) mText).insert(getSelectionEnd(), "\n");
12675                         ((Editable) mText).insert(getSelectionEnd(), paste);
12676                     }
12677                 }
12678             }
12679             sLastCutCopyOrTextChangedTime = 0;
12680         }
12681     }
12682 
shareSelectedText()12683     private void shareSelectedText() {
12684         String selectedText = getSelectedText();
12685         if (selectedText != null && !selectedText.isEmpty()) {
12686             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
12687             sharingIntent.setType("text/plain");
12688             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
12689             selectedText = TextUtils.trimToParcelableSize(selectedText);
12690             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
12691             getContext().startActivity(Intent.createChooser(sharingIntent, null));
12692             Selection.setSelection(mSpannable, getSelectionEnd());
12693         }
12694     }
12695 
12696     @CheckResult
setPrimaryClip(ClipData clip)12697     private boolean setPrimaryClip(ClipData clip) {
12698         ClipboardManager clipboard = getClipboardManagerForUser();
12699         try {
12700             clipboard.setPrimaryClip(clip);
12701         } catch (Throwable t) {
12702             return false;
12703         }
12704         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
12705         return true;
12706     }
12707 
12708     /**
12709      * Get the character offset closest to the specified absolute position. A typical use case is to
12710      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
12711      *
12712      * @param x The horizontal absolute position of a point on screen
12713      * @param y The vertical absolute position of a point on screen
12714      * @return the character offset for the character whose position is closest to the specified
12715      *  position. Returns -1 if there is no layout.
12716      */
getOffsetForPosition(float x, float y)12717     public int getOffsetForPosition(float x, float y) {
12718         if (getLayout() == null) return -1;
12719         final int line = getLineAtCoordinate(y);
12720         final int offset = getOffsetAtCoordinate(line, x);
12721         return offset;
12722     }
12723 
convertToLocalHorizontalCoordinate(float x)12724     float convertToLocalHorizontalCoordinate(float x) {
12725         x -= getTotalPaddingLeft();
12726         // Clamp the position to inside of the view.
12727         x = Math.max(0.0f, x);
12728         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
12729         x += getScrollX();
12730         return x;
12731     }
12732 
12733     @UnsupportedAppUsage
getLineAtCoordinate(float y)12734     int getLineAtCoordinate(float y) {
12735         y -= getTotalPaddingTop();
12736         // Clamp the position to inside of the view.
12737         y = Math.max(0.0f, y);
12738         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
12739         y += getScrollY();
12740         return getLayout().getLineForVertical((int) y);
12741     }
12742 
getLineAtCoordinateUnclamped(float y)12743     int getLineAtCoordinateUnclamped(float y) {
12744         y -= getTotalPaddingTop();
12745         y += getScrollY();
12746         return getLayout().getLineForVertical((int) y);
12747     }
12748 
getOffsetAtCoordinate(int line, float x)12749     int getOffsetAtCoordinate(int line, float x) {
12750         x = convertToLocalHorizontalCoordinate(x);
12751         return getLayout().getOffsetForHorizontal(line, x);
12752     }
12753 
12754     @Override
onDragEvent(DragEvent event)12755     public boolean onDragEvent(DragEvent event) {
12756         switch (event.getAction()) {
12757             case DragEvent.ACTION_DRAG_STARTED:
12758                 return mEditor != null && mEditor.hasInsertionController();
12759 
12760             case DragEvent.ACTION_DRAG_ENTERED:
12761                 TextView.this.requestFocus();
12762                 return true;
12763 
12764             case DragEvent.ACTION_DRAG_LOCATION:
12765                 if (mText instanceof Spannable) {
12766                     final int offset = getOffsetForPosition(event.getX(), event.getY());
12767                     Selection.setSelection(mSpannable, offset);
12768                 }
12769                 return true;
12770 
12771             case DragEvent.ACTION_DROP:
12772                 if (mEditor != null) mEditor.onDrop(event);
12773                 return true;
12774 
12775             case DragEvent.ACTION_DRAG_ENDED:
12776             case DragEvent.ACTION_DRAG_EXITED:
12777             default:
12778                 return true;
12779         }
12780     }
12781 
isInBatchEditMode()12782     boolean isInBatchEditMode() {
12783         if (mEditor == null) return false;
12784         final Editor.InputMethodState ims = mEditor.mInputMethodState;
12785         if (ims != null) {
12786             return ims.mBatchEditNesting > 0;
12787         }
12788         return mEditor.mInBatchEditControllers;
12789     }
12790 
12791     @Override
onRtlPropertiesChanged(int layoutDirection)12792     public void onRtlPropertiesChanged(int layoutDirection) {
12793         super.onRtlPropertiesChanged(layoutDirection);
12794 
12795         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
12796         if (mTextDir != newTextDir) {
12797             mTextDir = newTextDir;
12798             if (mLayout != null) {
12799                 checkForRelayout();
12800             }
12801         }
12802     }
12803 
12804     /**
12805      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
12806      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
12807      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
12808      * return value may not be the same as the one TextView uses if the View's layout direction is
12809      * not resolved or detached from parent root view.
12810      */
getTextDirectionHeuristic()12811     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
12812         if (hasPasswordTransformationMethod()) {
12813             // passwords fields should be LTR
12814             return TextDirectionHeuristics.LTR;
12815         }
12816 
12817         if (mEditor != null
12818                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
12819                     == EditorInfo.TYPE_CLASS_PHONE) {
12820             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
12821             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
12822             // RTL digits.
12823             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
12824             final String zero = symbols.getDigitStrings()[0];
12825             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
12826             // direction.
12827             final int firstCodepoint = zero.codePointAt(0);
12828             final byte digitDirection = Character.getDirectionality(firstCodepoint);
12829             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
12830                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
12831                 return TextDirectionHeuristics.RTL;
12832             } else {
12833                 return TextDirectionHeuristics.LTR;
12834             }
12835         }
12836 
12837         // Always need to resolve layout direction first
12838         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
12839 
12840         // Now, we can select the heuristic
12841         switch (getTextDirection()) {
12842             default:
12843             case TEXT_DIRECTION_FIRST_STRONG:
12844                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
12845                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
12846             case TEXT_DIRECTION_ANY_RTL:
12847                 return TextDirectionHeuristics.ANYRTL_LTR;
12848             case TEXT_DIRECTION_LTR:
12849                 return TextDirectionHeuristics.LTR;
12850             case TEXT_DIRECTION_RTL:
12851                 return TextDirectionHeuristics.RTL;
12852             case TEXT_DIRECTION_LOCALE:
12853                 return TextDirectionHeuristics.LOCALE;
12854             case TEXT_DIRECTION_FIRST_STRONG_LTR:
12855                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
12856             case TEXT_DIRECTION_FIRST_STRONG_RTL:
12857                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
12858         }
12859     }
12860 
12861     /**
12862      * @hide
12863      */
12864     @Override
onResolveDrawables(int layoutDirection)12865     public void onResolveDrawables(int layoutDirection) {
12866         // No need to resolve twice
12867         if (mLastLayoutDirection == layoutDirection) {
12868             return;
12869         }
12870         mLastLayoutDirection = layoutDirection;
12871 
12872         // Resolve drawables
12873         if (mDrawables != null) {
12874             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
12875                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
12876                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
12877                 applyCompoundDrawableTint();
12878             }
12879         }
12880     }
12881 
12882     /**
12883      * Prepares a drawable for display by propagating layout direction and
12884      * drawable state.
12885      *
12886      * @param dr the drawable to prepare
12887      */
prepareDrawableForDisplay(@ullable Drawable dr)12888     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
12889         if (dr == null) {
12890             return;
12891         }
12892 
12893         dr.setLayoutDirection(getLayoutDirection());
12894 
12895         if (dr.isStateful()) {
12896             dr.setState(getDrawableState());
12897             dr.jumpToCurrentState();
12898         }
12899     }
12900 
12901     /**
12902      * @hide
12903      */
resetResolvedDrawables()12904     protected void resetResolvedDrawables() {
12905         super.resetResolvedDrawables();
12906         mLastLayoutDirection = -1;
12907     }
12908 
12909     /**
12910      * @hide
12911      */
viewClicked(InputMethodManager imm)12912     protected void viewClicked(InputMethodManager imm) {
12913         if (imm != null) {
12914             imm.viewClicked(this);
12915         }
12916     }
12917 
12918     /**
12919      * Deletes the range of text [start, end[.
12920      * @hide
12921      */
12922     @UnsupportedAppUsage
deleteText_internal(int start, int end)12923     protected void deleteText_internal(int start, int end) {
12924         ((Editable) mText).delete(start, end);
12925     }
12926 
12927     /**
12928      * Replaces the range of text [start, end[ by replacement text
12929      * @hide
12930      */
replaceText_internal(int start, int end, CharSequence text)12931     protected void replaceText_internal(int start, int end, CharSequence text) {
12932         ((Editable) mText).replace(start, end, text);
12933     }
12934 
12935     /**
12936      * Sets a span on the specified range of text
12937      * @hide
12938      */
setSpan_internal(Object span, int start, int end, int flags)12939     protected void setSpan_internal(Object span, int start, int end, int flags) {
12940         ((Editable) mText).setSpan(span, start, end, flags);
12941     }
12942 
12943     /**
12944      * Moves the cursor to the specified offset position in text
12945      * @hide
12946      */
setCursorPosition_internal(int start, int end)12947     protected void setCursorPosition_internal(int start, int end) {
12948         Selection.setSelection(((Editable) mText), start, end);
12949     }
12950 
12951     /**
12952      * An Editor should be created as soon as any of the editable-specific fields (grouped
12953      * inside the Editor object) is assigned to a non-default value.
12954      * This method will create the Editor if needed.
12955      *
12956      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
12957      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
12958      * Editor for backward compatibility, as soon as one of these fields is assigned.
12959      *
12960      * Also note that for performance reasons, the mEditor is created when needed, but not
12961      * reset when no more edit-specific fields are needed.
12962      */
12963     @UnsupportedAppUsage
createEditorIfNeeded()12964     private void createEditorIfNeeded() {
12965         if (mEditor == null) {
12966             mEditor = new Editor(this);
12967         }
12968     }
12969 
12970     /**
12971      * @hide
12972      */
12973     @Override
12974     @UnsupportedAppUsage
getIterableTextForAccessibility()12975     public CharSequence getIterableTextForAccessibility() {
12976         return mText;
12977     }
12978 
ensureIterableTextForAccessibilitySelectable()12979     private void ensureIterableTextForAccessibilitySelectable() {
12980         if (!(mText instanceof Spannable)) {
12981             setText(mText, BufferType.SPANNABLE);
12982         }
12983     }
12984 
12985     /**
12986      * @hide
12987      */
12988     @Override
getIteratorForGranularity(int granularity)12989     public TextSegmentIterator getIteratorForGranularity(int granularity) {
12990         switch (granularity) {
12991             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
12992                 Spannable text = (Spannable) getIterableTextForAccessibility();
12993                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
12994                     AccessibilityIterators.LineTextSegmentIterator iterator =
12995                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
12996                     iterator.initialize(text, getLayout());
12997                     return iterator;
12998                 }
12999             } break;
13000             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
13001                 Spannable text = (Spannable) getIterableTextForAccessibility();
13002                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
13003                     AccessibilityIterators.PageTextSegmentIterator iterator =
13004                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
13005                     iterator.initialize(this);
13006                     return iterator;
13007                 }
13008             } break;
13009         }
13010         return super.getIteratorForGranularity(granularity);
13011     }
13012 
13013     /**
13014      * @hide
13015      */
13016     @Override
getAccessibilitySelectionStart()13017     public int getAccessibilitySelectionStart() {
13018         return getSelectionStart();
13019     }
13020 
13021     /**
13022      * @hide
13023      */
isAccessibilitySelectionExtendable()13024     public boolean isAccessibilitySelectionExtendable() {
13025         return true;
13026     }
13027 
13028     /**
13029      * @hide
13030      */
13031     @Override
getAccessibilitySelectionEnd()13032     public int getAccessibilitySelectionEnd() {
13033         return getSelectionEnd();
13034     }
13035 
13036     /**
13037      * @hide
13038      */
13039     @Override
setAccessibilitySelection(int start, int end)13040     public void setAccessibilitySelection(int start, int end) {
13041         if (getAccessibilitySelectionStart() == start
13042                 && getAccessibilitySelectionEnd() == end) {
13043             return;
13044         }
13045         CharSequence text = getIterableTextForAccessibility();
13046         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
13047             Selection.setSelection((Spannable) text, start, end);
13048         } else {
13049             Selection.removeSelection((Spannable) text);
13050         }
13051         // Hide all selection controllers used for adjusting selection
13052         // since we are doing so explicitlty by other means and these
13053         // controllers interact with how selection behaves.
13054         if (mEditor != null) {
13055             mEditor.hideCursorAndSpanControllers();
13056             mEditor.stopTextActionMode();
13057         }
13058     }
13059 
13060     /** @hide */
13061     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)13062     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
13063         super.encodeProperties(stream);
13064 
13065         TruncateAt ellipsize = getEllipsize();
13066         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
13067         stream.addProperty("text:textSize", getTextSize());
13068         stream.addProperty("text:scaledTextSize", getScaledTextSize());
13069         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
13070         stream.addProperty("text:selectionStart", getSelectionStart());
13071         stream.addProperty("text:selectionEnd", getSelectionEnd());
13072         stream.addProperty("text:curTextColor", mCurTextColor);
13073         stream.addProperty("text:text", mText == null ? null : mText.toString());
13074         stream.addProperty("text:gravity", mGravity);
13075     }
13076 
13077     /**
13078      * User interface state that is stored by TextView for implementing
13079      * {@link View#onSaveInstanceState}.
13080      */
13081     public static class SavedState extends BaseSavedState {
13082         int selStart = -1;
13083         int selEnd = -1;
13084         @UnsupportedAppUsage
13085         CharSequence text;
13086         boolean frozenWithFocus;
13087         CharSequence error;
13088         ParcelableParcel editorState;  // Optional state from Editor.
13089 
SavedState(Parcelable superState)13090         SavedState(Parcelable superState) {
13091             super(superState);
13092         }
13093 
13094         @Override
writeToParcel(Parcel out, int flags)13095         public void writeToParcel(Parcel out, int flags) {
13096             super.writeToParcel(out, flags);
13097             out.writeInt(selStart);
13098             out.writeInt(selEnd);
13099             out.writeInt(frozenWithFocus ? 1 : 0);
13100             TextUtils.writeToParcel(text, out, flags);
13101 
13102             if (error == null) {
13103                 out.writeInt(0);
13104             } else {
13105                 out.writeInt(1);
13106                 TextUtils.writeToParcel(error, out, flags);
13107             }
13108 
13109             if (editorState == null) {
13110                 out.writeInt(0);
13111             } else {
13112                 out.writeInt(1);
13113                 editorState.writeToParcel(out, flags);
13114             }
13115         }
13116 
13117         @Override
toString()13118         public String toString() {
13119             String str = "TextView.SavedState{"
13120                     + Integer.toHexString(System.identityHashCode(this))
13121                     + " start=" + selStart + " end=" + selEnd;
13122             if (text != null) {
13123                 str += " text=" + text;
13124             }
13125             return str + "}";
13126         }
13127 
13128         @SuppressWarnings("hiding")
13129         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
13130                 new Parcelable.Creator<SavedState>() {
13131                     public SavedState createFromParcel(Parcel in) {
13132                         return new SavedState(in);
13133                     }
13134 
13135                     public SavedState[] newArray(int size) {
13136                         return new SavedState[size];
13137                     }
13138                 };
13139 
SavedState(Parcel in)13140         private SavedState(Parcel in) {
13141             super(in);
13142             selStart = in.readInt();
13143             selEnd = in.readInt();
13144             frozenWithFocus = (in.readInt() != 0);
13145             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13146 
13147             if (in.readInt() != 0) {
13148                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13149             }
13150 
13151             if (in.readInt() != 0) {
13152                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
13153             }
13154         }
13155     }
13156 
13157     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
13158         private char[] mChars;
13159         private int mStart, mLength;
13160 
CharWrapper(char[] chars, int start, int len)13161         public CharWrapper(char[] chars, int start, int len) {
13162             mChars = chars;
13163             mStart = start;
13164             mLength = len;
13165         }
13166 
set(char[] chars, int start, int len)13167         /* package */ void set(char[] chars, int start, int len) {
13168             mChars = chars;
13169             mStart = start;
13170             mLength = len;
13171         }
13172 
length()13173         public int length() {
13174             return mLength;
13175         }
13176 
charAt(int off)13177         public char charAt(int off) {
13178             return mChars[off + mStart];
13179         }
13180 
13181         @Override
toString()13182         public String toString() {
13183             return new String(mChars, mStart, mLength);
13184         }
13185 
subSequence(int start, int end)13186         public CharSequence subSequence(int start, int end) {
13187             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13188                 throw new IndexOutOfBoundsException(start + ", " + end);
13189             }
13190 
13191             return new String(mChars, start + mStart, end - start);
13192         }
13193 
getChars(int start, int end, char[] buf, int off)13194         public void getChars(int start, int end, char[] buf, int off) {
13195             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13196                 throw new IndexOutOfBoundsException(start + ", " + end);
13197             }
13198 
13199             System.arraycopy(mChars, start + mStart, buf, off, end - start);
13200         }
13201 
13202         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13203         public void drawText(BaseCanvas c, int start, int end,
13204                              float x, float y, Paint p) {
13205             c.drawText(mChars, start + mStart, end - start, x, y, p);
13206         }
13207 
13208         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13209         public void drawTextRun(BaseCanvas c, int start, int end,
13210                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
13211             int count = end - start;
13212             int contextCount = contextEnd - contextStart;
13213             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
13214                     contextCount, x, y, isRtl, p);
13215         }
13216 
measureText(int start, int end, Paint p)13217         public float measureText(int start, int end, Paint p) {
13218             return p.measureText(mChars, start + mStart, end - start);
13219         }
13220 
getTextWidths(int start, int end, float[] widths, Paint p)13221         public int getTextWidths(int start, int end, float[] widths, Paint p) {
13222             return p.getTextWidths(mChars, start + mStart, end - start, widths);
13223         }
13224 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13225         public float getTextRunAdvances(int start, int end, int contextStart,
13226                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
13227                 Paint p) {
13228             int count = end - start;
13229             int contextCount = contextEnd - contextStart;
13230             return p.getTextRunAdvances(mChars, start + mStart, count,
13231                     contextStart + mStart, contextCount, isRtl, advances,
13232                     advancesIndex);
13233         }
13234 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13235         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
13236                 int offset, int cursorOpt, Paint p) {
13237             int contextCount = contextEnd - contextStart;
13238             return p.getTextRunCursor(mChars, contextStart + mStart,
13239                     contextCount, isRtl, offset + mStart, cursorOpt);
13240         }
13241     }
13242 
13243     private static final class Marquee {
13244         // TODO: Add an option to configure this
13245         private static final float MARQUEE_DELTA_MAX = 0.07f;
13246         private static final int MARQUEE_DELAY = 1200;
13247         private static final int MARQUEE_DP_PER_SECOND = 30;
13248 
13249         private static final byte MARQUEE_STOPPED = 0x0;
13250         private static final byte MARQUEE_STARTING = 0x1;
13251         private static final byte MARQUEE_RUNNING = 0x2;
13252 
13253         private final WeakReference<TextView> mView;
13254         private final Choreographer mChoreographer;
13255 
13256         private byte mStatus = MARQUEE_STOPPED;
13257         private final float mPixelsPerMs;
13258         private float mMaxScroll;
13259         private float mMaxFadeScroll;
13260         private float mGhostStart;
13261         private float mGhostOffset;
13262         private float mFadeStop;
13263         private int mRepeatLimit;
13264 
13265         private float mScroll;
13266         private long mLastAnimationMs;
13267 
Marquee(TextView v)13268         Marquee(TextView v) {
13269             final float density = v.getContext().getResources().getDisplayMetrics().density;
13270             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
13271             mView = new WeakReference<TextView>(v);
13272             mChoreographer = Choreographer.getInstance();
13273         }
13274 
13275         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
13276             @Override
13277             public void doFrame(long frameTimeNanos) {
13278                 tick();
13279             }
13280         };
13281 
13282         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
13283             @Override
13284             public void doFrame(long frameTimeNanos) {
13285                 mStatus = MARQUEE_RUNNING;
13286                 mLastAnimationMs = mChoreographer.getFrameTime();
13287                 tick();
13288             }
13289         };
13290 
13291         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
13292             @Override
13293             public void doFrame(long frameTimeNanos) {
13294                 if (mStatus == MARQUEE_RUNNING) {
13295                     if (mRepeatLimit >= 0) {
13296                         mRepeatLimit--;
13297                     }
13298                     start(mRepeatLimit);
13299                 }
13300             }
13301         };
13302 
tick()13303         void tick() {
13304             if (mStatus != MARQUEE_RUNNING) {
13305                 return;
13306             }
13307 
13308             mChoreographer.removeFrameCallback(mTickCallback);
13309 
13310             final TextView textView = mView.get();
13311             if (textView != null && (textView.isFocused() || textView.isSelected())) {
13312                 long currentMs = mChoreographer.getFrameTime();
13313                 long deltaMs = currentMs - mLastAnimationMs;
13314                 mLastAnimationMs = currentMs;
13315                 float deltaPx = deltaMs * mPixelsPerMs;
13316                 mScroll += deltaPx;
13317                 if (mScroll > mMaxScroll) {
13318                     mScroll = mMaxScroll;
13319                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
13320                 } else {
13321                     mChoreographer.postFrameCallback(mTickCallback);
13322                 }
13323                 textView.invalidate();
13324             }
13325         }
13326 
stop()13327         void stop() {
13328             mStatus = MARQUEE_STOPPED;
13329             mChoreographer.removeFrameCallback(mStartCallback);
13330             mChoreographer.removeFrameCallback(mRestartCallback);
13331             mChoreographer.removeFrameCallback(mTickCallback);
13332             resetScroll();
13333         }
13334 
resetScroll()13335         private void resetScroll() {
13336             mScroll = 0.0f;
13337             final TextView textView = mView.get();
13338             if (textView != null) textView.invalidate();
13339         }
13340 
start(int repeatLimit)13341         void start(int repeatLimit) {
13342             if (repeatLimit == 0) {
13343                 stop();
13344                 return;
13345             }
13346             mRepeatLimit = repeatLimit;
13347             final TextView textView = mView.get();
13348             if (textView != null && textView.mLayout != null) {
13349                 mStatus = MARQUEE_STARTING;
13350                 mScroll = 0.0f;
13351                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
13352                         - textView.getCompoundPaddingRight();
13353                 final float lineWidth = textView.mLayout.getLineWidth(0);
13354                 final float gap = textWidth / 3.0f;
13355                 mGhostStart = lineWidth - textWidth + gap;
13356                 mMaxScroll = mGhostStart + textWidth;
13357                 mGhostOffset = lineWidth + gap;
13358                 mFadeStop = lineWidth + textWidth / 6.0f;
13359                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
13360 
13361                 textView.invalidate();
13362                 mChoreographer.postFrameCallback(mStartCallback);
13363             }
13364         }
13365 
getGhostOffset()13366         float getGhostOffset() {
13367             return mGhostOffset;
13368         }
13369 
getScroll()13370         float getScroll() {
13371             return mScroll;
13372         }
13373 
getMaxFadeScroll()13374         float getMaxFadeScroll() {
13375             return mMaxFadeScroll;
13376         }
13377 
shouldDrawLeftFade()13378         boolean shouldDrawLeftFade() {
13379             return mScroll <= mFadeStop;
13380         }
13381 
shouldDrawGhost()13382         boolean shouldDrawGhost() {
13383             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
13384         }
13385 
isRunning()13386         boolean isRunning() {
13387             return mStatus == MARQUEE_RUNNING;
13388         }
13389 
isStopped()13390         boolean isStopped() {
13391             return mStatus == MARQUEE_STOPPED;
13392         }
13393     }
13394 
13395     private class ChangeWatcher implements TextWatcher, SpanWatcher {
13396 
13397         private CharSequence mBeforeText;
13398 
beforeTextChanged(CharSequence buffer, int start, int before, int after)13399         public void beforeTextChanged(CharSequence buffer, int start,
13400                                       int before, int after) {
13401             if (DEBUG_EXTRACT) {
13402                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
13403                         + " before=" + before + " after=" + after + ": " + buffer);
13404             }
13405 
13406             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
13407                 mBeforeText = mTransformed.toString();
13408             }
13409 
13410             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
13411         }
13412 
onTextChanged(CharSequence buffer, int start, int before, int after)13413         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
13414             if (DEBUG_EXTRACT) {
13415                 Log.v(LOG_TAG, "onTextChanged start=" + start
13416                         + " before=" + before + " after=" + after + ": " + buffer);
13417             }
13418             TextView.this.handleTextChanged(buffer, start, before, after);
13419 
13420             if (AccessibilityManager.getInstance(mContext).isEnabled()
13421                     && (isFocused() || isSelected() && isShown())) {
13422                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
13423                 mBeforeText = null;
13424             }
13425         }
13426 
afterTextChanged(Editable buffer)13427         public void afterTextChanged(Editable buffer) {
13428             if (DEBUG_EXTRACT) {
13429                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
13430             }
13431             TextView.this.sendAfterTextChanged(buffer);
13432 
13433             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
13434                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
13435             }
13436         }
13437 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13438         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
13439             if (DEBUG_EXTRACT) {
13440                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
13441                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
13442             }
13443             TextView.this.spanChange(buf, what, s, st, e, en);
13444         }
13445 
onSpanAdded(Spannable buf, Object what, int s, int e)13446         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
13447             if (DEBUG_EXTRACT) {
13448                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
13449             }
13450             TextView.this.spanChange(buf, what, -1, s, -1, e);
13451         }
13452 
onSpanRemoved(Spannable buf, Object what, int s, int e)13453         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
13454             if (DEBUG_EXTRACT) {
13455                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
13456             }
13457             TextView.this.spanChange(buf, what, s, -1, e, -1);
13458         }
13459     }
13460 }
13461