• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
20 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
21 import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT;
22 import static android.view.ContentInfo.SOURCE_AUTOFILL;
23 import static android.view.ContentInfo.SOURCE_CLIPBOARD;
24 import static android.view.ContentInfo.SOURCE_PROCESS_TEXT;
25 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY;
26 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
27 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
28 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
29 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
30 
31 import android.R;
32 import android.annotation.CallSuper;
33 import android.annotation.CheckResult;
34 import android.annotation.ColorInt;
35 import android.annotation.DrawableRes;
36 import android.annotation.FloatRange;
37 import android.annotation.IntDef;
38 import android.annotation.IntRange;
39 import android.annotation.NonNull;
40 import android.annotation.Nullable;
41 import android.annotation.Px;
42 import android.annotation.RequiresPermission;
43 import android.annotation.Size;
44 import android.annotation.StringRes;
45 import android.annotation.StyleRes;
46 import android.annotation.TestApi;
47 import android.annotation.XmlRes;
48 import android.app.Activity;
49 import android.app.PendingIntent;
50 import android.app.assist.AssistStructure;
51 import android.compat.annotation.UnsupportedAppUsage;
52 import android.content.ClipData;
53 import android.content.ClipDescription;
54 import android.content.ClipboardManager;
55 import android.content.Context;
56 import android.content.Intent;
57 import android.content.UndoManager;
58 import android.content.pm.PackageManager;
59 import android.content.res.ColorStateList;
60 import android.content.res.CompatibilityInfo;
61 import android.content.res.Configuration;
62 import android.content.res.Resources;
63 import android.content.res.TypedArray;
64 import android.content.res.XmlResourceParser;
65 import android.graphics.BaseCanvas;
66 import android.graphics.BlendMode;
67 import android.graphics.Canvas;
68 import android.graphics.Insets;
69 import android.graphics.Paint;
70 import android.graphics.Paint.FontMetricsInt;
71 import android.graphics.Path;
72 import android.graphics.PorterDuff;
73 import android.graphics.Rect;
74 import android.graphics.RectF;
75 import android.graphics.Typeface;
76 import android.graphics.drawable.Drawable;
77 import android.graphics.fonts.FontStyle;
78 import android.graphics.fonts.FontVariationAxis;
79 import android.icu.text.DecimalFormatSymbols;
80 import android.os.AsyncTask;
81 import android.os.Build;
82 import android.os.Build.VERSION_CODES;
83 import android.os.Bundle;
84 import android.os.Handler;
85 import android.os.LocaleList;
86 import android.os.Parcel;
87 import android.os.Parcelable;
88 import android.os.ParcelableParcel;
89 import android.os.Process;
90 import android.os.SystemClock;
91 import android.os.UserHandle;
92 import android.provider.Settings;
93 import android.text.BoringLayout;
94 import android.text.DynamicLayout;
95 import android.text.Editable;
96 import android.text.GetChars;
97 import android.text.GraphicsOperations;
98 import android.text.InputFilter;
99 import android.text.InputType;
100 import android.text.Layout;
101 import android.text.ParcelableSpan;
102 import android.text.PrecomputedText;
103 import android.text.Selection;
104 import android.text.SpanWatcher;
105 import android.text.Spannable;
106 import android.text.SpannableStringBuilder;
107 import android.text.Spanned;
108 import android.text.SpannedString;
109 import android.text.StaticLayout;
110 import android.text.TextDirectionHeuristic;
111 import android.text.TextDirectionHeuristics;
112 import android.text.TextPaint;
113 import android.text.TextUtils;
114 import android.text.TextUtils.TruncateAt;
115 import android.text.TextWatcher;
116 import android.text.method.AllCapsTransformationMethod;
117 import android.text.method.ArrowKeyMovementMethod;
118 import android.text.method.DateKeyListener;
119 import android.text.method.DateTimeKeyListener;
120 import android.text.method.DialerKeyListener;
121 import android.text.method.DigitsKeyListener;
122 import android.text.method.KeyListener;
123 import android.text.method.LinkMovementMethod;
124 import android.text.method.MetaKeyKeyListener;
125 import android.text.method.MovementMethod;
126 import android.text.method.PasswordTransformationMethod;
127 import android.text.method.SingleLineTransformationMethod;
128 import android.text.method.TextKeyListener;
129 import android.text.method.TimeKeyListener;
130 import android.text.method.TransformationMethod;
131 import android.text.method.TransformationMethod2;
132 import android.text.method.WordIterator;
133 import android.text.style.CharacterStyle;
134 import android.text.style.ClickableSpan;
135 import android.text.style.ParagraphStyle;
136 import android.text.style.SpellCheckSpan;
137 import android.text.style.SuggestionSpan;
138 import android.text.style.URLSpan;
139 import android.text.style.UpdateAppearance;
140 import android.text.util.Linkify;
141 import android.util.AttributeSet;
142 import android.util.DisplayMetrics;
143 import android.util.IntArray;
144 import android.util.Log;
145 import android.util.SparseIntArray;
146 import android.util.TypedValue;
147 import android.view.AccessibilityIterators.TextSegmentIterator;
148 import android.view.ActionMode;
149 import android.view.Choreographer;
150 import android.view.ContentInfo;
151 import android.view.ContextMenu;
152 import android.view.DragEvent;
153 import android.view.Gravity;
154 import android.view.HapticFeedbackConstants;
155 import android.view.InputDevice;
156 import android.view.KeyCharacterMap;
157 import android.view.KeyEvent;
158 import android.view.MotionEvent;
159 import android.view.PointerIcon;
160 import android.view.View;
161 import android.view.ViewConfiguration;
162 import android.view.ViewDebug;
163 import android.view.ViewGroup.LayoutParams;
164 import android.view.ViewHierarchyEncoder;
165 import android.view.ViewParent;
166 import android.view.ViewRootImpl;
167 import android.view.ViewStructure;
168 import android.view.ViewTreeObserver;
169 import android.view.accessibility.AccessibilityEvent;
170 import android.view.accessibility.AccessibilityManager;
171 import android.view.accessibility.AccessibilityNodeInfo;
172 import android.view.animation.AnimationUtils;
173 import android.view.autofill.AutofillManager;
174 import android.view.autofill.AutofillValue;
175 import android.view.contentcapture.ContentCaptureManager;
176 import android.view.contentcapture.ContentCaptureSession;
177 import android.view.inputmethod.BaseInputConnection;
178 import android.view.inputmethod.CompletionInfo;
179 import android.view.inputmethod.CorrectionInfo;
180 import android.view.inputmethod.CursorAnchorInfo;
181 import android.view.inputmethod.EditorInfo;
182 import android.view.inputmethod.ExtractedText;
183 import android.view.inputmethod.ExtractedTextRequest;
184 import android.view.inputmethod.InputConnection;
185 import android.view.inputmethod.InputMethodManager;
186 import android.view.inspector.InspectableProperty;
187 import android.view.inspector.InspectableProperty.EnumEntry;
188 import android.view.inspector.InspectableProperty.FlagEntry;
189 import android.view.textclassifier.TextClassification;
190 import android.view.textclassifier.TextClassificationContext;
191 import android.view.textclassifier.TextClassificationManager;
192 import android.view.textclassifier.TextClassifier;
193 import android.view.textclassifier.TextLinks;
194 import android.view.textservice.SpellCheckerSubtype;
195 import android.view.textservice.TextServicesManager;
196 import android.view.translation.TranslationRequestValue;
197 import android.view.translation.TranslationSpec;
198 import android.view.translation.UiTranslationController;
199 import android.view.translation.ViewTranslationCallback;
200 import android.view.translation.ViewTranslationRequest;
201 import android.widget.RemoteViews.RemoteView;
202 
203 import com.android.internal.annotations.VisibleForTesting;
204 import com.android.internal.logging.MetricsLogger;
205 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
206 import com.android.internal.util.ArrayUtils;
207 import com.android.internal.util.FastMath;
208 import com.android.internal.util.Preconditions;
209 import com.android.internal.widget.EditableInputConnection;
210 
211 import libcore.util.EmptyArray;
212 
213 import org.xmlpull.v1.XmlPullParserException;
214 
215 import java.io.IOException;
216 import java.lang.annotation.Retention;
217 import java.lang.annotation.RetentionPolicy;
218 import java.lang.ref.WeakReference;
219 import java.util.ArrayList;
220 import java.util.Arrays;
221 import java.util.Locale;
222 import java.util.Objects;
223 import java.util.concurrent.CompletableFuture;
224 import java.util.concurrent.TimeUnit;
225 import java.util.function.Consumer;
226 import java.util.function.Supplier;
227 
228 /**
229  * A user interface element that displays text to the user.
230  * To provide user-editable text, see {@link EditText}.
231  * <p>
232  * The following code sample shows a typical use, with an XML layout
233  * and code to modify the contents of the text view:
234  * </p>
235 
236  * <pre>
237  * &lt;LinearLayout
238        xmlns:android="http://schemas.android.com/apk/res/android"
239        android:layout_width="match_parent"
240        android:layout_height="match_parent"&gt;
241  *    &lt;TextView
242  *        android:id="@+id/text_view_id"
243  *        android:layout_height="wrap_content"
244  *        android:layout_width="wrap_content"
245  *        android:text="@string/hello" /&gt;
246  * &lt;/LinearLayout&gt;
247  * </pre>
248  * <p>
249  * This code sample demonstrates how to modify the contents of the text view
250  * defined in the previous XML layout:
251  * </p>
252  * <pre>
253  * public class MainActivity extends Activity {
254  *
255  *    protected void onCreate(Bundle savedInstanceState) {
256  *         super.onCreate(savedInstanceState);
257  *         setContentView(R.layout.activity_main);
258  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
259  *         helloTextView.setText(R.string.user_greeting);
260  *     }
261  * }
262  * </pre>
263  * <p>
264  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
265  * </p>
266  * <p>
267  * <b>XML attributes</b>
268  * <p>
269  * See {@link android.R.styleable#TextView TextView Attributes},
270  * {@link android.R.styleable#View View Attributes}
271  *
272  * @attr ref android.R.styleable#TextView_text
273  * @attr ref android.R.styleable#TextView_bufferType
274  * @attr ref android.R.styleable#TextView_hint
275  * @attr ref android.R.styleable#TextView_textColor
276  * @attr ref android.R.styleable#TextView_textColorHighlight
277  * @attr ref android.R.styleable#TextView_textColorHint
278  * @attr ref android.R.styleable#TextView_textAppearance
279  * @attr ref android.R.styleable#TextView_textColorLink
280  * @attr ref android.R.styleable#TextView_textFontWeight
281  * @attr ref android.R.styleable#TextView_textSize
282  * @attr ref android.R.styleable#TextView_textScaleX
283  * @attr ref android.R.styleable#TextView_fontFamily
284  * @attr ref android.R.styleable#TextView_typeface
285  * @attr ref android.R.styleable#TextView_textStyle
286  * @attr ref android.R.styleable#TextView_cursorVisible
287  * @attr ref android.R.styleable#TextView_maxLines
288  * @attr ref android.R.styleable#TextView_maxHeight
289  * @attr ref android.R.styleable#TextView_lines
290  * @attr ref android.R.styleable#TextView_height
291  * @attr ref android.R.styleable#TextView_minLines
292  * @attr ref android.R.styleable#TextView_minHeight
293  * @attr ref android.R.styleable#TextView_maxEms
294  * @attr ref android.R.styleable#TextView_maxWidth
295  * @attr ref android.R.styleable#TextView_ems
296  * @attr ref android.R.styleable#TextView_width
297  * @attr ref android.R.styleable#TextView_minEms
298  * @attr ref android.R.styleable#TextView_minWidth
299  * @attr ref android.R.styleable#TextView_gravity
300  * @attr ref android.R.styleable#TextView_scrollHorizontally
301  * @attr ref android.R.styleable#TextView_password
302  * @attr ref android.R.styleable#TextView_singleLine
303  * @attr ref android.R.styleable#TextView_selectAllOnFocus
304  * @attr ref android.R.styleable#TextView_includeFontPadding
305  * @attr ref android.R.styleable#TextView_maxLength
306  * @attr ref android.R.styleable#TextView_shadowColor
307  * @attr ref android.R.styleable#TextView_shadowDx
308  * @attr ref android.R.styleable#TextView_shadowDy
309  * @attr ref android.R.styleable#TextView_shadowRadius
310  * @attr ref android.R.styleable#TextView_autoLink
311  * @attr ref android.R.styleable#TextView_linksClickable
312  * @attr ref android.R.styleable#TextView_numeric
313  * @attr ref android.R.styleable#TextView_digits
314  * @attr ref android.R.styleable#TextView_phoneNumber
315  * @attr ref android.R.styleable#TextView_inputMethod
316  * @attr ref android.R.styleable#TextView_capitalize
317  * @attr ref android.R.styleable#TextView_autoText
318  * @attr ref android.R.styleable#TextView_editable
319  * @attr ref android.R.styleable#TextView_freezesText
320  * @attr ref android.R.styleable#TextView_ellipsize
321  * @attr ref android.R.styleable#TextView_drawableTop
322  * @attr ref android.R.styleable#TextView_drawableBottom
323  * @attr ref android.R.styleable#TextView_drawableRight
324  * @attr ref android.R.styleable#TextView_drawableLeft
325  * @attr ref android.R.styleable#TextView_drawableStart
326  * @attr ref android.R.styleable#TextView_drawableEnd
327  * @attr ref android.R.styleable#TextView_drawablePadding
328  * @attr ref android.R.styleable#TextView_drawableTint
329  * @attr ref android.R.styleable#TextView_drawableTintMode
330  * @attr ref android.R.styleable#TextView_lineSpacingExtra
331  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
332  * @attr ref android.R.styleable#TextView_justificationMode
333  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
334  * @attr ref android.R.styleable#TextView_inputType
335  * @attr ref android.R.styleable#TextView_imeOptions
336  * @attr ref android.R.styleable#TextView_privateImeOptions
337  * @attr ref android.R.styleable#TextView_imeActionLabel
338  * @attr ref android.R.styleable#TextView_imeActionId
339  * @attr ref android.R.styleable#TextView_editorExtras
340  * @attr ref android.R.styleable#TextView_elegantTextHeight
341  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
342  * @attr ref android.R.styleable#TextView_letterSpacing
343  * @attr ref android.R.styleable#TextView_fontFeatureSettings
344  * @attr ref android.R.styleable#TextView_fontVariationSettings
345  * @attr ref android.R.styleable#TextView_breakStrategy
346  * @attr ref android.R.styleable#TextView_hyphenationFrequency
347  * @attr ref android.R.styleable#TextView_autoSizeTextType
348  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
349  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
350  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
351  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
352  * @attr ref android.R.styleable#TextView_textCursorDrawable
353  * @attr ref android.R.styleable#TextView_textSelectHandle
354  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
355  * @attr ref android.R.styleable#TextView_textSelectHandleRight
356  * @attr ref android.R.styleable#TextView_allowUndo
357  * @attr ref android.R.styleable#TextView_enabled
358  */
359 @RemoteView
360 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
361     static final String LOG_TAG = "TextView";
362     static final boolean DEBUG_EXTRACT = false;
363     static final boolean DEBUG_CURSOR = false;
364 
365     private static final float[] TEMP_POSITION = new float[2];
366 
367     // Enum for the "typeface" XML parameter.
368     // TODO: How can we get this from the XML instead of hardcoding it here?
369     /** @hide */
370     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
371     @Retention(RetentionPolicy.SOURCE)
372     public @interface XMLTypefaceAttr{}
373     private static final int DEFAULT_TYPEFACE = -1;
374     private static final int SANS = 1;
375     private static final int SERIF = 2;
376     private static final int MONOSPACE = 3;
377 
378     // Enum for the "ellipsize" XML parameter.
379     private static final int ELLIPSIZE_NOT_SET = -1;
380     private static final int ELLIPSIZE_NONE = 0;
381     private static final int ELLIPSIZE_START = 1;
382     private static final int ELLIPSIZE_MIDDLE = 2;
383     private static final int ELLIPSIZE_END = 3;
384     private static final int ELLIPSIZE_MARQUEE = 4;
385 
386     // Bitfield for the "numeric" XML parameter.
387     // TODO: How can we get this from the XML instead of hardcoding it here?
388     private static final int SIGNED = 2;
389     private static final int DECIMAL = 4;
390 
391     /**
392      * Draw marquee text with fading edges as usual
393      */
394     private static final int MARQUEE_FADE_NORMAL = 0;
395 
396     /**
397      * Draw marquee text as ellipsize end while inactive instead of with the fade.
398      * (Useful for devices where the fade can be expensive if overdone)
399      */
400     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
401 
402     /**
403      * Draw marquee text with fading edges because it is currently active/animating.
404      */
405     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
406 
407     @UnsupportedAppUsage
408     private static final int LINES = 1;
409     private static final int EMS = LINES;
410     private static final int PIXELS = 2;
411 
412     // Maximum text length for single line input.
413     private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
414     private InputFilter.LengthFilter mSingleLineLengthFilter = null;
415 
416     private static final RectF TEMP_RECTF = new RectF();
417 
418     /** @hide */
419     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
420     private static final int ANIMATED_SCROLL_GAP = 250;
421 
422     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
423     private static final Spanned EMPTY_SPANNED = new SpannedString("");
424 
425     private static final int CHANGE_WATCHER_PRIORITY = 100;
426 
427     // New state used to change background based on whether this TextView is multiline.
428     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
429 
430     // Accessibility action to share selected text.
431     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
432 
433     /**
434      * @hide
435      */
436     // Accessibility action start id for "process text" actions.
437     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
438 
439     /**
440      * @hide
441      */
442     @TestApi
443     public static final int PROCESS_TEXT_REQUEST_CODE = 100;
444 
445     /**
446      *  Return code of {@link #doKeyDown}.
447      */
448     private static final int KEY_EVENT_NOT_HANDLED = 0;
449     private static final int KEY_EVENT_HANDLED = -1;
450     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
451     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
452 
453     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
454 
455     // System wide time for last cut, copy or text changed action.
456     static long sLastCutCopyOrTextChangedTime;
457 
458     private ColorStateList mTextColor;
459     private ColorStateList mHintTextColor;
460     private ColorStateList mLinkTextColor;
461     @ViewDebug.ExportedProperty(category = "text")
462 
463     /**
464      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
465      */
466     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
467     private int mCurTextColor;
468 
469     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
470     private int mCurHintTextColor;
471     private boolean mFreezesText;
472 
473     @UnsupportedAppUsage
474     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
475     @UnsupportedAppUsage
476     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
477 
478     @UnsupportedAppUsage
479     private float mShadowRadius;
480     @UnsupportedAppUsage
481     private float mShadowDx;
482     @UnsupportedAppUsage
483     private float mShadowDy;
484     private int mShadowColor;
485 
486     private boolean mPreDrawRegistered;
487     private boolean mPreDrawListenerDetached;
488 
489     private TextClassifier mTextClassifier;
490     private TextClassifier mTextClassificationSession;
491     private TextClassificationContext mTextClassificationContext;
492 
493     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
494     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
495     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
496     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
497     // into by the user holding the movement key down) then we shouldn't prevent the focus from
498     // changing.
499     private boolean mPreventDefaultMovement;
500 
501     private TextUtils.TruncateAt mEllipsize;
502 
503     // A flag to indicate the cursor was hidden by IME.
504     private boolean mImeIsConsumingInput;
505 
506     // Whether cursor is visible without regard to {@link mImeConsumesInput}.
507     // {@code true} is the default value.
508     private boolean mCursorVisibleFromAttr = true;
509 
510     static class Drawables {
511         static final int LEFT = 0;
512         static final int TOP = 1;
513         static final int RIGHT = 2;
514         static final int BOTTOM = 3;
515 
516         static final int DRAWABLE_NONE = -1;
517         static final int DRAWABLE_RIGHT = 0;
518         static final int DRAWABLE_LEFT = 1;
519 
520         final Rect mCompoundRect = new Rect();
521 
522         final Drawable[] mShowing = new Drawable[4];
523 
524         ColorStateList mTintList;
525         BlendMode mBlendMode;
526         boolean mHasTint;
527         boolean mHasTintMode;
528 
529         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
530         Drawable mDrawableLeftInitial, mDrawableRightInitial;
531 
532         boolean mIsRtlCompatibilityMode;
533         boolean mOverride;
534 
535         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
536                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
537 
538         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
539                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
540 
541         int mDrawablePadding;
542 
543         int mDrawableSaved = DRAWABLE_NONE;
544 
Drawables(Context context)545         public Drawables(Context context) {
546             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
547             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
548                     || !context.getApplicationInfo().hasRtlSupport();
549             mOverride = false;
550         }
551 
552         /**
553          * @return {@code true} if this object contains metadata that needs to
554          *         be retained, {@code false} otherwise
555          */
556         public boolean hasMetadata() {
557             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
558         }
559 
560         /**
561          * Updates the list of displayed drawables to account for the current
562          * layout direction.
563          *
564          * @param layoutDirection the current layout direction
565          * @return {@code true} if the displayed drawables changed
566          */
567         public boolean resolveWithLayoutDirection(int layoutDirection) {
568             final Drawable previousLeft = mShowing[Drawables.LEFT];
569             final Drawable previousRight = mShowing[Drawables.RIGHT];
570 
571             // First reset "left" and "right" drawables to their initial values
572             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
573             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
574 
575             if (mIsRtlCompatibilityMode) {
576                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
577                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
578                     mShowing[Drawables.LEFT] = mDrawableStart;
579                     mDrawableSizeLeft = mDrawableSizeStart;
580                     mDrawableHeightLeft = mDrawableHeightStart;
581                 }
582                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
583                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
584                     mShowing[Drawables.RIGHT] = mDrawableEnd;
585                     mDrawableSizeRight = mDrawableSizeEnd;
586                     mDrawableHeightRight = mDrawableHeightEnd;
587                 }
588             } else {
589                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
590                 // drawable if and only if they have been defined
591                 switch(layoutDirection) {
592                     case LAYOUT_DIRECTION_RTL:
593                         if (mOverride) {
594                             mShowing[Drawables.RIGHT] = mDrawableStart;
595                             mDrawableSizeRight = mDrawableSizeStart;
596                             mDrawableHeightRight = mDrawableHeightStart;
597 
598                             mShowing[Drawables.LEFT] = mDrawableEnd;
599                             mDrawableSizeLeft = mDrawableSizeEnd;
600                             mDrawableHeightLeft = mDrawableHeightEnd;
601                         }
602                         break;
603 
604                     case LAYOUT_DIRECTION_LTR:
605                     default:
606                         if (mOverride) {
607                             mShowing[Drawables.LEFT] = mDrawableStart;
608                             mDrawableSizeLeft = mDrawableSizeStart;
609                             mDrawableHeightLeft = mDrawableHeightStart;
610 
611                             mShowing[Drawables.RIGHT] = mDrawableEnd;
612                             mDrawableSizeRight = mDrawableSizeEnd;
613                             mDrawableHeightRight = mDrawableHeightEnd;
614                         }
615                         break;
616                 }
617             }
618 
619             applyErrorDrawableIfNeeded(layoutDirection);
620 
621             return mShowing[Drawables.LEFT] != previousLeft
622                     || mShowing[Drawables.RIGHT] != previousRight;
623         }
624 
625         public void setErrorDrawable(Drawable dr, TextView tv) {
626             if (mDrawableError != dr && mDrawableError != null) {
627                 mDrawableError.setCallback(null);
628             }
629             mDrawableError = dr;
630 
631             if (mDrawableError != null) {
632                 final Rect compoundRect = mCompoundRect;
633                 final int[] state = tv.getDrawableState();
634 
635                 mDrawableError.setState(state);
636                 mDrawableError.copyBounds(compoundRect);
637                 mDrawableError.setCallback(tv);
638                 mDrawableSizeError = compoundRect.width();
639                 mDrawableHeightError = compoundRect.height();
640             } else {
641                 mDrawableSizeError = mDrawableHeightError = 0;
642             }
643         }
644 
645         private void applyErrorDrawableIfNeeded(int layoutDirection) {
646             // first restore the initial state if needed
647             switch (mDrawableSaved) {
648                 case DRAWABLE_LEFT:
649                     mShowing[Drawables.LEFT] = mDrawableTemp;
650                     mDrawableSizeLeft = mDrawableSizeTemp;
651                     mDrawableHeightLeft = mDrawableHeightTemp;
652                     break;
653                 case DRAWABLE_RIGHT:
654                     mShowing[Drawables.RIGHT] = mDrawableTemp;
655                     mDrawableSizeRight = mDrawableSizeTemp;
656                     mDrawableHeightRight = mDrawableHeightTemp;
657                     break;
658                 case DRAWABLE_NONE:
659                 default:
660             }
661             // then, if needed, assign the Error drawable to the correct location
662             if (mDrawableError != null) {
663                 switch(layoutDirection) {
664                     case LAYOUT_DIRECTION_RTL:
665                         mDrawableSaved = DRAWABLE_LEFT;
666 
667                         mDrawableTemp = mShowing[Drawables.LEFT];
668                         mDrawableSizeTemp = mDrawableSizeLeft;
669                         mDrawableHeightTemp = mDrawableHeightLeft;
670 
671                         mShowing[Drawables.LEFT] = mDrawableError;
672                         mDrawableSizeLeft = mDrawableSizeError;
673                         mDrawableHeightLeft = mDrawableHeightError;
674                         break;
675                     case LAYOUT_DIRECTION_LTR:
676                     default:
677                         mDrawableSaved = DRAWABLE_RIGHT;
678 
679                         mDrawableTemp = mShowing[Drawables.RIGHT];
680                         mDrawableSizeTemp = mDrawableSizeRight;
681                         mDrawableHeightTemp = mDrawableHeightRight;
682 
683                         mShowing[Drawables.RIGHT] = mDrawableError;
684                         mDrawableSizeRight = mDrawableSizeError;
685                         mDrawableHeightRight = mDrawableHeightError;
686                         break;
687                 }
688             }
689         }
690     }
691 
692     @UnsupportedAppUsage
693     Drawables mDrawables;
694 
695     @UnsupportedAppUsage
696     private CharWrapper mCharWrapper;
697 
698     @UnsupportedAppUsage(trackingBug = 124050217)
699     private Marquee mMarquee;
700     @UnsupportedAppUsage
701     private boolean mRestartMarquee;
702 
703     private int mMarqueeRepeatLimit = 3;
704 
705     private int mLastLayoutDirection = -1;
706 
707     /**
708      * On some devices the fading edges add a performance penalty if used
709      * extensively in the same layout. This mode indicates how the marquee
710      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
711      */
712     @UnsupportedAppUsage
713     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
714 
715     /**
716      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
717      * the layout that should be used when the mode switches.
718      */
719     @UnsupportedAppUsage
720     private Layout mSavedMarqueeModeLayout;
721 
722     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
723     @ViewDebug.ExportedProperty(category = "text")
724     @UnsupportedAppUsage
725     private @Nullable CharSequence mText;
726     private @Nullable Spannable mSpannable;
727     private @Nullable PrecomputedText mPrecomputed;
728 
729     @UnsupportedAppUsage
730     private CharSequence mTransformed;
731     @UnsupportedAppUsage
732     private BufferType mBufferType = BufferType.NORMAL;
733 
734     private CharSequence mHint;
735     @UnsupportedAppUsage
736     private Layout mHintLayout;
737 
738     private MovementMethod mMovement;
739 
740     private TransformationMethod mTransformation;
741     @UnsupportedAppUsage
742     private boolean mAllowTransformationLengthChange;
743     @UnsupportedAppUsage
744     private ChangeWatcher mChangeWatcher;
745 
746     @UnsupportedAppUsage(trackingBug = 123769451)
747     private ArrayList<TextWatcher> mListeners;
748 
749     // display attributes
750     @UnsupportedAppUsage
751     private final TextPaint mTextPaint;
752     @UnsupportedAppUsage
753     private boolean mUserSetTextScaleX;
754     @UnsupportedAppUsage
755     private Layout mLayout;
756     private boolean mLocalesChanged = false;
757     private int mTextSizeUnit = -1;
758 
759     // This is used to reflect the current user preference for changing font weight and making text
760     // more bold.
761     private int mFontWeightAdjustment;
762     private Typeface mOriginalTypeface;
763 
764     // True if setKeyListener() has been explicitly called
765     private boolean mListenerChanged = false;
766     // True if internationalized input should be used for numbers and date and time.
767     private final boolean mUseInternationalizedInput;
768     // True if fallback fonts that end up getting used should be allowed to affect line spacing.
769     /* package */ boolean mUseFallbackLineSpacing;
770     // True if the view text can be padded for compat reasons, when the view is translated.
771     private final boolean mUseTextPaddingForUiTranslation;
772 
773     @ViewDebug.ExportedProperty(category = "text")
774     @UnsupportedAppUsage
775     private int mGravity = Gravity.TOP | Gravity.START;
776     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
777     private boolean mHorizontallyScrolling;
778 
779     private int mAutoLinkMask;
780     private boolean mLinksClickable = true;
781 
782     @UnsupportedAppUsage
783     private float mSpacingMult = 1.0f;
784     @UnsupportedAppUsage
785     private float mSpacingAdd = 0.0f;
786 
787     private int mBreakStrategy;
788     private int mHyphenationFrequency;
789     private int mJustificationMode;
790 
791     @UnsupportedAppUsage
792     private int mMaximum = Integer.MAX_VALUE;
793     @UnsupportedAppUsage
794     private int mMaxMode = LINES;
795     @UnsupportedAppUsage
796     private int mMinimum = 0;
797     @UnsupportedAppUsage
798     private int mMinMode = LINES;
799 
800     @UnsupportedAppUsage
801     private int mOldMaximum = mMaximum;
802     @UnsupportedAppUsage
803     private int mOldMaxMode = mMaxMode;
804 
805     @UnsupportedAppUsage
806     private int mMaxWidth = Integer.MAX_VALUE;
807     @UnsupportedAppUsage
808     private int mMaxWidthMode = PIXELS;
809     @UnsupportedAppUsage
810     private int mMinWidth = 0;
811     @UnsupportedAppUsage
812     private int mMinWidthMode = PIXELS;
813 
814     @UnsupportedAppUsage
815     private boolean mSingleLine;
816     @UnsupportedAppUsage
817     private int mDesiredHeightAtMeasure = -1;
818     @UnsupportedAppUsage
819     private boolean mIncludePad = true;
820     private int mDeferScroll = -1;
821 
822     // tmp primitives, so we don't alloc them on each draw
823     private Rect mTempRect;
824     private long mLastScroll;
825     private Scroller mScroller;
826     private TextPaint mTempTextPaint;
827 
828     @UnsupportedAppUsage
829     private BoringLayout.Metrics mBoring;
830     @UnsupportedAppUsage
831     private BoringLayout.Metrics mHintBoring;
832     @UnsupportedAppUsage
833     private BoringLayout mSavedLayout;
834     @UnsupportedAppUsage
835     private BoringLayout mSavedHintLayout;
836 
837     @UnsupportedAppUsage
838     private TextDirectionHeuristic mTextDir;
839 
840     private InputFilter[] mFilters = NO_FILTERS;
841 
842     /**
843      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
844      * the same as {@link Process#myUserHandle()}.
845      *
846      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
847      * other apps may need to set this so that the system can use right user's resources and
848      * services such as input methods and spell checkers.</p>
849      *
850      * @see #setTextOperationUser(UserHandle)
851      */
852     @Nullable
853     private UserHandle mTextOperationUser;
854 
855     private volatile Locale mCurrentSpellCheckerLocaleCache;
856 
857     // It is possible to have a selection even when mEditor is null (programmatically set, like when
858     // a link is pressed). These highlight-related fields do not go in mEditor.
859     @UnsupportedAppUsage
860     int mHighlightColor = 0x6633B5E5;
861     private Path mHighlightPath;
862     @UnsupportedAppUsage
863     private final Paint mHighlightPaint;
864     @UnsupportedAppUsage
865     private boolean mHighlightPathBogus = true;
866 
867     // Although these fields are specific to editable text, they are not added to Editor because
868     // they are defined by the TextView's style and are theme-dependent.
869     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
870     int mCursorDrawableRes;
871     private Drawable mCursorDrawable;
872     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
873     // by removing it, but we would break apps targeting <= P that use it by reflection.
874     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
875     int mTextSelectHandleLeftRes;
876     private Drawable mTextSelectHandleLeft;
877     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
878     // by removing it, but we would break apps targeting <= P that use it by reflection.
879     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
880     int mTextSelectHandleRightRes;
881     private Drawable mTextSelectHandleRight;
882     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
883     // by removing it, but we would break apps targeting <= P that use it by reflection.
884     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
885     int mTextSelectHandleRes;
886     private Drawable mTextSelectHandle;
887     int mTextEditSuggestionItemLayout;
888     int mTextEditSuggestionContainerLayout;
889     int mTextEditSuggestionHighlightStyle;
890 
891     private static final int NO_POINTER_ID = -1;
892     /**
893      * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among
894      * TextView and the handle views which are rendered on popup windows.
895      */
896     private int mPrimePointerId = NO_POINTER_ID;
897 
898     /**
899      * Whether the prime pointer is from the event delivered to selection handle or insertion
900      * handle.
901      */
902     private boolean mIsPrimePointerFromHandleView;
903 
904     /**
905      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
906      * See {@link #createEditorIfNeeded()}.
907      */
908     @UnsupportedAppUsage
909     private Editor mEditor;
910 
911     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
912     private static final int DEVICE_PROVISIONED_NO = 1;
913     private static final int DEVICE_PROVISIONED_YES = 2;
914 
915     /**
916      * Some special options such as sharing selected text should only be shown if the device
917      * is provisioned. Only check the provisioned state once for a given view instance.
918      */
919     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
920 
921     /**
922      * The TextView does not auto-size text (default).
923      */
924     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
925 
926     /**
927      * The TextView scales text size both horizontally and vertically to fit within the
928      * container.
929      */
930     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
931 
932     /** @hide */
933     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
934             AUTO_SIZE_TEXT_TYPE_NONE,
935             AUTO_SIZE_TEXT_TYPE_UNIFORM
936     })
937     @Retention(RetentionPolicy.SOURCE)
938     public @interface AutoSizeTextType {}
939     // Default minimum size for auto-sizing text in scaled pixels.
940     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
941     // Default maximum size for auto-sizing text in scaled pixels.
942     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
943     // Default value for the step size in pixels.
944     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
945     // Use this to specify that any of the auto-size configuration int values have not been set.
946     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
947     // Auto-size text type.
948     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
949     // Specify if auto-size text is needed.
950     private boolean mNeedsAutoSizeText = false;
951     // Step size for auto-sizing in pixels.
952     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
953     // Minimum text size for auto-sizing in pixels.
954     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
955     // Maximum text size for auto-sizing in pixels.
956     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
957     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
958     // when auto-sizing text.
959     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
960     // Specifies whether auto-size should use the provided auto size steps set or if it should
961     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
962     // mAutoSizeStepGranularityInPx.
963     private boolean mHasPresetAutoSizeValues = false;
964 
965     // Autofill-related attributes
966     //
967     // Indicates whether the text was set statically or dynamically, so it can be used to
968     // sanitize autofill requests.
969     private boolean mTextSetFromXmlOrResourceId = false;
970     // Resource id used to set the text.
971     private @StringRes int mTextId = Resources.ID_NULL;
972     // Resource id used to set the hint.
973     private @StringRes int mHintId = Resources.ID_NULL;
974     //
975     // End of autofill-related attributes
976 
977     /**
978      * Kick-start the font cache for the zygote process (to pay the cost of
979      * initializing freetype for our default font only once).
980      * @hide
981      */
982     public static void preloadFontCache() {
983         if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
984             return;
985         }
986         Paint p = new Paint();
987         p.setAntiAlias(true);
988         // Ensure that the Typeface is loaded here.
989         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
990         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
991         // since Paint.measureText can not be called without Typeface static initializer.
992         p.setTypeface(Typeface.DEFAULT);
993         // We don't care about the result, just the side-effect of measuring.
994         p.measureText("H");
995     }
996 
997     /**
998      * Interface definition for a callback to be invoked when an action is
999      * performed on the editor.
1000      */
1001     public interface OnEditorActionListener {
1002         /**
1003          * Called when an action is being performed.
1004          *
1005          * @param v The view that was clicked.
1006          * @param actionId Identifier of the action.  This will be either the
1007          * identifier you supplied, or {@link EditorInfo#IME_NULL
1008          * EditorInfo.IME_NULL} if being called due to the enter key
1009          * being pressed.
1010          * @param event If triggered by an enter key, this is the event;
1011          * otherwise, this is null.
1012          * @return Return true if you have consumed the action, else false.
1013          */
1014         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
1015     }
1016 
1017     public TextView(Context context) {
1018         this(context, null);
1019     }
1020 
1021     public TextView(Context context, @Nullable AttributeSet attrs) {
1022         this(context, attrs, com.android.internal.R.attr.textViewStyle);
1023     }
1024 
1025     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
1026         this(context, attrs, defStyleAttr, 0);
1027     }
1028 
1029     @SuppressWarnings("deprecation")
1030     public TextView(
1031             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1032         super(context, attrs, defStyleAttr, defStyleRes);
1033 
1034         // TextView is important by default, unless app developer overrode attribute.
1035         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
1036             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
1037         }
1038         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
1039             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
1040         }
1041 
1042         setTextInternal("");
1043 
1044         final Resources res = getResources();
1045         final CompatibilityInfo compat = res.getCompatibilityInfo();
1046 
1047         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
1048         mTextPaint.density = res.getDisplayMetrics().density;
1049         mTextPaint.setCompatibilityScaling(compat.applicationScale);
1050 
1051         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1052         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
1053 
1054         mMovement = getDefaultMovementMethod();
1055 
1056         mTransformation = null;
1057 
1058         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
1059         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
1060         attributes.mTextSize = 15;
1061         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1062         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1063         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1064 
1065         final Resources.Theme theme = context.getTheme();
1066 
1067         /*
1068          * Look the appearance up without checking first if it exists because
1069          * almost every TextView has one and it greatly simplifies the logic
1070          * to be able to parse the appearance first and then let specific tags
1071          * for this View override it.
1072          */
1073         TypedArray a = theme.obtainStyledAttributes(attrs,
1074                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1075         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1076                 attrs, a, defStyleAttr, defStyleRes);
1077         TypedArray appearance = null;
1078         int ap = a.getResourceId(
1079                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1080         a.recycle();
1081         if (ap != -1) {
1082             appearance = theme.obtainStyledAttributes(
1083                     ap, com.android.internal.R.styleable.TextAppearance);
1084             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1085                     null, appearance, 0, ap);
1086         }
1087         if (appearance != null) {
1088             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1089             attributes.mFontFamilyExplicit = false;
1090             appearance.recycle();
1091         }
1092 
1093         boolean editable = getDefaultEditable();
1094         CharSequence inputMethod = null;
1095         int numeric = 0;
1096         CharSequence digits = null;
1097         boolean phone = false;
1098         boolean autotext = false;
1099         int autocap = -1;
1100         int buffertype = 0;
1101         boolean selectallonfocus = false;
1102         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1103                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1104         ColorStateList drawableTint = null;
1105         BlendMode drawableTintMode = null;
1106         int drawablePadding = 0;
1107         int ellipsize = ELLIPSIZE_NOT_SET;
1108         boolean singleLine = false;
1109         int maxlength = -1;
1110         CharSequence text = "";
1111         CharSequence hint = null;
1112         boolean password = false;
1113         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1114         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1115         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1116         int inputType = EditorInfo.TYPE_NULL;
1117         a = theme.obtainStyledAttributes(
1118                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1119         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1120                 defStyleAttr, defStyleRes);
1121         int firstBaselineToTopHeight = -1;
1122         int lastBaselineToBottomHeight = -1;
1123         int lineHeight = -1;
1124 
1125         readTextAppearance(context, a, attributes, true /* styleArray */);
1126 
1127         int n = a.getIndexCount();
1128 
1129         // Must set id in a temporary variable because it will be reset by setText()
1130         boolean textIsSetFromXml = false;
1131         for (int i = 0; i < n; i++) {
1132             int attr = a.getIndex(i);
1133 
1134             switch (attr) {
1135                 case com.android.internal.R.styleable.TextView_editable:
1136                     editable = a.getBoolean(attr, editable);
1137                     break;
1138 
1139                 case com.android.internal.R.styleable.TextView_inputMethod:
1140                     inputMethod = a.getText(attr);
1141                     break;
1142 
1143                 case com.android.internal.R.styleable.TextView_numeric:
1144                     numeric = a.getInt(attr, numeric);
1145                     break;
1146 
1147                 case com.android.internal.R.styleable.TextView_digits:
1148                     digits = a.getText(attr);
1149                     break;
1150 
1151                 case com.android.internal.R.styleable.TextView_phoneNumber:
1152                     phone = a.getBoolean(attr, phone);
1153                     break;
1154 
1155                 case com.android.internal.R.styleable.TextView_autoText:
1156                     autotext = a.getBoolean(attr, autotext);
1157                     break;
1158 
1159                 case com.android.internal.R.styleable.TextView_capitalize:
1160                     autocap = a.getInt(attr, autocap);
1161                     break;
1162 
1163                 case com.android.internal.R.styleable.TextView_bufferType:
1164                     buffertype = a.getInt(attr, buffertype);
1165                     break;
1166 
1167                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1168                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1169                     break;
1170 
1171                 case com.android.internal.R.styleable.TextView_autoLink:
1172                     mAutoLinkMask = a.getInt(attr, 0);
1173                     break;
1174 
1175                 case com.android.internal.R.styleable.TextView_linksClickable:
1176                     mLinksClickable = a.getBoolean(attr, true);
1177                     break;
1178 
1179                 case com.android.internal.R.styleable.TextView_drawableLeft:
1180                     drawableLeft = a.getDrawable(attr);
1181                     break;
1182 
1183                 case com.android.internal.R.styleable.TextView_drawableTop:
1184                     drawableTop = a.getDrawable(attr);
1185                     break;
1186 
1187                 case com.android.internal.R.styleable.TextView_drawableRight:
1188                     drawableRight = a.getDrawable(attr);
1189                     break;
1190 
1191                 case com.android.internal.R.styleable.TextView_drawableBottom:
1192                     drawableBottom = a.getDrawable(attr);
1193                     break;
1194 
1195                 case com.android.internal.R.styleable.TextView_drawableStart:
1196                     drawableStart = a.getDrawable(attr);
1197                     break;
1198 
1199                 case com.android.internal.R.styleable.TextView_drawableEnd:
1200                     drawableEnd = a.getDrawable(attr);
1201                     break;
1202 
1203                 case com.android.internal.R.styleable.TextView_drawableTint:
1204                     drawableTint = a.getColorStateList(attr);
1205                     break;
1206 
1207                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1208                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1209                             drawableTintMode);
1210                     break;
1211 
1212                 case com.android.internal.R.styleable.TextView_drawablePadding:
1213                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1214                     break;
1215 
1216                 case com.android.internal.R.styleable.TextView_maxLines:
1217                     setMaxLines(a.getInt(attr, -1));
1218                     break;
1219 
1220                 case com.android.internal.R.styleable.TextView_maxHeight:
1221                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1222                     break;
1223 
1224                 case com.android.internal.R.styleable.TextView_lines:
1225                     setLines(a.getInt(attr, -1));
1226                     break;
1227 
1228                 case com.android.internal.R.styleable.TextView_height:
1229                     setHeight(a.getDimensionPixelSize(attr, -1));
1230                     break;
1231 
1232                 case com.android.internal.R.styleable.TextView_minLines:
1233                     setMinLines(a.getInt(attr, -1));
1234                     break;
1235 
1236                 case com.android.internal.R.styleable.TextView_minHeight:
1237                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1238                     break;
1239 
1240                 case com.android.internal.R.styleable.TextView_maxEms:
1241                     setMaxEms(a.getInt(attr, -1));
1242                     break;
1243 
1244                 case com.android.internal.R.styleable.TextView_maxWidth:
1245                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1246                     break;
1247 
1248                 case com.android.internal.R.styleable.TextView_ems:
1249                     setEms(a.getInt(attr, -1));
1250                     break;
1251 
1252                 case com.android.internal.R.styleable.TextView_width:
1253                     setWidth(a.getDimensionPixelSize(attr, -1));
1254                     break;
1255 
1256                 case com.android.internal.R.styleable.TextView_minEms:
1257                     setMinEms(a.getInt(attr, -1));
1258                     break;
1259 
1260                 case com.android.internal.R.styleable.TextView_minWidth:
1261                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1262                     break;
1263 
1264                 case com.android.internal.R.styleable.TextView_gravity:
1265                     setGravity(a.getInt(attr, -1));
1266                     break;
1267 
1268                 case com.android.internal.R.styleable.TextView_hint:
1269                     mHintId = a.getResourceId(attr, Resources.ID_NULL);
1270                     hint = a.getText(attr);
1271                     break;
1272 
1273                 case com.android.internal.R.styleable.TextView_text:
1274                     textIsSetFromXml = true;
1275                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1276                     text = a.getText(attr);
1277                     break;
1278 
1279                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1280                     if (a.getBoolean(attr, false)) {
1281                         setHorizontallyScrolling(true);
1282                     }
1283                     break;
1284 
1285                 case com.android.internal.R.styleable.TextView_singleLine:
1286                     singleLine = a.getBoolean(attr, singleLine);
1287                     break;
1288 
1289                 case com.android.internal.R.styleable.TextView_ellipsize:
1290                     ellipsize = a.getInt(attr, ellipsize);
1291                     break;
1292 
1293                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1294                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1295                     break;
1296 
1297                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1298                     if (!a.getBoolean(attr, true)) {
1299                         setIncludeFontPadding(false);
1300                     }
1301                     break;
1302 
1303                 case com.android.internal.R.styleable.TextView_cursorVisible:
1304                     if (!a.getBoolean(attr, true)) {
1305                         setCursorVisible(false);
1306                     }
1307                     break;
1308 
1309                 case com.android.internal.R.styleable.TextView_maxLength:
1310                     maxlength = a.getInt(attr, -1);
1311                     break;
1312 
1313                 case com.android.internal.R.styleable.TextView_textScaleX:
1314                     setTextScaleX(a.getFloat(attr, 1.0f));
1315                     break;
1316 
1317                 case com.android.internal.R.styleable.TextView_freezesText:
1318                     mFreezesText = a.getBoolean(attr, false);
1319                     break;
1320 
1321                 case com.android.internal.R.styleable.TextView_enabled:
1322                     setEnabled(a.getBoolean(attr, isEnabled()));
1323                     break;
1324 
1325                 case com.android.internal.R.styleable.TextView_password:
1326                     password = a.getBoolean(attr, password);
1327                     break;
1328 
1329                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1330                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1331                     break;
1332 
1333                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1334                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1335                     break;
1336 
1337                 case com.android.internal.R.styleable.TextView_inputType:
1338                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1339                     break;
1340 
1341                 case com.android.internal.R.styleable.TextView_allowUndo:
1342                     createEditorIfNeeded();
1343                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1344                     break;
1345 
1346                 case com.android.internal.R.styleable.TextView_imeOptions:
1347                     createEditorIfNeeded();
1348                     mEditor.createInputContentTypeIfNeeded();
1349                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1350                             mEditor.mInputContentType.imeOptions);
1351                     break;
1352 
1353                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1354                     createEditorIfNeeded();
1355                     mEditor.createInputContentTypeIfNeeded();
1356                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1357                     break;
1358 
1359                 case com.android.internal.R.styleable.TextView_imeActionId:
1360                     createEditorIfNeeded();
1361                     mEditor.createInputContentTypeIfNeeded();
1362                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1363                             mEditor.mInputContentType.imeActionId);
1364                     break;
1365 
1366                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1367                     setPrivateImeOptions(a.getString(attr));
1368                     break;
1369 
1370                 case com.android.internal.R.styleable.TextView_editorExtras:
1371                     try {
1372                         setInputExtras(a.getResourceId(attr, 0));
1373                     } catch (XmlPullParserException e) {
1374                         Log.w(LOG_TAG, "Failure reading input extras", e);
1375                     } catch (IOException e) {
1376                         Log.w(LOG_TAG, "Failure reading input extras", e);
1377                     }
1378                     break;
1379 
1380                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1381                     mCursorDrawableRes = a.getResourceId(attr, 0);
1382                     break;
1383 
1384                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1385                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1386                     break;
1387 
1388                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1389                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1390                     break;
1391 
1392                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1393                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1394                     break;
1395 
1396                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1397                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1398                     break;
1399 
1400                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1401                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1402                     break;
1403 
1404                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1405                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1406                     break;
1407 
1408                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1409                     setTextIsSelectable(a.getBoolean(attr, false));
1410                     break;
1411 
1412                 case com.android.internal.R.styleable.TextView_breakStrategy:
1413                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1414                     break;
1415 
1416                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1417                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1418                     break;
1419 
1420                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1421                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1422                     break;
1423 
1424                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1425                     autoSizeStepGranularityInPx = a.getDimension(attr,
1426                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1427                     break;
1428 
1429                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1430                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1431                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1432                     break;
1433 
1434                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1435                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1436                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1437                     break;
1438 
1439                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1440                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1441                     if (autoSizeStepSizeArrayResId > 0) {
1442                         final TypedArray autoSizePresetTextSizes = a.getResources()
1443                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1444                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1445                         autoSizePresetTextSizes.recycle();
1446                     }
1447                     break;
1448                 case com.android.internal.R.styleable.TextView_justificationMode:
1449                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1450                     break;
1451 
1452                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1453                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1454                     break;
1455 
1456                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1457                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1458                     break;
1459 
1460                 case com.android.internal.R.styleable.TextView_lineHeight:
1461                     lineHeight = a.getDimensionPixelSize(attr, -1);
1462                     break;
1463             }
1464         }
1465 
1466         a.recycle();
1467 
1468         BufferType bufferType = BufferType.EDITABLE;
1469 
1470         final int variation =
1471                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1472         final boolean passwordInputType = variation
1473                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1474         final boolean webPasswordInputType = variation
1475                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1476         final boolean numberPasswordInputType = variation
1477                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1478 
1479         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1480         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1481         mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
1482         // TODO(b/179693024): Use a ChangeId instead.
1483         mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R;
1484 
1485         if (inputMethod != null) {
1486             Class<?> c;
1487 
1488             try {
1489                 c = Class.forName(inputMethod.toString());
1490             } catch (ClassNotFoundException ex) {
1491                 throw new RuntimeException(ex);
1492             }
1493 
1494             try {
1495                 createEditorIfNeeded();
1496                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1497             } catch (InstantiationException ex) {
1498                 throw new RuntimeException(ex);
1499             } catch (IllegalAccessException ex) {
1500                 throw new RuntimeException(ex);
1501             }
1502             try {
1503                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1504                         ? inputType
1505                         : mEditor.mKeyListener.getInputType();
1506             } catch (IncompatibleClassChangeError e) {
1507                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1508             }
1509         } else if (digits != null) {
1510             createEditorIfNeeded();
1511             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1512             // If no input type was specified, we will default to generic
1513             // text, since we can't tell the IME about the set of digits
1514             // that was selected.
1515             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1516                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1517         } else if (inputType != EditorInfo.TYPE_NULL) {
1518             setInputType(inputType, true);
1519             // If set, the input type overrides what was set using the deprecated singleLine flag.
1520             singleLine = !isMultilineInputType(inputType);
1521         } else if (phone) {
1522             createEditorIfNeeded();
1523             mEditor.mKeyListener = DialerKeyListener.getInstance();
1524             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1525         } else if (numeric != 0) {
1526             createEditorIfNeeded();
1527             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1528                     null,  // locale
1529                     (numeric & SIGNED) != 0,
1530                     (numeric & DECIMAL) != 0);
1531             inputType = mEditor.mKeyListener.getInputType();
1532             mEditor.mInputType = inputType;
1533         } else if (autotext || autocap != -1) {
1534             TextKeyListener.Capitalize cap;
1535 
1536             inputType = EditorInfo.TYPE_CLASS_TEXT;
1537 
1538             switch (autocap) {
1539                 case 1:
1540                     cap = TextKeyListener.Capitalize.SENTENCES;
1541                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1542                     break;
1543 
1544                 case 2:
1545                     cap = TextKeyListener.Capitalize.WORDS;
1546                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1547                     break;
1548 
1549                 case 3:
1550                     cap = TextKeyListener.Capitalize.CHARACTERS;
1551                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1552                     break;
1553 
1554                 default:
1555                     cap = TextKeyListener.Capitalize.NONE;
1556                     break;
1557             }
1558 
1559             createEditorIfNeeded();
1560             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1561             mEditor.mInputType = inputType;
1562         } else if (editable) {
1563             createEditorIfNeeded();
1564             mEditor.mKeyListener = TextKeyListener.getInstance();
1565             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1566         } else if (isTextSelectable()) {
1567             // Prevent text changes from keyboard.
1568             if (mEditor != null) {
1569                 mEditor.mKeyListener = null;
1570                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1571             }
1572             bufferType = BufferType.SPANNABLE;
1573             // So that selection can be changed using arrow keys and touch is handled.
1574             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1575         } else {
1576             if (mEditor != null) mEditor.mKeyListener = null;
1577 
1578             switch (buffertype) {
1579                 case 0:
1580                     bufferType = BufferType.NORMAL;
1581                     break;
1582                 case 1:
1583                     bufferType = BufferType.SPANNABLE;
1584                     break;
1585                 case 2:
1586                     bufferType = BufferType.EDITABLE;
1587                     break;
1588             }
1589         }
1590 
1591         if (mEditor != null) {
1592             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1593                     numberPasswordInputType);
1594         }
1595 
1596         if (selectallonfocus) {
1597             createEditorIfNeeded();
1598             mEditor.mSelectAllOnFocus = true;
1599 
1600             if (bufferType == BufferType.NORMAL) {
1601                 bufferType = BufferType.SPANNABLE;
1602             }
1603         }
1604 
1605         // Set up the tint (if needed) before setting the drawables so that it
1606         // gets applied correctly.
1607         if (drawableTint != null || drawableTintMode != null) {
1608             if (mDrawables == null) {
1609                 mDrawables = new Drawables(context);
1610             }
1611             if (drawableTint != null) {
1612                 mDrawables.mTintList = drawableTint;
1613                 mDrawables.mHasTint = true;
1614             }
1615             if (drawableTintMode != null) {
1616                 mDrawables.mBlendMode = drawableTintMode;
1617                 mDrawables.mHasTintMode = true;
1618             }
1619         }
1620 
1621         // This call will save the initial left/right drawables
1622         setCompoundDrawablesWithIntrinsicBounds(
1623                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1624         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1625         setCompoundDrawablePadding(drawablePadding);
1626 
1627         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1628         // of lines of height are unchanged for multi-line TextViews.
1629         setInputTypeSingleLine(singleLine);
1630         applySingleLine(singleLine, singleLine, singleLine,
1631                 // Does not apply automated max length filter since length filter will be resolved
1632                 // later in this function.
1633                 false
1634         );
1635 
1636         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1637             ellipsize = ELLIPSIZE_END;
1638         }
1639 
1640         switch (ellipsize) {
1641             case ELLIPSIZE_START:
1642                 setEllipsize(TextUtils.TruncateAt.START);
1643                 break;
1644             case ELLIPSIZE_MIDDLE:
1645                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1646                 break;
1647             case ELLIPSIZE_END:
1648                 setEllipsize(TextUtils.TruncateAt.END);
1649                 break;
1650             case ELLIPSIZE_MARQUEE:
1651                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1652                     setHorizontalFadingEdgeEnabled(true);
1653                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1654                 } else {
1655                     setHorizontalFadingEdgeEnabled(false);
1656                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1657                 }
1658                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1659                 break;
1660         }
1661 
1662         final boolean isPassword = password || passwordInputType || webPasswordInputType
1663                 || numberPasswordInputType;
1664         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1665                 && (mEditor.mInputType
1666                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1667                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1668         if (isMonospaceEnforced) {
1669             attributes.mTypefaceIndex = MONOSPACE;
1670         }
1671 
1672         mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment;
1673         applyTextAppearance(attributes);
1674 
1675         if (isPassword) {
1676             setTransformationMethod(PasswordTransformationMethod.getInstance());
1677         }
1678 
1679         // For addressing b/145128646
1680         // For the performance reason, we limit characters for single line text field.
1681         if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) {
1682             mSingleLineLengthFilter = new InputFilter.LengthFilter(
1683                 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
1684         }
1685 
1686         if (mSingleLineLengthFilter != null) {
1687             setFilters(new InputFilter[] { mSingleLineLengthFilter });
1688         } else if (maxlength >= 0) {
1689             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1690         } else {
1691             setFilters(NO_FILTERS);
1692         }
1693 
1694         setText(text, bufferType);
1695         if (mText == null) {
1696             mText = "";
1697         }
1698         if (mTransformed == null) {
1699             mTransformed = "";
1700         }
1701 
1702         if (textIsSetFromXml) {
1703             mTextSetFromXmlOrResourceId = true;
1704         }
1705 
1706         if (hint != null) setHint(hint);
1707 
1708         /*
1709          * Views are not normally clickable unless specified to be.
1710          * However, TextViews that have input or movement methods *are*
1711          * clickable by default. By setting clickable here, we implicitly set focusable as well
1712          * if not overridden by the developer.
1713          */
1714         a = context.obtainStyledAttributes(
1715                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1716         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1717         boolean clickable = canInputOrMove || isClickable();
1718         boolean longClickable = canInputOrMove || isLongClickable();
1719         int focusable = getFocusable();
1720 
1721         n = a.getIndexCount();
1722         for (int i = 0; i < n; i++) {
1723             int attr = a.getIndex(i);
1724 
1725             switch (attr) {
1726                 case com.android.internal.R.styleable.View_focusable:
1727                     TypedValue val = new TypedValue();
1728                     if (a.getValue(attr, val)) {
1729                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1730                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1731                                 : val.data;
1732                     }
1733                     break;
1734 
1735                 case com.android.internal.R.styleable.View_clickable:
1736                     clickable = a.getBoolean(attr, clickable);
1737                     break;
1738 
1739                 case com.android.internal.R.styleable.View_longClickable:
1740                     longClickable = a.getBoolean(attr, longClickable);
1741                     break;
1742             }
1743         }
1744         a.recycle();
1745 
1746         // Some apps were relying on the undefined behavior of focusable winning over
1747         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1748         // when starting with EditText and setting only focusable=false). To keep those apps from
1749         // breaking, re-apply the focusable attribute here.
1750         if (focusable != getFocusable()) {
1751             setFocusable(focusable);
1752         }
1753         setClickable(clickable);
1754         setLongClickable(longClickable);
1755 
1756         if (mEditor != null) mEditor.prepareCursorControllers();
1757 
1758         // If not explicitly specified this view is important for accessibility.
1759         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1760             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1761         }
1762 
1763         if (supportsAutoSizeText()) {
1764             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1765                 // If uniform auto-size has been specified but preset values have not been set then
1766                 // replace the auto-size configuration values that have not been specified with the
1767                 // defaults.
1768                 if (!mHasPresetAutoSizeValues) {
1769                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1770 
1771                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1772                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1773                                 TypedValue.COMPLEX_UNIT_SP,
1774                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1775                                 displayMetrics);
1776                     }
1777 
1778                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1779                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1780                                 TypedValue.COMPLEX_UNIT_SP,
1781                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1782                                 displayMetrics);
1783                     }
1784 
1785                     if (autoSizeStepGranularityInPx
1786                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1787                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1788                     }
1789 
1790                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1791                             autoSizeMaxTextSizeInPx,
1792                             autoSizeStepGranularityInPx);
1793                 }
1794 
1795                 setupAutoSizeText();
1796             }
1797         } else {
1798             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1799         }
1800 
1801         if (firstBaselineToTopHeight >= 0) {
1802             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1803         }
1804         if (lastBaselineToBottomHeight >= 0) {
1805             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1806         }
1807         if (lineHeight >= 0) {
1808             setLineHeight(lineHeight);
1809         }
1810     }
1811 
1812     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)1813     private void setTextInternal(@Nullable CharSequence text) {
1814         mText = text;
1815         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
1816         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
1817     }
1818 
1819     /**
1820      * Specify whether this widget should automatically scale the text to try to perfectly fit
1821      * within the layout bounds by using the default auto-size configuration.
1822      *
1823      * @param autoSizeTextType the type of auto-size. Must be one of
1824      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1825      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1826      *
1827      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1828      *
1829      * @attr ref android.R.styleable#TextView_autoSizeTextType
1830      *
1831      * @see #getAutoSizeTextType()
1832      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1833     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1834         if (supportsAutoSizeText()) {
1835             switch (autoSizeTextType) {
1836                 case AUTO_SIZE_TEXT_TYPE_NONE:
1837                     clearAutoSizeConfiguration();
1838                     break;
1839                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1840                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1841                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1842                             TypedValue.COMPLEX_UNIT_SP,
1843                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1844                             displayMetrics);
1845                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1846                             TypedValue.COMPLEX_UNIT_SP,
1847                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1848                             displayMetrics);
1849 
1850                     validateAndSetAutoSizeTextTypeUniformConfiguration(
1851                             autoSizeMinTextSizeInPx,
1852                             autoSizeMaxTextSizeInPx,
1853                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1854                     if (setupAutoSizeText()) {
1855                         autoSizeText();
1856                         invalidate();
1857                     }
1858                     break;
1859                 default:
1860                     throw new IllegalArgumentException(
1861                             "Unknown auto-size text type: " + autoSizeTextType);
1862             }
1863         }
1864     }
1865 
1866     /**
1867      * Specify whether this widget should automatically scale the text to try to perfectly fit
1868      * within the layout bounds. If all the configuration params are valid the type of auto-size is
1869      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1870      *
1871      * @param autoSizeMinTextSize the minimum text size available for auto-size
1872      * @param autoSizeMaxTextSize the maximum text size available for auto-size
1873      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1874      *                                the minimum and maximum text size in order to build the set of
1875      *                                text sizes the system uses to choose from when auto-sizing
1876      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1877      *             possible dimension units
1878      *
1879      * @throws IllegalArgumentException if any of the configuration params are invalid.
1880      *
1881      * @attr ref android.R.styleable#TextView_autoSizeTextType
1882      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1883      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1884      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1885      *
1886      * @see #setAutoSizeTextTypeWithDefaults(int)
1887      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1888      * @see #getAutoSizeMinTextSize()
1889      * @see #getAutoSizeMaxTextSize()
1890      * @see #getAutoSizeStepGranularity()
1891      * @see #getAutoSizeTextAvailableSizes()
1892      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1893     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1894             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1895         if (supportsAutoSizeText()) {
1896             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1897             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1898                     unit, autoSizeMinTextSize, displayMetrics);
1899             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1900                     unit, autoSizeMaxTextSize, displayMetrics);
1901             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1902                     unit, autoSizeStepGranularity, displayMetrics);
1903 
1904             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1905                     autoSizeMaxTextSizeInPx,
1906                     autoSizeStepGranularityInPx);
1907 
1908             if (setupAutoSizeText()) {
1909                 autoSizeText();
1910                 invalidate();
1911             }
1912         }
1913     }
1914 
1915     /**
1916      * Specify whether this widget should automatically scale the text to try to perfectly fit
1917      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1918      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1919      *
1920      * @param presetSizes an {@code int} array of sizes in pixels
1921      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1922      *             the possible dimension units
1923      *
1924      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1925      *
1926      * @attr ref android.R.styleable#TextView_autoSizeTextType
1927      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1928      *
1929      * @see #setAutoSizeTextTypeWithDefaults(int)
1930      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1931      * @see #getAutoSizeMinTextSize()
1932      * @see #getAutoSizeMaxTextSize()
1933      * @see #getAutoSizeTextAvailableSizes()
1934      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1935     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1936         if (supportsAutoSizeText()) {
1937             final int presetSizesLength = presetSizes.length;
1938             if (presetSizesLength > 0) {
1939                 int[] presetSizesInPx = new int[presetSizesLength];
1940 
1941                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1942                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1943                 } else {
1944                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1945                     // Convert all to sizes to pixels.
1946                     for (int i = 0; i < presetSizesLength; i++) {
1947                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
1948                             presetSizes[i], displayMetrics));
1949                     }
1950                 }
1951 
1952                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1953                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
1954                     throw new IllegalArgumentException("None of the preset sizes is valid: "
1955                             + Arrays.toString(presetSizes));
1956                 }
1957             } else {
1958                 mHasPresetAutoSizeValues = false;
1959             }
1960 
1961             if (setupAutoSizeText()) {
1962                 autoSizeText();
1963                 invalidate();
1964             }
1965         }
1966     }
1967 
1968     /**
1969      * Returns the type of auto-size set for this widget.
1970      *
1971      * @return an {@code int} corresponding to one of the auto-size types:
1972      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1973      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1974      *
1975      * @attr ref android.R.styleable#TextView_autoSizeTextType
1976      *
1977      * @see #setAutoSizeTextTypeWithDefaults(int)
1978      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1979      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1980      */
1981     @InspectableProperty(enumMapping = {
1982             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
1983             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
1984     })
1985     @AutoSizeTextType
getAutoSizeTextType()1986     public int getAutoSizeTextType() {
1987         return mAutoSizeTextType;
1988     }
1989 
1990     /**
1991      * @return the current auto-size step granularity in pixels.
1992      *
1993      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1994      *
1995      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1996      */
1997     @InspectableProperty
getAutoSizeStepGranularity()1998     public int getAutoSizeStepGranularity() {
1999         return Math.round(mAutoSizeStepGranularityInPx);
2000     }
2001 
2002     /**
2003      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
2004      *         if auto-size has not been configured this function returns {@code -1}.
2005      *
2006      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2007      *
2008      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2009      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2010      */
2011     @InspectableProperty
getAutoSizeMinTextSize()2012     public int getAutoSizeMinTextSize() {
2013         return Math.round(mAutoSizeMinTextSizeInPx);
2014     }
2015 
2016     /**
2017      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
2018      *         if auto-size has not been configured this function returns {@code -1}.
2019      *
2020      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2021      *
2022      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2023      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2024      */
2025     @InspectableProperty
getAutoSizeMaxTextSize()2026     public int getAutoSizeMaxTextSize() {
2027         return Math.round(mAutoSizeMaxTextSizeInPx);
2028     }
2029 
2030     /**
2031      * @return the current auto-size {@code int} sizes array (in pixels).
2032      *
2033      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2034      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2035      */
getAutoSizeTextAvailableSizes()2036     public int[] getAutoSizeTextAvailableSizes() {
2037         return mAutoSizeTextSizesInPx;
2038     }
2039 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)2040     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
2041         final int textSizesLength = textSizes.length();
2042         final int[] parsedSizes = new int[textSizesLength];
2043 
2044         if (textSizesLength > 0) {
2045             for (int i = 0; i < textSizesLength; i++) {
2046                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
2047             }
2048             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
2049             setupAutoSizeUniformPresetSizesConfiguration();
2050         }
2051     }
2052 
setupAutoSizeUniformPresetSizesConfiguration()2053     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
2054         final int sizesLength = mAutoSizeTextSizesInPx.length;
2055         mHasPresetAutoSizeValues = sizesLength > 0;
2056         if (mHasPresetAutoSizeValues) {
2057             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2058             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
2059             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
2060             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2061         }
2062         return mHasPresetAutoSizeValues;
2063     }
2064 
2065     /**
2066      * If all params are valid then save the auto-size configuration.
2067      *
2068      * @throws IllegalArgumentException if any of the params are invalid
2069      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2070     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
2071             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
2072         // First validate.
2073         if (autoSizeMinTextSizeInPx <= 0) {
2074             throw new IllegalArgumentException("Minimum auto-size text size ("
2075                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
2076         }
2077 
2078         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2079             throw new IllegalArgumentException("Maximum auto-size text size ("
2080                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2081                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2082         }
2083 
2084         if (autoSizeStepGranularityInPx <= 0) {
2085             throw new IllegalArgumentException("The auto-size step granularity ("
2086                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2087         }
2088 
2089         // All good, persist the configuration.
2090         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2091         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2092         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2093         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2094         mHasPresetAutoSizeValues = false;
2095     }
2096 
clearAutoSizeConfiguration()2097     private void clearAutoSizeConfiguration() {
2098         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2099         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2100         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2101         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2102         mAutoSizeTextSizesInPx = EmptyArray.INT;
2103         mNeedsAutoSizeText = false;
2104     }
2105 
2106     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)2107     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2108         final int presetValuesLength = presetValues.length;
2109         if (presetValuesLength == 0) {
2110             return presetValues;
2111         }
2112         Arrays.sort(presetValues);
2113 
2114         final IntArray uniqueValidSizes = new IntArray();
2115         for (int i = 0; i < presetValuesLength; i++) {
2116             final int currentPresetValue = presetValues[i];
2117 
2118             if (currentPresetValue > 0
2119                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2120                 uniqueValidSizes.add(currentPresetValue);
2121             }
2122         }
2123 
2124         return presetValuesLength == uniqueValidSizes.size()
2125             ? presetValues
2126             : uniqueValidSizes.toArray();
2127     }
2128 
setupAutoSizeText()2129     private boolean setupAutoSizeText() {
2130         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2131             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2132             // not have a predefined set of sizes or if the current sizes array is empty.
2133             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2134                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2135                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2136                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2137                 for (int i = 0; i < autoSizeValuesLength; i++) {
2138                     autoSizeTextSizesInPx[i] = Math.round(
2139                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2140                 }
2141                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2142             }
2143 
2144             mNeedsAutoSizeText = true;
2145         } else {
2146             mNeedsAutoSizeText = false;
2147         }
2148 
2149         return mNeedsAutoSizeText;
2150     }
2151 
parseDimensionArray(TypedArray dimens)2152     private int[] parseDimensionArray(TypedArray dimens) {
2153         if (dimens == null) {
2154             return null;
2155         }
2156         int[] result = new int[dimens.length()];
2157         for (int i = 0; i < result.length; i++) {
2158             result[i] = dimens.getDimensionPixelSize(i, 0);
2159         }
2160         return result;
2161     }
2162 
2163     /**
2164      * @hide
2165      */
2166     @TestApi
2167     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)2168     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
2169         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2170             if (resultCode == Activity.RESULT_OK && data != null) {
2171                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2172                 if (result != null) {
2173                     if (isTextEditable()) {
2174                         ClipData clip = ClipData.newPlainText("", result);
2175                         ContentInfo payload =
2176                                 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build();
2177                         performReceiveContent(payload);
2178                         if (mEditor != null) {
2179                             mEditor.refreshTextActionMode();
2180                         }
2181                     } else {
2182                         if (result.length() > 0) {
2183                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2184                                 .show();
2185                         }
2186                     }
2187                 }
2188             } else if (mSpannable != null) {
2189                 // Reset the selection.
2190                 Selection.setSelection(mSpannable, getSelectionEnd());
2191             }
2192         }
2193     }
2194 
2195     /**
2196      * Sets the Typeface taking into account the given attributes.
2197      *
2198      * @param typeface a typeface
2199      * @param familyName family name string, e.g. "serif"
2200      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2201      * @param style a typeface style
2202      * @param weight a weight value for the Typeface or -1 if not specified.
2203      */
setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2204     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2205             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2206             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2207         if (typeface == null && familyName != null) {
2208             // Lookup normal Typeface from system font map.
2209             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2210             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2211         } else if (typeface != null) {
2212             resolveStyleAndSetTypeface(typeface, style, weight);
2213         } else {  // both typeface and familyName is null.
2214             switch (typefaceIndex) {
2215                 case SANS:
2216                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2217                     break;
2218                 case SERIF:
2219                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2220                     break;
2221                 case MONOSPACE:
2222                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2223                     break;
2224                 case DEFAULT_TYPEFACE:
2225                 default:
2226                     resolveStyleAndSetTypeface(null, style, weight);
2227                     break;
2228             }
2229         }
2230     }
2231 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2232     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2233             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2234         if (weight >= 0) {
2235             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2236             final boolean italic = (style & Typeface.ITALIC) != 0;
2237             setTypeface(Typeface.create(typeface, weight, italic));
2238         } else {
2239             setTypeface(typeface, style);
2240         }
2241     }
2242 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2243     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2244         boolean hasRelativeDrawables = (start != null) || (end != null);
2245         if (hasRelativeDrawables) {
2246             Drawables dr = mDrawables;
2247             if (dr == null) {
2248                 mDrawables = dr = new Drawables(getContext());
2249             }
2250             mDrawables.mOverride = true;
2251             final Rect compoundRect = dr.mCompoundRect;
2252             int[] state = getDrawableState();
2253             if (start != null) {
2254                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2255                 start.setState(state);
2256                 start.copyBounds(compoundRect);
2257                 start.setCallback(this);
2258 
2259                 dr.mDrawableStart = start;
2260                 dr.mDrawableSizeStart = compoundRect.width();
2261                 dr.mDrawableHeightStart = compoundRect.height();
2262             } else {
2263                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2264             }
2265             if (end != null) {
2266                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2267                 end.setState(state);
2268                 end.copyBounds(compoundRect);
2269                 end.setCallback(this);
2270 
2271                 dr.mDrawableEnd = end;
2272                 dr.mDrawableSizeEnd = compoundRect.width();
2273                 dr.mDrawableHeightEnd = compoundRect.height();
2274             } else {
2275                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2276             }
2277             resetResolvedDrawables();
2278             resolveDrawables();
2279             applyCompoundDrawableTint();
2280         }
2281     }
2282 
2283     @android.view.RemotableViewMethod
2284     @Override
setEnabled(boolean enabled)2285     public void setEnabled(boolean enabled) {
2286         if (enabled == isEnabled()) {
2287             return;
2288         }
2289 
2290         if (!enabled) {
2291             // Hide the soft input if the currently active TextView is disabled
2292             InputMethodManager imm = getInputMethodManager();
2293             if (imm != null && imm.isActive(this)) {
2294                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2295             }
2296         }
2297 
2298         super.setEnabled(enabled);
2299 
2300         if (enabled) {
2301             // Make sure IME is updated with current editor info.
2302             InputMethodManager imm = getInputMethodManager();
2303             if (imm != null) imm.restartInput(this);
2304         }
2305 
2306         // Will change text color
2307         if (mEditor != null) {
2308             mEditor.invalidateTextDisplayList();
2309             mEditor.prepareCursorControllers();
2310 
2311             // start or stop the cursor blinking as appropriate
2312             mEditor.makeBlink();
2313         }
2314     }
2315 
2316     /**
2317      * Sets the typeface and style in which the text should be displayed,
2318      * and turns on the fake bold and italic bits in the Paint if the
2319      * Typeface that you provided does not have all the bits in the
2320      * style that you specified.
2321      *
2322      * @attr ref android.R.styleable#TextView_typeface
2323      * @attr ref android.R.styleable#TextView_textStyle
2324      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2325     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2326         if (style > 0) {
2327             if (tf == null) {
2328                 tf = Typeface.defaultFromStyle(style);
2329             } else {
2330                 tf = Typeface.create(tf, style);
2331             }
2332 
2333             setTypeface(tf);
2334             // now compute what (if any) algorithmic styling is needed
2335             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2336             int need = style & ~typefaceStyle;
2337             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2338             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2339         } else {
2340             mTextPaint.setFakeBoldText(false);
2341             mTextPaint.setTextSkewX(0);
2342             setTypeface(tf);
2343         }
2344     }
2345 
2346     /**
2347      * Subclasses override this to specify that they have a KeyListener
2348      * by default even if not specifically called for in the XML options.
2349      */
getDefaultEditable()2350     protected boolean getDefaultEditable() {
2351         return false;
2352     }
2353 
2354     /**
2355      * Subclasses override this to specify a default movement method.
2356      */
getDefaultMovementMethod()2357     protected MovementMethod getDefaultMovementMethod() {
2358         return null;
2359     }
2360 
2361     /**
2362      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2363      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2364      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2365      * the return value from this method to Spannable or Editable, respectively.
2366      *
2367      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2368      * should make your own copy first.</p>
2369      *
2370      * @return The text displayed by the text view.
2371      * @attr ref android.R.styleable#TextView_text
2372      */
2373     @ViewDebug.CapturedViewProperty
2374     @InspectableProperty
getText()2375     public CharSequence getText() {
2376         if (mUseTextPaddingForUiTranslation) {
2377             ViewTranslationCallback callback = getViewTranslationCallback();
2378             if (callback != null && callback instanceof TextViewTranslationCallback) {
2379                 TextViewTranslationCallback defaultCallback =
2380                         (TextViewTranslationCallback) callback;
2381                 if (defaultCallback.isTextPaddingEnabled()
2382                         && defaultCallback.isShowingTranslation()) {
2383                     return defaultCallback.getPaddedText(mText, mTransformed);
2384                 }
2385             }
2386         }
2387         return mText;
2388     }
2389 
2390     /**
2391      * Returns the length, in characters, of the text managed by this TextView
2392      * @return The length of the text managed by the TextView in characters.
2393      */
length()2394     public int length() {
2395         return mText.length();
2396     }
2397 
2398     /**
2399      * Return the text that TextView is displaying as an Editable object. If the text is not
2400      * editable, null is returned.
2401      *
2402      * @see #getText
2403      */
getEditableText()2404     public Editable getEditableText() {
2405         return (mText instanceof Editable) ? (Editable) mText : null;
2406     }
2407 
2408     /**
2409      * @hide
2410      */
2411     @VisibleForTesting
getTransformed()2412     public CharSequence getTransformed() {
2413         return mTransformed;
2414     }
2415 
2416     /**
2417      * Gets the vertical distance between lines of text, in pixels.
2418      * Note that markup within the text can cause individual lines
2419      * to be taller or shorter than this height, and the layout may
2420      * contain additional first-or last-line padding.
2421      * @return The height of one standard line in pixels.
2422      */
2423     @InspectableProperty
getLineHeight()2424     public int getLineHeight() {
2425         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2426     }
2427 
2428     /**
2429      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2430      * This value can be null if the text or width has recently changed.
2431      * @return The Layout that is currently being used to display the text.
2432      */
getLayout()2433     public final Layout getLayout() {
2434         return mLayout;
2435     }
2436 
2437     /**
2438      * @return the {@link android.text.Layout} that is currently being used to
2439      * display the hint text. This can be null.
2440      */
2441     @UnsupportedAppUsage
getHintLayout()2442     final Layout getHintLayout() {
2443         return mHintLayout;
2444     }
2445 
2446     /**
2447      * Retrieve the {@link android.content.UndoManager} that is currently associated
2448      * with this TextView.  By default there is no associated UndoManager, so null
2449      * is returned.  One can be associated with the TextView through
2450      * {@link #setUndoManager(android.content.UndoManager, String)}
2451      *
2452      * @hide
2453      */
getUndoManager()2454     public final UndoManager getUndoManager() {
2455         // TODO: Consider supporting a global undo manager.
2456         throw new UnsupportedOperationException("not implemented");
2457     }
2458 
2459 
2460     /**
2461      * @hide
2462      */
2463     @VisibleForTesting
getEditorForTesting()2464     public final Editor getEditorForTesting() {
2465         return mEditor;
2466     }
2467 
2468     /**
2469      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2470      * done, all edit operations on the TextView will result in appropriate
2471      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2472      * stack.
2473      *
2474      * @param undoManager The {@link android.content.UndoManager} to associate with
2475      * this TextView, or null to clear any existing association.
2476      * @param tag String tag identifying this particular TextView owner in the
2477      * UndoManager.  This is used to keep the correct association with the
2478      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2479      *
2480      * @hide
2481      */
setUndoManager(UndoManager undoManager, String tag)2482     public final void setUndoManager(UndoManager undoManager, String tag) {
2483         // TODO: Consider supporting a global undo manager. An implementation will need to:
2484         // * createEditorIfNeeded()
2485         // * Promote to BufferType.EDITABLE if needed.
2486         // * Update the UndoManager and UndoOwner.
2487         // Likewise it will need to be able to restore the default UndoManager.
2488         throw new UnsupportedOperationException("not implemented");
2489     }
2490 
2491     /**
2492      * Gets the current {@link KeyListener} for the TextView.
2493      * This will frequently be null for non-EditText TextViews.
2494      * @return the current key listener for this TextView.
2495      *
2496      * @attr ref android.R.styleable#TextView_numeric
2497      * @attr ref android.R.styleable#TextView_digits
2498      * @attr ref android.R.styleable#TextView_phoneNumber
2499      * @attr ref android.R.styleable#TextView_inputMethod
2500      * @attr ref android.R.styleable#TextView_capitalize
2501      * @attr ref android.R.styleable#TextView_autoText
2502      */
getKeyListener()2503     public final KeyListener getKeyListener() {
2504         return mEditor == null ? null : mEditor.mKeyListener;
2505     }
2506 
2507     /**
2508      * Sets the key listener to be used with this TextView.  This can be null
2509      * to disallow user input.  Note that this method has significant and
2510      * subtle interactions with soft keyboards and other input method:
2511      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2512      * for important details.  Calling this method will replace the current
2513      * content type of the text view with the content type returned by the
2514      * key listener.
2515      * <p>
2516      * Be warned that if you want a TextView with a key listener or movement
2517      * method not to be focusable, or if you want a TextView without a
2518      * key listener or movement method to be focusable, you must call
2519      * {@link #setFocusable} again after calling this to get the focusability
2520      * back the way you want it.
2521      *
2522      * @attr ref android.R.styleable#TextView_numeric
2523      * @attr ref android.R.styleable#TextView_digits
2524      * @attr ref android.R.styleable#TextView_phoneNumber
2525      * @attr ref android.R.styleable#TextView_inputMethod
2526      * @attr ref android.R.styleable#TextView_capitalize
2527      * @attr ref android.R.styleable#TextView_autoText
2528      */
setKeyListener(KeyListener input)2529     public void setKeyListener(KeyListener input) {
2530         mListenerChanged = true;
2531         setKeyListenerOnly(input);
2532         fixFocusableAndClickableSettings();
2533 
2534         if (input != null) {
2535             createEditorIfNeeded();
2536             setInputTypeFromEditor();
2537         } else {
2538             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2539         }
2540 
2541         InputMethodManager imm = getInputMethodManager();
2542         if (imm != null) imm.restartInput(this);
2543     }
2544 
setInputTypeFromEditor()2545     private void setInputTypeFromEditor() {
2546         try {
2547             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2548         } catch (IncompatibleClassChangeError e) {
2549             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2550         }
2551         // Change inputType, without affecting transformation.
2552         // No need to applySingleLine since mSingleLine is unchanged.
2553         setInputTypeSingleLine(mSingleLine);
2554     }
2555 
setKeyListenerOnly(KeyListener input)2556     private void setKeyListenerOnly(KeyListener input) {
2557         if (mEditor == null && input == null) return; // null is the default value
2558 
2559         createEditorIfNeeded();
2560         if (mEditor.mKeyListener != input) {
2561             mEditor.mKeyListener = input;
2562             if (input != null && !(mText instanceof Editable)) {
2563                 setText(mText);
2564             }
2565 
2566             setFilters((Editable) mText, mFilters);
2567         }
2568     }
2569 
2570     /**
2571      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2572      * which provides positioning, scrolling, and text selection functionality.
2573      * This will frequently be null for non-EditText TextViews.
2574      * @return the movement method being used for this TextView.
2575      * @see android.text.method.MovementMethod
2576      */
getMovementMethod()2577     public final MovementMethod getMovementMethod() {
2578         return mMovement;
2579     }
2580 
2581     /**
2582      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2583      * for this TextView. This can be null to disallow using the arrow keys to move the
2584      * cursor or scroll the view.
2585      * <p>
2586      * Be warned that if you want a TextView with a key listener or movement
2587      * method not to be focusable, or if you want a TextView without a
2588      * key listener or movement method to be focusable, you must call
2589      * {@link #setFocusable} again after calling this to get the focusability
2590      * back the way you want it.
2591      */
setMovementMethod(MovementMethod movement)2592     public final void setMovementMethod(MovementMethod movement) {
2593         if (mMovement != movement) {
2594             mMovement = movement;
2595 
2596             if (movement != null && mSpannable == null) {
2597                 setText(mText);
2598             }
2599 
2600             fixFocusableAndClickableSettings();
2601 
2602             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2603             // mMovement
2604             if (mEditor != null) mEditor.prepareCursorControllers();
2605         }
2606     }
2607 
fixFocusableAndClickableSettings()2608     private void fixFocusableAndClickableSettings() {
2609         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2610             setFocusable(FOCUSABLE);
2611             setClickable(true);
2612             setLongClickable(true);
2613         } else {
2614             setFocusable(FOCUSABLE_AUTO);
2615             setClickable(false);
2616             setLongClickable(false);
2617         }
2618     }
2619 
2620     /**
2621      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2622      * This is frequently null, except for single-line and password fields.
2623      * @return the current transformation method for this TextView.
2624      *
2625      * @attr ref android.R.styleable#TextView_password
2626      * @attr ref android.R.styleable#TextView_singleLine
2627      */
getTransformationMethod()2628     public final TransformationMethod getTransformationMethod() {
2629         return mTransformation;
2630     }
2631 
2632     /**
2633      * Sets the transformation that is applied to the text that this
2634      * TextView is displaying.
2635      *
2636      * @attr ref android.R.styleable#TextView_password
2637      * @attr ref android.R.styleable#TextView_singleLine
2638      */
setTransformationMethod(TransformationMethod method)2639     public final void setTransformationMethod(TransformationMethod method) {
2640         if (method == mTransformation) {
2641             // Avoid the setText() below if the transformation is
2642             // the same.
2643             return;
2644         }
2645         if (mTransformation != null) {
2646             if (mSpannable != null) {
2647                 mSpannable.removeSpan(mTransformation);
2648             }
2649         }
2650 
2651         mTransformation = method;
2652 
2653         if (method instanceof TransformationMethod2) {
2654             TransformationMethod2 method2 = (TransformationMethod2) method;
2655             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2656             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2657         } else {
2658             mAllowTransformationLengthChange = false;
2659         }
2660 
2661         setText(mText);
2662 
2663         if (hasPasswordTransformationMethod()) {
2664             notifyViewAccessibilityStateChangedIfNeeded(
2665                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2666         }
2667 
2668         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2669         // getTextDirectionHeuristic, needs reset
2670         mTextDir = getTextDirectionHeuristic();
2671     }
2672 
2673     /**
2674      * Returns the top padding of the view, plus space for the top
2675      * Drawable if any.
2676      */
getCompoundPaddingTop()2677     public int getCompoundPaddingTop() {
2678         final Drawables dr = mDrawables;
2679         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2680             return mPaddingTop;
2681         } else {
2682             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2683         }
2684     }
2685 
2686     /**
2687      * Returns the bottom padding of the view, plus space for the bottom
2688      * Drawable if any.
2689      */
getCompoundPaddingBottom()2690     public int getCompoundPaddingBottom() {
2691         final Drawables dr = mDrawables;
2692         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2693             return mPaddingBottom;
2694         } else {
2695             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2696         }
2697     }
2698 
2699     /**
2700      * Returns the left padding of the view, plus space for the left
2701      * Drawable if any.
2702      */
getCompoundPaddingLeft()2703     public int getCompoundPaddingLeft() {
2704         final Drawables dr = mDrawables;
2705         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2706             return mPaddingLeft;
2707         } else {
2708             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2709         }
2710     }
2711 
2712     /**
2713      * Returns the right padding of the view, plus space for the right
2714      * Drawable if any.
2715      */
getCompoundPaddingRight()2716     public int getCompoundPaddingRight() {
2717         final Drawables dr = mDrawables;
2718         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2719             return mPaddingRight;
2720         } else {
2721             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2722         }
2723     }
2724 
2725     /**
2726      * Returns the start padding of the view, plus space for the start
2727      * Drawable if any.
2728      */
getCompoundPaddingStart()2729     public int getCompoundPaddingStart() {
2730         resolveDrawables();
2731         switch(getLayoutDirection()) {
2732             default:
2733             case LAYOUT_DIRECTION_LTR:
2734                 return getCompoundPaddingLeft();
2735             case LAYOUT_DIRECTION_RTL:
2736                 return getCompoundPaddingRight();
2737         }
2738     }
2739 
2740     /**
2741      * Returns the end padding of the view, plus space for the end
2742      * Drawable if any.
2743      */
getCompoundPaddingEnd()2744     public int getCompoundPaddingEnd() {
2745         resolveDrawables();
2746         switch(getLayoutDirection()) {
2747             default:
2748             case LAYOUT_DIRECTION_LTR:
2749                 return getCompoundPaddingRight();
2750             case LAYOUT_DIRECTION_RTL:
2751                 return getCompoundPaddingLeft();
2752         }
2753     }
2754 
2755     /**
2756      * Returns the extended top padding of the view, including both the
2757      * top Drawable if any and any extra space to keep more than maxLines
2758      * of text from showing.  It is only valid to call this after measuring.
2759      */
getExtendedPaddingTop()2760     public int getExtendedPaddingTop() {
2761         if (mMaxMode != LINES) {
2762             return getCompoundPaddingTop();
2763         }
2764 
2765         if (mLayout == null) {
2766             assumeLayout();
2767         }
2768 
2769         if (mLayout.getLineCount() <= mMaximum) {
2770             return getCompoundPaddingTop();
2771         }
2772 
2773         int top = getCompoundPaddingTop();
2774         int bottom = getCompoundPaddingBottom();
2775         int viewht = getHeight() - top - bottom;
2776         int layoutht = mLayout.getLineTop(mMaximum);
2777 
2778         if (layoutht >= viewht) {
2779             return top;
2780         }
2781 
2782         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2783         if (gravity == Gravity.TOP) {
2784             return top;
2785         } else if (gravity == Gravity.BOTTOM) {
2786             return top + viewht - layoutht;
2787         } else { // (gravity == Gravity.CENTER_VERTICAL)
2788             return top + (viewht - layoutht) / 2;
2789         }
2790     }
2791 
2792     /**
2793      * Returns the extended bottom padding of the view, including both the
2794      * bottom Drawable if any and any extra space to keep more than maxLines
2795      * of text from showing.  It is only valid to call this after measuring.
2796      */
getExtendedPaddingBottom()2797     public int getExtendedPaddingBottom() {
2798         if (mMaxMode != LINES) {
2799             return getCompoundPaddingBottom();
2800         }
2801 
2802         if (mLayout == null) {
2803             assumeLayout();
2804         }
2805 
2806         if (mLayout.getLineCount() <= mMaximum) {
2807             return getCompoundPaddingBottom();
2808         }
2809 
2810         int top = getCompoundPaddingTop();
2811         int bottom = getCompoundPaddingBottom();
2812         int viewht = getHeight() - top - bottom;
2813         int layoutht = mLayout.getLineTop(mMaximum);
2814 
2815         if (layoutht >= viewht) {
2816             return bottom;
2817         }
2818 
2819         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2820         if (gravity == Gravity.TOP) {
2821             return bottom + viewht - layoutht;
2822         } else if (gravity == Gravity.BOTTOM) {
2823             return bottom;
2824         } else { // (gravity == Gravity.CENTER_VERTICAL)
2825             return bottom + (viewht - layoutht) / 2;
2826         }
2827     }
2828 
2829     /**
2830      * Returns the total left padding of the view, including the left
2831      * Drawable if any.
2832      */
getTotalPaddingLeft()2833     public int getTotalPaddingLeft() {
2834         return getCompoundPaddingLeft();
2835     }
2836 
2837     /**
2838      * Returns the total right padding of the view, including the right
2839      * Drawable if any.
2840      */
getTotalPaddingRight()2841     public int getTotalPaddingRight() {
2842         return getCompoundPaddingRight();
2843     }
2844 
2845     /**
2846      * Returns the total start padding of the view, including the start
2847      * Drawable if any.
2848      */
getTotalPaddingStart()2849     public int getTotalPaddingStart() {
2850         return getCompoundPaddingStart();
2851     }
2852 
2853     /**
2854      * Returns the total end padding of the view, including the end
2855      * Drawable if any.
2856      */
getTotalPaddingEnd()2857     public int getTotalPaddingEnd() {
2858         return getCompoundPaddingEnd();
2859     }
2860 
2861     /**
2862      * Returns the total top padding of the view, including the top
2863      * Drawable if any, the extra space to keep more than maxLines
2864      * from showing, and the vertical offset for gravity, if any.
2865      */
getTotalPaddingTop()2866     public int getTotalPaddingTop() {
2867         return getExtendedPaddingTop() + getVerticalOffset(true);
2868     }
2869 
2870     /**
2871      * Returns the total bottom padding of the view, including the bottom
2872      * Drawable if any, the extra space to keep more than maxLines
2873      * from showing, and the vertical offset for gravity, if any.
2874      */
getTotalPaddingBottom()2875     public int getTotalPaddingBottom() {
2876         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2877     }
2878 
2879     /**
2880      * Sets the Drawables (if any) to appear to the left of, above, to the
2881      * right of, and below the text. Use {@code null} if you do not want a
2882      * Drawable there. The Drawables must already have had
2883      * {@link Drawable#setBounds} called.
2884      * <p>
2885      * Calling this method will overwrite any Drawables previously set using
2886      * {@link #setCompoundDrawablesRelative} or related methods.
2887      *
2888      * @attr ref android.R.styleable#TextView_drawableLeft
2889      * @attr ref android.R.styleable#TextView_drawableTop
2890      * @attr ref android.R.styleable#TextView_drawableRight
2891      * @attr ref android.R.styleable#TextView_drawableBottom
2892      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2893     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2894             @Nullable Drawable right, @Nullable Drawable bottom) {
2895         Drawables dr = mDrawables;
2896 
2897         // We're switching to absolute, discard relative.
2898         if (dr != null) {
2899             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2900             dr.mDrawableStart = null;
2901             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2902             dr.mDrawableEnd = null;
2903             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2904             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2905         }
2906 
2907         final boolean drawables = left != null || top != null || right != null || bottom != null;
2908         if (!drawables) {
2909             // Clearing drawables...  can we free the data structure?
2910             if (dr != null) {
2911                 if (!dr.hasMetadata()) {
2912                     mDrawables = null;
2913                 } else {
2914                     // We need to retain the last set padding, so just clear
2915                     // out all of the fields in the existing structure.
2916                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2917                         if (dr.mShowing[i] != null) {
2918                             dr.mShowing[i].setCallback(null);
2919                         }
2920                         dr.mShowing[i] = null;
2921                     }
2922                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2923                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2924                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2925                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2926                 }
2927             }
2928         } else {
2929             if (dr == null) {
2930                 mDrawables = dr = new Drawables(getContext());
2931             }
2932 
2933             mDrawables.mOverride = false;
2934 
2935             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2936                 dr.mShowing[Drawables.LEFT].setCallback(null);
2937             }
2938             dr.mShowing[Drawables.LEFT] = left;
2939 
2940             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2941                 dr.mShowing[Drawables.TOP].setCallback(null);
2942             }
2943             dr.mShowing[Drawables.TOP] = top;
2944 
2945             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2946                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2947             }
2948             dr.mShowing[Drawables.RIGHT] = right;
2949 
2950             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2951                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2952             }
2953             dr.mShowing[Drawables.BOTTOM] = bottom;
2954 
2955             final Rect compoundRect = dr.mCompoundRect;
2956             int[] state;
2957 
2958             state = getDrawableState();
2959 
2960             if (left != null) {
2961                 left.setState(state);
2962                 left.copyBounds(compoundRect);
2963                 left.setCallback(this);
2964                 dr.mDrawableSizeLeft = compoundRect.width();
2965                 dr.mDrawableHeightLeft = compoundRect.height();
2966             } else {
2967                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2968             }
2969 
2970             if (right != null) {
2971                 right.setState(state);
2972                 right.copyBounds(compoundRect);
2973                 right.setCallback(this);
2974                 dr.mDrawableSizeRight = compoundRect.width();
2975                 dr.mDrawableHeightRight = compoundRect.height();
2976             } else {
2977                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2978             }
2979 
2980             if (top != null) {
2981                 top.setState(state);
2982                 top.copyBounds(compoundRect);
2983                 top.setCallback(this);
2984                 dr.mDrawableSizeTop = compoundRect.height();
2985                 dr.mDrawableWidthTop = compoundRect.width();
2986             } else {
2987                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2988             }
2989 
2990             if (bottom != null) {
2991                 bottom.setState(state);
2992                 bottom.copyBounds(compoundRect);
2993                 bottom.setCallback(this);
2994                 dr.mDrawableSizeBottom = compoundRect.height();
2995                 dr.mDrawableWidthBottom = compoundRect.width();
2996             } else {
2997                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2998             }
2999         }
3000 
3001         // Save initial left/right drawables
3002         if (dr != null) {
3003             dr.mDrawableLeftInitial = left;
3004             dr.mDrawableRightInitial = right;
3005         }
3006 
3007         resetResolvedDrawables();
3008         resolveDrawables();
3009         applyCompoundDrawableTint();
3010         invalidate();
3011         requestLayout();
3012     }
3013 
3014     /**
3015      * Sets the Drawables (if any) to appear to the left of, above, to the
3016      * right of, and below the text. Use 0 if you do not want a Drawable there.
3017      * The Drawables' bounds will be set to their intrinsic bounds.
3018      * <p>
3019      * Calling this method will overwrite any Drawables previously set using
3020      * {@link #setCompoundDrawablesRelative} or related methods.
3021      *
3022      * @param left Resource identifier of the left Drawable.
3023      * @param top Resource identifier of the top Drawable.
3024      * @param right Resource identifier of the right Drawable.
3025      * @param bottom Resource identifier of the bottom Drawable.
3026      *
3027      * @attr ref android.R.styleable#TextView_drawableLeft
3028      * @attr ref android.R.styleable#TextView_drawableTop
3029      * @attr ref android.R.styleable#TextView_drawableRight
3030      * @attr ref android.R.styleable#TextView_drawableBottom
3031      */
3032     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)3033     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
3034             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
3035         final Context context = getContext();
3036         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
3037                 top != 0 ? context.getDrawable(top) : null,
3038                 right != 0 ? context.getDrawable(right) : null,
3039                 bottom != 0 ? context.getDrawable(bottom) : null);
3040     }
3041 
3042     /**
3043      * Sets the Drawables (if any) to appear to the left of, above, to the
3044      * right of, and below the text. Use {@code null} if you do not want a
3045      * Drawable there. The Drawables' bounds will be set to their intrinsic
3046      * bounds.
3047      * <p>
3048      * Calling this method will overwrite any Drawables previously set using
3049      * {@link #setCompoundDrawablesRelative} or related methods.
3050      *
3051      * @attr ref android.R.styleable#TextView_drawableLeft
3052      * @attr ref android.R.styleable#TextView_drawableTop
3053      * @attr ref android.R.styleable#TextView_drawableRight
3054      * @attr ref android.R.styleable#TextView_drawableBottom
3055      */
3056     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3057     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
3058             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
3059 
3060         if (left != null) {
3061             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
3062         }
3063         if (right != null) {
3064             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
3065         }
3066         if (top != null) {
3067             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3068         }
3069         if (bottom != null) {
3070             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3071         }
3072         setCompoundDrawables(left, top, right, bottom);
3073     }
3074 
3075     /**
3076      * Sets the Drawables (if any) to appear to the start of, above, to the end
3077      * of, and below the text. Use {@code null} if you do not want a Drawable
3078      * there. The Drawables must already have had {@link Drawable#setBounds}
3079      * called.
3080      * <p>
3081      * Calling this method will overwrite any Drawables previously set using
3082      * {@link #setCompoundDrawables} or related methods.
3083      *
3084      * @attr ref android.R.styleable#TextView_drawableStart
3085      * @attr ref android.R.styleable#TextView_drawableTop
3086      * @attr ref android.R.styleable#TextView_drawableEnd
3087      * @attr ref android.R.styleable#TextView_drawableBottom
3088      */
3089     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3090     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
3091             @Nullable Drawable end, @Nullable Drawable bottom) {
3092         Drawables dr = mDrawables;
3093 
3094         // We're switching to relative, discard absolute.
3095         if (dr != null) {
3096             if (dr.mShowing[Drawables.LEFT] != null) {
3097                 dr.mShowing[Drawables.LEFT].setCallback(null);
3098             }
3099             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3100             if (dr.mShowing[Drawables.RIGHT] != null) {
3101                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3102             }
3103             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3104             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3105             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3106         }
3107 
3108         final boolean drawables = start != null || top != null
3109                 || end != null || bottom != null;
3110 
3111         if (!drawables) {
3112             // Clearing drawables...  can we free the data structure?
3113             if (dr != null) {
3114                 if (!dr.hasMetadata()) {
3115                     mDrawables = null;
3116                 } else {
3117                     // We need to retain the last set padding, so just clear
3118                     // out all of the fields in the existing structure.
3119                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3120                     dr.mDrawableStart = null;
3121                     if (dr.mShowing[Drawables.TOP] != null) {
3122                         dr.mShowing[Drawables.TOP].setCallback(null);
3123                     }
3124                     dr.mShowing[Drawables.TOP] = null;
3125                     if (dr.mDrawableEnd != null) {
3126                         dr.mDrawableEnd.setCallback(null);
3127                     }
3128                     dr.mDrawableEnd = null;
3129                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3130                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3131                     }
3132                     dr.mShowing[Drawables.BOTTOM] = null;
3133                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3134                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3135                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3136                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3137                 }
3138             }
3139         } else {
3140             if (dr == null) {
3141                 mDrawables = dr = new Drawables(getContext());
3142             }
3143 
3144             mDrawables.mOverride = true;
3145 
3146             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3147                 dr.mDrawableStart.setCallback(null);
3148             }
3149             dr.mDrawableStart = start;
3150 
3151             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3152                 dr.mShowing[Drawables.TOP].setCallback(null);
3153             }
3154             dr.mShowing[Drawables.TOP] = top;
3155 
3156             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3157                 dr.mDrawableEnd.setCallback(null);
3158             }
3159             dr.mDrawableEnd = end;
3160 
3161             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3162                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3163             }
3164             dr.mShowing[Drawables.BOTTOM] = bottom;
3165 
3166             final Rect compoundRect = dr.mCompoundRect;
3167             int[] state;
3168 
3169             state = getDrawableState();
3170 
3171             if (start != null) {
3172                 start.setState(state);
3173                 start.copyBounds(compoundRect);
3174                 start.setCallback(this);
3175                 dr.mDrawableSizeStart = compoundRect.width();
3176                 dr.mDrawableHeightStart = compoundRect.height();
3177             } else {
3178                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3179             }
3180 
3181             if (end != null) {
3182                 end.setState(state);
3183                 end.copyBounds(compoundRect);
3184                 end.setCallback(this);
3185                 dr.mDrawableSizeEnd = compoundRect.width();
3186                 dr.mDrawableHeightEnd = compoundRect.height();
3187             } else {
3188                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3189             }
3190 
3191             if (top != null) {
3192                 top.setState(state);
3193                 top.copyBounds(compoundRect);
3194                 top.setCallback(this);
3195                 dr.mDrawableSizeTop = compoundRect.height();
3196                 dr.mDrawableWidthTop = compoundRect.width();
3197             } else {
3198                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3199             }
3200 
3201             if (bottom != null) {
3202                 bottom.setState(state);
3203                 bottom.copyBounds(compoundRect);
3204                 bottom.setCallback(this);
3205                 dr.mDrawableSizeBottom = compoundRect.height();
3206                 dr.mDrawableWidthBottom = compoundRect.width();
3207             } else {
3208                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3209             }
3210         }
3211 
3212         resetResolvedDrawables();
3213         resolveDrawables();
3214         invalidate();
3215         requestLayout();
3216     }
3217 
3218     /**
3219      * Sets the Drawables (if any) to appear to the start of, above, to the end
3220      * of, and below the text. Use 0 if you do not want a Drawable there. The
3221      * Drawables' bounds will be set to their intrinsic bounds.
3222      * <p>
3223      * Calling this method will overwrite any Drawables previously set using
3224      * {@link #setCompoundDrawables} or related methods.
3225      *
3226      * @param start Resource identifier of the start Drawable.
3227      * @param top Resource identifier of the top Drawable.
3228      * @param end Resource identifier of the end Drawable.
3229      * @param bottom Resource identifier of the bottom Drawable.
3230      *
3231      * @attr ref android.R.styleable#TextView_drawableStart
3232      * @attr ref android.R.styleable#TextView_drawableTop
3233      * @attr ref android.R.styleable#TextView_drawableEnd
3234      * @attr ref android.R.styleable#TextView_drawableBottom
3235      */
3236     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3237     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3238             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3239         final Context context = getContext();
3240         setCompoundDrawablesRelativeWithIntrinsicBounds(
3241                 start != 0 ? context.getDrawable(start) : null,
3242                 top != 0 ? context.getDrawable(top) : null,
3243                 end != 0 ? context.getDrawable(end) : null,
3244                 bottom != 0 ? context.getDrawable(bottom) : null);
3245     }
3246 
3247     /**
3248      * Sets the Drawables (if any) to appear to the start of, above, to the end
3249      * of, and below the text. Use {@code null} if you do not want a Drawable
3250      * there. The Drawables' bounds will be set to their intrinsic bounds.
3251      * <p>
3252      * Calling this method will overwrite any Drawables previously set using
3253      * {@link #setCompoundDrawables} or related methods.
3254      *
3255      * @attr ref android.R.styleable#TextView_drawableStart
3256      * @attr ref android.R.styleable#TextView_drawableTop
3257      * @attr ref android.R.styleable#TextView_drawableEnd
3258      * @attr ref android.R.styleable#TextView_drawableBottom
3259      */
3260     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3261     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3262             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3263 
3264         if (start != null) {
3265             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3266         }
3267         if (end != null) {
3268             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3269         }
3270         if (top != null) {
3271             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3272         }
3273         if (bottom != null) {
3274             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3275         }
3276         setCompoundDrawablesRelative(start, top, end, bottom);
3277     }
3278 
3279     /**
3280      * Returns drawables for the left, top, right, and bottom borders.
3281      *
3282      * @attr ref android.R.styleable#TextView_drawableLeft
3283      * @attr ref android.R.styleable#TextView_drawableTop
3284      * @attr ref android.R.styleable#TextView_drawableRight
3285      * @attr ref android.R.styleable#TextView_drawableBottom
3286      */
3287     @NonNull
getCompoundDrawables()3288     public Drawable[] getCompoundDrawables() {
3289         final Drawables dr = mDrawables;
3290         if (dr != null) {
3291             return dr.mShowing.clone();
3292         } else {
3293             return new Drawable[] { null, null, null, null };
3294         }
3295     }
3296 
3297     /**
3298      * Returns drawables for the start, top, end, and bottom borders.
3299      *
3300      * @attr ref android.R.styleable#TextView_drawableStart
3301      * @attr ref android.R.styleable#TextView_drawableTop
3302      * @attr ref android.R.styleable#TextView_drawableEnd
3303      * @attr ref android.R.styleable#TextView_drawableBottom
3304      */
3305     @NonNull
getCompoundDrawablesRelative()3306     public Drawable[] getCompoundDrawablesRelative() {
3307         final Drawables dr = mDrawables;
3308         if (dr != null) {
3309             return new Drawable[] {
3310                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3311                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3312             };
3313         } else {
3314             return new Drawable[] { null, null, null, null };
3315         }
3316     }
3317 
3318     /**
3319      * Sets the size of the padding between the compound drawables and
3320      * the text.
3321      *
3322      * @attr ref android.R.styleable#TextView_drawablePadding
3323      */
3324     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3325     public void setCompoundDrawablePadding(int pad) {
3326         Drawables dr = mDrawables;
3327         if (pad == 0) {
3328             if (dr != null) {
3329                 dr.mDrawablePadding = pad;
3330             }
3331         } else {
3332             if (dr == null) {
3333                 mDrawables = dr = new Drawables(getContext());
3334             }
3335             dr.mDrawablePadding = pad;
3336         }
3337 
3338         invalidate();
3339         requestLayout();
3340     }
3341 
3342     /**
3343      * Returns the padding between the compound drawables and the text.
3344      *
3345      * @attr ref android.R.styleable#TextView_drawablePadding
3346      */
3347     @InspectableProperty(name = "drawablePadding")
getCompoundDrawablePadding()3348     public int getCompoundDrawablePadding() {
3349         final Drawables dr = mDrawables;
3350         return dr != null ? dr.mDrawablePadding : 0;
3351     }
3352 
3353     /**
3354      * Applies a tint to the compound drawables. Does not modify the
3355      * current tint mode, which is {@link BlendMode#SRC_IN} by default.
3356      * <p>
3357      * Subsequent calls to
3358      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3359      * and related methods will automatically mutate the drawables and apply
3360      * the specified tint and tint mode using
3361      * {@link Drawable#setTintList(ColorStateList)}.
3362      *
3363      * @param tint the tint to apply, may be {@code null} to clear tint
3364      *
3365      * @attr ref android.R.styleable#TextView_drawableTint
3366      * @see #getCompoundDrawableTintList()
3367      * @see Drawable#setTintList(ColorStateList)
3368      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3369     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3370         if (mDrawables == null) {
3371             mDrawables = new Drawables(getContext());
3372         }
3373         mDrawables.mTintList = tint;
3374         mDrawables.mHasTint = true;
3375 
3376         applyCompoundDrawableTint();
3377     }
3378 
3379     /**
3380      * @return the tint applied to the compound drawables
3381      * @attr ref android.R.styleable#TextView_drawableTint
3382      * @see #setCompoundDrawableTintList(ColorStateList)
3383      */
3384     @InspectableProperty(name = "drawableTint")
getCompoundDrawableTintList()3385     public ColorStateList getCompoundDrawableTintList() {
3386         return mDrawables != null ? mDrawables.mTintList : null;
3387     }
3388 
3389     /**
3390      * Specifies the blending mode used to apply the tint specified by
3391      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3392      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3393      *
3394      * @param tintMode the blending mode used to apply the tint, may be
3395      *                 {@code null} to clear tint
3396      * @attr ref android.R.styleable#TextView_drawableTintMode
3397      * @see #setCompoundDrawableTintList(ColorStateList)
3398      * @see Drawable#setTintMode(PorterDuff.Mode)
3399      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3400     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3401         setCompoundDrawableTintBlendMode(tintMode != null
3402                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3403     }
3404 
3405     /**
3406      * Specifies the blending mode used to apply the tint specified by
3407      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3408      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3409      *
3410      * @param blendMode the blending mode used to apply the tint, may be
3411      *                 {@code null} to clear tint
3412      * @attr ref android.R.styleable#TextView_drawableTintMode
3413      * @see #setCompoundDrawableTintList(ColorStateList)
3414      * @see Drawable#setTintBlendMode(BlendMode)
3415      */
setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3416     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3417         if (mDrawables == null) {
3418             mDrawables = new Drawables(getContext());
3419         }
3420         mDrawables.mBlendMode = blendMode;
3421         mDrawables.mHasTintMode = true;
3422 
3423         applyCompoundDrawableTint();
3424     }
3425 
3426     /**
3427      * Returns the blending mode used to apply the tint to the compound
3428      * drawables, if specified.
3429      *
3430      * @return the blending mode used to apply the tint to the compound
3431      *         drawables
3432      * @attr ref android.R.styleable#TextView_drawableTintMode
3433      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3434      *
3435      */
3436     @InspectableProperty(name = "drawableTintMode")
getCompoundDrawableTintMode()3437     public PorterDuff.Mode getCompoundDrawableTintMode() {
3438         BlendMode mode = getCompoundDrawableTintBlendMode();
3439         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3440     }
3441 
3442     /**
3443      * Returns the blending mode used to apply the tint to the compound
3444      * drawables, if specified.
3445      *
3446      * @return the blending mode used to apply the tint to the compound
3447      *         drawables
3448      * @attr ref android.R.styleable#TextView_drawableTintMode
3449      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3450      */
3451     @InspectableProperty(name = "drawableBlendMode",
3452             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
getCompoundDrawableTintBlendMode()3453     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3454         return mDrawables != null ? mDrawables.mBlendMode : null;
3455     }
3456 
applyCompoundDrawableTint()3457     private void applyCompoundDrawableTint() {
3458         if (mDrawables == null) {
3459             return;
3460         }
3461 
3462         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3463             final ColorStateList tintList = mDrawables.mTintList;
3464             final BlendMode blendMode = mDrawables.mBlendMode;
3465             final boolean hasTint = mDrawables.mHasTint;
3466             final boolean hasTintMode = mDrawables.mHasTintMode;
3467             final int[] state = getDrawableState();
3468 
3469             for (Drawable dr : mDrawables.mShowing) {
3470                 if (dr == null) {
3471                     continue;
3472                 }
3473 
3474                 if (dr == mDrawables.mDrawableError) {
3475                     // From a developer's perspective, the error drawable isn't
3476                     // a compound drawable. Don't apply the generic compound
3477                     // drawable tint to it.
3478                     continue;
3479                 }
3480 
3481                 dr.mutate();
3482 
3483                 if (hasTint) {
3484                     dr.setTintList(tintList);
3485                 }
3486 
3487                 if (hasTintMode) {
3488                     dr.setTintBlendMode(blendMode);
3489                 }
3490 
3491                 // The drawable (or one of its children) may not have been
3492                 // stateful before applying the tint, so let's try again.
3493                 if (dr.isStateful()) {
3494                     dr.setState(state);
3495                 }
3496             }
3497         }
3498     }
3499 
3500     /**
3501      * @inheritDoc
3502      *
3503      * @see #setFirstBaselineToTopHeight(int)
3504      * @see #setLastBaselineToBottomHeight(int)
3505      */
3506     @Override
setPadding(int left, int top, int right, int bottom)3507     public void setPadding(int left, int top, int right, int bottom) {
3508         if (left != mPaddingLeft
3509                 || right != mPaddingRight
3510                 || top != mPaddingTop
3511                 ||  bottom != mPaddingBottom) {
3512             nullLayouts();
3513         }
3514 
3515         // the super call will requestLayout()
3516         super.setPadding(left, top, right, bottom);
3517         invalidate();
3518     }
3519 
3520     /**
3521      * @inheritDoc
3522      *
3523      * @see #setFirstBaselineToTopHeight(int)
3524      * @see #setLastBaselineToBottomHeight(int)
3525      */
3526     @Override
setPaddingRelative(int start, int top, int end, int bottom)3527     public void setPaddingRelative(int start, int top, int end, int bottom) {
3528         if (start != getPaddingStart()
3529                 || end != getPaddingEnd()
3530                 || top != mPaddingTop
3531                 || bottom != mPaddingBottom) {
3532             nullLayouts();
3533         }
3534 
3535         // the super call will requestLayout()
3536         super.setPaddingRelative(start, top, end, bottom);
3537         invalidate();
3538     }
3539 
3540     /**
3541      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3542      * the distance between the top of the TextView and first line's baseline.
3543      * <p>
3544      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3545      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3546      *
3547      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3548      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3549      * Moreover since this function sets the top padding, if the height of the TextView is less than
3550      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3551      * down and bottom will be clipped.
3552      *
3553      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3554      *      in pixels
3555      *
3556      * @see #getFirstBaselineToTopHeight()
3557      * @see #setLastBaselineToBottomHeight(int)
3558      * @see #setPadding(int, int, int, int)
3559      * @see #setPaddingRelative(int, int, int, int)
3560      *
3561      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3562      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3563     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3564         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3565 
3566         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3567         final int fontMetricsTop;
3568         if (getIncludeFontPadding()) {
3569             fontMetricsTop = fontMetrics.top;
3570         } else {
3571             fontMetricsTop = fontMetrics.ascent;
3572         }
3573 
3574         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3575         // in settings). At the moment, we don't.
3576 
3577         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3578             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3579             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3580         }
3581     }
3582 
3583     /**
3584      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3585      * the distance between the bottom of the TextView and the last line's baseline.
3586      * <p>
3587      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3588      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3589      *
3590      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3591      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3592      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3593      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3594      * clipped.
3595      *
3596      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3597      *      in pixels
3598      *
3599      * @see #getLastBaselineToBottomHeight()
3600      * @see #setFirstBaselineToTopHeight(int)
3601      * @see #setPadding(int, int, int, int)
3602      * @see #setPaddingRelative(int, int, int, int)
3603      *
3604      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3605      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3606     public void setLastBaselineToBottomHeight(
3607             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3608         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3609 
3610         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3611         final int fontMetricsBottom;
3612         if (getIncludeFontPadding()) {
3613             fontMetricsBottom = fontMetrics.bottom;
3614         } else {
3615             fontMetricsBottom = fontMetrics.descent;
3616         }
3617 
3618         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3619         // in settings). At the moment, we don't.
3620 
3621         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3622             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3623             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3624         }
3625     }
3626 
3627     /**
3628      * Returns the distance between the first text baseline and the top of this TextView.
3629      *
3630      * @see #setFirstBaselineToTopHeight(int)
3631      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3632      */
3633     @InspectableProperty
getFirstBaselineToTopHeight()3634     public int getFirstBaselineToTopHeight() {
3635         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3636     }
3637 
3638     /**
3639      * Returns the distance between the last text baseline and the bottom of this TextView.
3640      *
3641      * @see #setLastBaselineToBottomHeight(int)
3642      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3643      */
3644     @InspectableProperty
getLastBaselineToBottomHeight()3645     public int getLastBaselineToBottomHeight() {
3646         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3647     }
3648 
3649     /**
3650      * Gets the autolink mask of the text.
3651      *
3652      * See {@link Linkify#ALL} and peers for possible values.
3653      *
3654      * @attr ref android.R.styleable#TextView_autoLink
3655      */
3656     @InspectableProperty(name = "autoLink", flagMapping = {
3657             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3658             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3659             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3660             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3661     })
getAutoLinkMask()3662     public final int getAutoLinkMask() {
3663         return mAutoLinkMask;
3664     }
3665 
3666     /**
3667      * Sets the Drawable corresponding to the selection handle used for
3668      * positioning the cursor within text. The Drawable defaults to the value
3669      * of the textSelectHandle attribute.
3670      * Note that any change applied to the handle Drawable will not be visible
3671      * until the handle is hidden and then drawn again.
3672      *
3673      * @see #setTextSelectHandle(int)
3674      * @attr ref android.R.styleable#TextView_textSelectHandle
3675      */
3676     @android.view.RemotableViewMethod
setTextSelectHandle(@onNull Drawable textSelectHandle)3677     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3678         Preconditions.checkNotNull(textSelectHandle,
3679                 "The text select handle should not be null.");
3680         mTextSelectHandle = textSelectHandle;
3681         mTextSelectHandleRes = 0;
3682         if (mEditor != null) {
3683             mEditor.loadHandleDrawables(true /* overwrite */);
3684         }
3685     }
3686 
3687     /**
3688      * Sets the Drawable corresponding to the selection handle used for
3689      * positioning the cursor within text. The Drawable defaults to the value
3690      * of the textSelectHandle attribute.
3691      * Note that any change applied to the handle Drawable will not be visible
3692      * until the handle is hidden and then drawn again.
3693      *
3694      * @see #setTextSelectHandle(Drawable)
3695      * @attr ref android.R.styleable#TextView_textSelectHandle
3696      */
3697     @android.view.RemotableViewMethod
setTextSelectHandle(@rawableRes int textSelectHandle)3698     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3699         Preconditions.checkArgument(textSelectHandle != 0,
3700                 "The text select handle should be a valid drawable resource id.");
3701         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3702     }
3703 
3704     /**
3705      * Returns the Drawable corresponding to the selection handle used
3706      * for positioning the cursor within text.
3707      * Note that any change applied to the handle Drawable will not be visible
3708      * until the handle is hidden and then drawn again.
3709      *
3710      * @return the text select handle drawable
3711      *
3712      * @see #setTextSelectHandle(Drawable)
3713      * @see #setTextSelectHandle(int)
3714      * @attr ref android.R.styleable#TextView_textSelectHandle
3715      */
getTextSelectHandle()3716     @Nullable public Drawable getTextSelectHandle() {
3717         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3718             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3719         }
3720         return mTextSelectHandle;
3721     }
3722 
3723     /**
3724      * Sets the Drawable corresponding to the left handle used
3725      * for selecting text. The Drawable defaults to the value of the
3726      * textSelectHandleLeft attribute.
3727      * Note that any change applied to the handle Drawable will not be visible
3728      * until the handle is hidden and then drawn again.
3729      *
3730      * @see #setTextSelectHandleLeft(int)
3731      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3732      */
3733     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3734     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3735         Preconditions.checkNotNull(textSelectHandleLeft,
3736                 "The left text select handle should not be null.");
3737         mTextSelectHandleLeft = textSelectHandleLeft;
3738         mTextSelectHandleLeftRes = 0;
3739         if (mEditor != null) {
3740             mEditor.loadHandleDrawables(true /* overwrite */);
3741         }
3742     }
3743 
3744     /**
3745      * Sets the Drawable corresponding to the left handle used
3746      * for selecting text. The Drawable defaults to the value of the
3747      * textSelectHandleLeft attribute.
3748      * Note that any change applied to the handle Drawable will not be visible
3749      * until the handle is hidden and then drawn again.
3750      *
3751      * @see #setTextSelectHandleLeft(Drawable)
3752      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3753      */
3754     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3755     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
3756         Preconditions.checkArgument(textSelectHandleLeft != 0,
3757                 "The text select left handle should be a valid drawable resource id.");
3758         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
3759     }
3760 
3761     /**
3762      * Returns the Drawable corresponding to the left handle used
3763      * for selecting text.
3764      * Note that any change applied to the handle Drawable will not be visible
3765      * until the handle is hidden and then drawn again.
3766      *
3767      * @return the left text selection handle drawable
3768      *
3769      * @see #setTextSelectHandleLeft(Drawable)
3770      * @see #setTextSelectHandleLeft(int)
3771      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3772      */
getTextSelectHandleLeft()3773     @Nullable public Drawable getTextSelectHandleLeft() {
3774         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
3775             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
3776         }
3777         return mTextSelectHandleLeft;
3778     }
3779 
3780     /**
3781      * Sets the Drawable corresponding to the right handle used
3782      * for selecting text. The Drawable defaults to the value of the
3783      * textSelectHandleRight attribute.
3784      * Note that any change applied to the handle Drawable will not be visible
3785      * until the handle is hidden and then drawn again.
3786      *
3787      * @see #setTextSelectHandleRight(int)
3788      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3789      */
3790     @android.view.RemotableViewMethod
setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3791     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
3792         Preconditions.checkNotNull(textSelectHandleRight,
3793                 "The right text select handle should not be null.");
3794         mTextSelectHandleRight = textSelectHandleRight;
3795         mTextSelectHandleRightRes = 0;
3796         if (mEditor != null) {
3797             mEditor.loadHandleDrawables(true /* overwrite */);
3798         }
3799     }
3800 
3801     /**
3802      * Sets the Drawable corresponding to the right handle used
3803      * for selecting text. The Drawable defaults to the value of the
3804      * textSelectHandleRight attribute.
3805      * Note that any change applied to the handle Drawable will not be visible
3806      * until the handle is hidden and then drawn again.
3807      *
3808      * @see #setTextSelectHandleRight(Drawable)
3809      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3810      */
3811     @android.view.RemotableViewMethod
setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3812     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
3813         Preconditions.checkArgument(textSelectHandleRight != 0,
3814                 "The text select right handle should be a valid drawable resource id.");
3815         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
3816     }
3817 
3818     /**
3819      * Returns the Drawable corresponding to the right handle used
3820      * for selecting text.
3821      * Note that any change applied to the handle Drawable will not be visible
3822      * until the handle is hidden and then drawn again.
3823      *
3824      * @return the right text selection handle drawable
3825      *
3826      * @see #setTextSelectHandleRight(Drawable)
3827      * @see #setTextSelectHandleRight(int)
3828      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3829      */
getTextSelectHandleRight()3830     @Nullable public Drawable getTextSelectHandleRight() {
3831         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
3832             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
3833         }
3834         return mTextSelectHandleRight;
3835     }
3836 
3837     /**
3838      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3839      * value of the textCursorDrawable attribute.
3840      * Note that any change applied to the cursor Drawable will not be visible
3841      * until the cursor is hidden and then drawn again.
3842      *
3843      * @see #setTextCursorDrawable(int)
3844      * @attr ref android.R.styleable#TextView_textCursorDrawable
3845      */
setTextCursorDrawable(@ullable Drawable textCursorDrawable)3846     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
3847         mCursorDrawable = textCursorDrawable;
3848         mCursorDrawableRes = 0;
3849         if (mEditor != null) {
3850             mEditor.loadCursorDrawable();
3851         }
3852     }
3853 
3854     /**
3855      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3856      * value of the textCursorDrawable attribute.
3857      * Note that any change applied to the cursor Drawable will not be visible
3858      * until the cursor is hidden and then drawn again.
3859      *
3860      * @see #setTextCursorDrawable(Drawable)
3861      * @attr ref android.R.styleable#TextView_textCursorDrawable
3862      */
setTextCursorDrawable(@rawableRes int textCursorDrawable)3863     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
3864         setTextCursorDrawable(
3865                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
3866     }
3867 
3868     /**
3869      * Returns the Drawable corresponding to the text cursor.
3870      * Note that any change applied to the cursor Drawable will not be visible
3871      * until the cursor is hidden and then drawn again.
3872      *
3873      * @return the text cursor drawable
3874      *
3875      * @see #setTextCursorDrawable(Drawable)
3876      * @see #setTextCursorDrawable(int)
3877      * @attr ref android.R.styleable#TextView_textCursorDrawable
3878      */
getTextCursorDrawable()3879     @Nullable public Drawable getTextCursorDrawable() {
3880         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
3881             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
3882         }
3883         return mCursorDrawable;
3884     }
3885 
3886     /**
3887      * Sets the text appearance from the specified style resource.
3888      * <p>
3889      * Use a framework-defined {@code TextAppearance} style like
3890      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3891      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3892      * set of attributes that can be used in a custom style.
3893      *
3894      * @param resId the resource identifier of the style to apply
3895      * @attr ref android.R.styleable#TextView_textAppearance
3896      */
3897     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)3898     public void setTextAppearance(@StyleRes int resId) {
3899         setTextAppearance(mContext, resId);
3900     }
3901 
3902     /**
3903      * Sets the text color, size, style, hint color, and highlight color
3904      * from the specified TextAppearance resource.
3905      *
3906      * @deprecated Use {@link #setTextAppearance(int)} instead.
3907      */
3908     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)3909     public void setTextAppearance(Context context, @StyleRes int resId) {
3910         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3911         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
3912         readTextAppearance(context, ta, attributes, false /* styleArray */);
3913         ta.recycle();
3914         applyTextAppearance(attributes);
3915     }
3916 
3917     /**
3918      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
3919      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
3920      */
3921     private static class TextAppearanceAttributes {
3922         int mTextColorHighlight = 0;
3923         ColorStateList mTextColor = null;
3924         ColorStateList mTextColorHint = null;
3925         ColorStateList mTextColorLink = null;
3926         int mTextSize = -1;
3927         int mTextSizeUnit = -1;
3928         LocaleList mTextLocales = null;
3929         String mFontFamily = null;
3930         Typeface mFontTypeface = null;
3931         boolean mFontFamilyExplicit = false;
3932         int mTypefaceIndex = -1;
3933         int mTextStyle = 0;
3934         int mFontWeight = -1;
3935         boolean mAllCaps = false;
3936         int mShadowColor = 0;
3937         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
3938         boolean mHasElegant = false;
3939         boolean mElegant = false;
3940         boolean mHasFallbackLineSpacing = false;
3941         boolean mFallbackLineSpacing = false;
3942         boolean mHasLetterSpacing = false;
3943         float mLetterSpacing = 0;
3944         String mFontFeatureSettings = null;
3945         String mFontVariationSettings = null;
3946 
3947         @Override
toString()3948         public String toString() {
3949             return "TextAppearanceAttributes {\n"
3950                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
3951                     + "    mTextColor:" + mTextColor + "\n"
3952                     + "    mTextColorHint:" + mTextColorHint + "\n"
3953                     + "    mTextColorLink:" + mTextColorLink + "\n"
3954                     + "    mTextSize:" + mTextSize + "\n"
3955                     + "    mTextSizeUnit:" + mTextSizeUnit + "\n"
3956                     + "    mTextLocales:" + mTextLocales + "\n"
3957                     + "    mFontFamily:" + mFontFamily + "\n"
3958                     + "    mFontTypeface:" + mFontTypeface + "\n"
3959                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
3960                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
3961                     + "    mTextStyle:" + mTextStyle + "\n"
3962                     + "    mFontWeight:" + mFontWeight + "\n"
3963                     + "    mAllCaps:" + mAllCaps + "\n"
3964                     + "    mShadowColor:" + mShadowColor + "\n"
3965                     + "    mShadowDx:" + mShadowDx + "\n"
3966                     + "    mShadowDy:" + mShadowDy + "\n"
3967                     + "    mShadowRadius:" + mShadowRadius + "\n"
3968                     + "    mHasElegant:" + mHasElegant + "\n"
3969                     + "    mElegant:" + mElegant + "\n"
3970                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
3971                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
3972                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
3973                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
3974                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
3975                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
3976                     + "}";
3977         }
3978     }
3979 
3980     // Maps styleable attributes that exist both in TextView style and TextAppearance.
3981     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
3982     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3983         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
3984                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3985         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
3986                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3987         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
3988                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3989         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
3990                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3991         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
3992                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3993         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
3994                 com.android.internal.R.styleable.TextAppearance_textLocale);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3995         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
3996                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3997         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
3998                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3999         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
4000                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4001         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
4002                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4003         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
4004                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4005         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
4006                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4007         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
4008                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4009         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
4010                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4011         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
4012                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4013         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
4014                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4015         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
4016                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4017         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
4018                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4019         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
4020                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4021         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
4022                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
4023     }
4024 
4025     /**
4026      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
4027      * set. If the TypedArray contains a value that was already set in the given attributes, that
4028      * will be overridden.
4029      *
4030      * @param context The Context to be used
4031      * @param appearance The TypedArray to read properties from
4032      * @param attributes the TextAppearanceAttributes to fill in
4033      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
4034      *                   what attribute indexes will be used to read the properties.
4035      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)4036     private void readTextAppearance(Context context, TypedArray appearance,
4037             TextAppearanceAttributes attributes, boolean styleArray) {
4038         final int n = appearance.getIndexCount();
4039         for (int i = 0; i < n; i++) {
4040             final int attr = appearance.getIndex(i);
4041             int index = attr;
4042             // Translate style array index ids to TextAppearance ids.
4043             if (styleArray) {
4044                 index = sAppearanceValues.get(attr, -1);
4045                 if (index == -1) {
4046                     // This value is not part of a Text Appearance and should be ignored.
4047                     continue;
4048                 }
4049             }
4050             switch (index) {
4051                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
4052                     attributes.mTextColorHighlight =
4053                             appearance.getColor(attr, attributes.mTextColorHighlight);
4054                     break;
4055                 case com.android.internal.R.styleable.TextAppearance_textColor:
4056                     attributes.mTextColor = appearance.getColorStateList(attr);
4057                     break;
4058                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
4059                     attributes.mTextColorHint = appearance.getColorStateList(attr);
4060                     break;
4061                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
4062                     attributes.mTextColorLink = appearance.getColorStateList(attr);
4063                     break;
4064                 case com.android.internal.R.styleable.TextAppearance_textSize:
4065                     attributes.mTextSize =
4066                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
4067                     attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit();
4068                     break;
4069                 case com.android.internal.R.styleable.TextAppearance_textLocale:
4070                     final String localeString = appearance.getString(attr);
4071                     if (localeString != null) {
4072                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
4073                         if (!localeList.isEmpty()) {
4074                             attributes.mTextLocales = localeList;
4075                         }
4076                     }
4077                     break;
4078                 case com.android.internal.R.styleable.TextAppearance_typeface:
4079                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
4080                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4081                         attributes.mFontFamily = null;
4082                     }
4083                     break;
4084                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
4085                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
4086                         try {
4087                             attributes.mFontTypeface = appearance.getFont(attr);
4088                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
4089                             // Expected if it is not a font resource.
4090                         }
4091                     }
4092                     if (attributes.mFontTypeface == null) {
4093                         attributes.mFontFamily = appearance.getString(attr);
4094                     }
4095                     attributes.mFontFamilyExplicit = true;
4096                     break;
4097                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4098                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4099                     break;
4100                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4101                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4102                     break;
4103                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4104                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4105                     break;
4106                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4107                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4108                     break;
4109                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4110                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4111                     break;
4112                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4113                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4114                     break;
4115                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4116                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4117                     break;
4118                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4119                     attributes.mHasElegant = true;
4120                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4121                     break;
4122                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4123                     attributes.mHasFallbackLineSpacing = true;
4124                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4125                             attributes.mFallbackLineSpacing);
4126                     break;
4127                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4128                     attributes.mHasLetterSpacing = true;
4129                     attributes.mLetterSpacing =
4130                             appearance.getFloat(attr, attributes.mLetterSpacing);
4131                     break;
4132                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4133                     attributes.mFontFeatureSettings = appearance.getString(attr);
4134                     break;
4135                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4136                     attributes.mFontVariationSettings = appearance.getString(attr);
4137                     break;
4138                 default:
4139             }
4140         }
4141     }
4142 
applyTextAppearance(TextAppearanceAttributes attributes)4143     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4144         if (attributes.mTextColor != null) {
4145             setTextColor(attributes.mTextColor);
4146         }
4147 
4148         if (attributes.mTextColorHint != null) {
4149             setHintTextColor(attributes.mTextColorHint);
4150         }
4151 
4152         if (attributes.mTextColorLink != null) {
4153             setLinkTextColor(attributes.mTextColorLink);
4154         }
4155 
4156         if (attributes.mTextColorHighlight != 0) {
4157             setHighlightColor(attributes.mTextColorHighlight);
4158         }
4159 
4160         if (attributes.mTextSize != -1) {
4161             mTextSizeUnit = attributes.mTextSizeUnit;
4162             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4163         }
4164 
4165         if (attributes.mTextLocales != null) {
4166             setTextLocales(attributes.mTextLocales);
4167         }
4168 
4169         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4170             attributes.mFontFamily = null;
4171         }
4172         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4173                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4174 
4175         if (attributes.mShadowColor != 0) {
4176             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4177                     attributes.mShadowColor);
4178         }
4179 
4180         if (attributes.mAllCaps) {
4181             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4182         }
4183 
4184         if (attributes.mHasElegant) {
4185             setElegantTextHeight(attributes.mElegant);
4186         }
4187 
4188         if (attributes.mHasFallbackLineSpacing) {
4189             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4190         }
4191 
4192         if (attributes.mHasLetterSpacing) {
4193             setLetterSpacing(attributes.mLetterSpacing);
4194         }
4195 
4196         if (attributes.mFontFeatureSettings != null) {
4197             setFontFeatureSettings(attributes.mFontFeatureSettings);
4198         }
4199 
4200         if (attributes.mFontVariationSettings != null) {
4201             setFontVariationSettings(attributes.mFontVariationSettings);
4202         }
4203     }
4204 
4205     /**
4206      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4207      * the first member of {@link #getTextLocales()}.
4208      * @return the default primary {@link Locale} of the text in this TextView.
4209      */
4210     @NonNull
getTextLocale()4211     public Locale getTextLocale() {
4212         return mTextPaint.getTextLocale();
4213     }
4214 
4215     /**
4216      * Get the default {@link LocaleList} of the text in this TextView.
4217      * @return the default {@link LocaleList} of the text in this TextView.
4218      */
4219     @NonNull @Size(min = 1)
getTextLocales()4220     public LocaleList getTextLocales() {
4221         return mTextPaint.getTextLocales();
4222     }
4223 
changeListenerLocaleTo(@ullable Locale locale)4224     private void changeListenerLocaleTo(@Nullable Locale locale) {
4225         if (mListenerChanged) {
4226             // If a listener has been explicitly set, don't change it. We may break something.
4227             return;
4228         }
4229         // The following null check is not absolutely necessary since all calling points of
4230         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4231         // here in case others would want to call this method in the future.
4232         if (mEditor != null) {
4233             KeyListener listener = mEditor.mKeyListener;
4234             if (listener instanceof DigitsKeyListener) {
4235                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4236             } else if (listener instanceof DateKeyListener) {
4237                 listener = DateKeyListener.getInstance(locale);
4238             } else if (listener instanceof TimeKeyListener) {
4239                 listener = TimeKeyListener.getInstance(locale);
4240             } else if (listener instanceof DateTimeKeyListener) {
4241                 listener = DateTimeKeyListener.getInstance(locale);
4242             } else {
4243                 return;
4244             }
4245             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4246             setKeyListenerOnly(listener);
4247             setInputTypeFromEditor();
4248             if (wasPasswordType) {
4249                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4250                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4251                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4252                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4253                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4254                 }
4255             }
4256         }
4257     }
4258 
4259     /**
4260      * Set the default {@link Locale} of the text in this TextView to a one-member
4261      * {@link LocaleList} containing just the given Locale.
4262      *
4263      * @param locale the {@link Locale} for drawing text, must not be null.
4264      *
4265      * @see #setTextLocales
4266      */
setTextLocale(@onNull Locale locale)4267     public void setTextLocale(@NonNull Locale locale) {
4268         mLocalesChanged = true;
4269         mTextPaint.setTextLocale(locale);
4270         if (mLayout != null) {
4271             nullLayouts();
4272             requestLayout();
4273             invalidate();
4274         }
4275     }
4276 
4277     /**
4278      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4279      *
4280      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4281      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4282      * other aspects of text display, including line breaking.
4283      *
4284      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4285      *
4286      * @see Paint#setTextLocales
4287      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)4288     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4289         mLocalesChanged = true;
4290         mTextPaint.setTextLocales(locales);
4291         if (mLayout != null) {
4292             nullLayouts();
4293             requestLayout();
4294             invalidate();
4295         }
4296     }
4297 
4298     @Override
onConfigurationChanged(Configuration newConfig)4299     protected void onConfigurationChanged(Configuration newConfig) {
4300         super.onConfigurationChanged(newConfig);
4301         if (!mLocalesChanged) {
4302             mTextPaint.setTextLocales(LocaleList.getDefault());
4303             if (mLayout != null) {
4304                 nullLayouts();
4305                 requestLayout();
4306                 invalidate();
4307             }
4308         }
4309         if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) {
4310             mFontWeightAdjustment = newConfig.fontWeightAdjustment;
4311             setTypeface(getTypeface());
4312         }
4313     }
4314 
4315     /**
4316      * @return the size (in pixels) of the default text size in this TextView.
4317      */
4318     @InspectableProperty
4319     @ViewDebug.ExportedProperty(category = "text")
getTextSize()4320     public float getTextSize() {
4321         return mTextPaint.getTextSize();
4322     }
4323 
4324     /**
4325      * @return the size (in scaled pixels) of the default text size in this TextView.
4326      * @hide
4327      */
4328     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()4329     public float getScaledTextSize() {
4330         return mTextPaint.getTextSize() / mTextPaint.density;
4331     }
4332 
4333     /** @hide */
4334     @ViewDebug.ExportedProperty(category = "text", mapping = {
4335             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4336             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4337             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4338             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4339     })
getTypefaceStyle()4340     public int getTypefaceStyle() {
4341         Typeface typeface = mTextPaint.getTypeface();
4342         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4343     }
4344 
4345     /**
4346      * Set the default text size to the given value, interpreted as "scaled
4347      * pixel" units.  This size is adjusted based on the current density and
4348      * user font size preference.
4349      *
4350      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4351      *
4352      * @param size The scaled pixel size.
4353      *
4354      * @attr ref android.R.styleable#TextView_textSize
4355      */
4356     @android.view.RemotableViewMethod
setTextSize(float size)4357     public void setTextSize(float size) {
4358         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4359     }
4360 
4361     /**
4362      * Set the default text size to a given unit and value. See {@link
4363      * TypedValue} for the possible dimension units.
4364      *
4365      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4366      *
4367      * @param unit The desired dimension unit.
4368      * @param size The desired size in the given units.
4369      *
4370      * @attr ref android.R.styleable#TextView_textSize
4371      */
setTextSize(int unit, float size)4372     public void setTextSize(int unit, float size) {
4373         if (!isAutoSizeEnabled()) {
4374             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4375         }
4376     }
4377 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4378     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4379         Context c = getContext();
4380         Resources r;
4381 
4382         if (c == null) {
4383             r = Resources.getSystem();
4384         } else {
4385             r = c.getResources();
4386         }
4387 
4388         mTextSizeUnit = unit;
4389         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
4390                 shouldRequestLayout);
4391     }
4392 
4393     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setRawTextSize(float size, boolean shouldRequestLayout)4394     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4395         if (size != mTextPaint.getTextSize()) {
4396             mTextPaint.setTextSize(size);
4397 
4398             if (shouldRequestLayout && mLayout != null) {
4399                 // Do not auto-size right after setting the text size.
4400                 mNeedsAutoSizeText = false;
4401                 nullLayouts();
4402                 requestLayout();
4403                 invalidate();
4404             }
4405         }
4406     }
4407 
4408     /**
4409      * Gets the text size unit defined by the developer. It may be specified in resources or be
4410      * passed as the unit argument of {@link #setTextSize(int, float)} at runtime.
4411      *
4412      * @return the dimension type of the text size unit originally defined.
4413      * @see TypedValue#TYPE_DIMENSION
4414      */
getTextSizeUnit()4415     public int getTextSizeUnit() {
4416         return mTextSizeUnit;
4417     }
4418 
4419     /**
4420      * Gets the extent by which text should be stretched horizontally.
4421      * This will usually be 1.0.
4422      * @return The horizontal scale factor.
4423      */
4424     @InspectableProperty
getTextScaleX()4425     public float getTextScaleX() {
4426         return mTextPaint.getTextScaleX();
4427     }
4428 
4429     /**
4430      * Sets the horizontal scale factor for text. The default value
4431      * is 1.0. Values greater than 1.0 stretch the text wider.
4432      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4433      * @param size The horizontal scale factor.
4434      * @attr ref android.R.styleable#TextView_textScaleX
4435      */
4436     @android.view.RemotableViewMethod
setTextScaleX(float size)4437     public void setTextScaleX(float size) {
4438         if (size != mTextPaint.getTextScaleX()) {
4439             mUserSetTextScaleX = true;
4440             mTextPaint.setTextScaleX(size);
4441 
4442             if (mLayout != null) {
4443                 nullLayouts();
4444                 requestLayout();
4445                 invalidate();
4446             }
4447         }
4448     }
4449 
4450     /**
4451      * Sets the typeface and style in which the text should be displayed.
4452      * Note that not all Typeface families actually have bold and italic
4453      * variants, so you may need to use
4454      * {@link #setTypeface(Typeface, int)} to get the appearance
4455      * that you actually want.
4456      *
4457      * @see #getTypeface()
4458      *
4459      * @attr ref android.R.styleable#TextView_fontFamily
4460      * @attr ref android.R.styleable#TextView_typeface
4461      * @attr ref android.R.styleable#TextView_textStyle
4462      */
setTypeface(@ullable Typeface tf)4463     public void setTypeface(@Nullable Typeface tf) {
4464         mOriginalTypeface = tf;
4465         if (mFontWeightAdjustment != 0
4466                 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
4467             if (tf == null) {
4468                 tf = Typeface.DEFAULT;
4469             } else {
4470                 int newWeight = Math.min(
4471                         Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
4472                         FontStyle.FONT_WEIGHT_MAX);
4473                 int typefaceStyle = tf != null ? tf.getStyle() : 0;
4474                 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0;
4475                 tf = Typeface.create(tf, newWeight, italic);
4476             }
4477         }
4478         if (mTextPaint.getTypeface() != tf) {
4479             mTextPaint.setTypeface(tf);
4480 
4481             if (mLayout != null) {
4482                 nullLayouts();
4483                 requestLayout();
4484                 invalidate();
4485             }
4486         }
4487     }
4488 
4489     /**
4490      * Gets the current {@link Typeface} that is used to style the text.
4491      * @return The current Typeface.
4492      *
4493      * @see #setTypeface(Typeface)
4494      *
4495      * @attr ref android.R.styleable#TextView_fontFamily
4496      * @attr ref android.R.styleable#TextView_typeface
4497      * @attr ref android.R.styleable#TextView_textStyle
4498      */
4499     @InspectableProperty
getTypeface()4500     public Typeface getTypeface() {
4501         return mOriginalTypeface;
4502     }
4503 
4504     /**
4505      * Set the TextView's elegant height metrics flag. This setting selects font
4506      * variants that have not been compacted to fit Latin-based vertical
4507      * metrics, and also increases top and bottom bounds to provide more space.
4508      *
4509      * @param elegant set the paint's elegant metrics flag.
4510      *
4511      * @see #isElegantTextHeight()
4512      * @see Paint#isElegantTextHeight()
4513      *
4514      * @attr ref android.R.styleable#TextView_elegantTextHeight
4515      */
setElegantTextHeight(boolean elegant)4516     public void setElegantTextHeight(boolean elegant) {
4517         if (elegant != mTextPaint.isElegantTextHeight()) {
4518             mTextPaint.setElegantTextHeight(elegant);
4519             if (mLayout != null) {
4520                 nullLayouts();
4521                 requestLayout();
4522                 invalidate();
4523             }
4524         }
4525     }
4526 
4527     /**
4528      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4529      * displaying the text (which is needed to avoid text from consecutive lines running into
4530      * each other). If set, fallback fonts that end up getting used can increase the ascent
4531      * and descent of the lines that they are used on.
4532      * <p/>
4533      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4534      * is typically much taller or deeper than Latin text.
4535      *
4536      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4537      *
4538      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4539      *
4540      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4541      */
setFallbackLineSpacing(boolean enabled)4542     public void setFallbackLineSpacing(boolean enabled) {
4543         if (mUseFallbackLineSpacing != enabled) {
4544             mUseFallbackLineSpacing = enabled;
4545             if (mLayout != null) {
4546                 nullLayouts();
4547                 requestLayout();
4548                 invalidate();
4549             }
4550         }
4551     }
4552 
4553     /**
4554      * @return whether fallback line spacing is enabled, {@code true} by default
4555      *
4556      * @see #setFallbackLineSpacing(boolean)
4557      *
4558      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4559      */
4560     @InspectableProperty
isFallbackLineSpacing()4561     public boolean isFallbackLineSpacing() {
4562         return mUseFallbackLineSpacing;
4563     }
4564 
4565     /**
4566      * Get the value of the TextView's elegant height metrics flag. This setting selects font
4567      * variants that have not been compacted to fit Latin-based vertical
4568      * metrics, and also increases top and bottom bounds to provide more space.
4569      * @return {@code true} if the elegant height metrics flag is set.
4570      *
4571      * @see #setElegantTextHeight(boolean)
4572      * @see Paint#setElegantTextHeight(boolean)
4573      */
4574     @InspectableProperty
isElegantTextHeight()4575     public boolean isElegantTextHeight() {
4576         return mTextPaint.isElegantTextHeight();
4577     }
4578 
4579     /**
4580      * Gets the text letter-space value, which determines the spacing between characters.
4581      * The value returned is in ems. Normally, this value is 0.0.
4582      * @return The text letter-space value in ems.
4583      *
4584      * @see #setLetterSpacing(float)
4585      * @see Paint#setLetterSpacing
4586      */
4587     @InspectableProperty
getLetterSpacing()4588     public float getLetterSpacing() {
4589         return mTextPaint.getLetterSpacing();
4590     }
4591 
4592     /**
4593      * Sets text letter-spacing in em units.  Typical values
4594      * for slight expansion will be around 0.05.  Negative values tighten text.
4595      *
4596      * @see #getLetterSpacing()
4597      * @see Paint#getLetterSpacing
4598      *
4599      * @param letterSpacing A text letter-space value in ems.
4600      * @attr ref android.R.styleable#TextView_letterSpacing
4601      */
4602     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)4603     public void setLetterSpacing(float letterSpacing) {
4604         if (letterSpacing != mTextPaint.getLetterSpacing()) {
4605             mTextPaint.setLetterSpacing(letterSpacing);
4606 
4607             if (mLayout != null) {
4608                 nullLayouts();
4609                 requestLayout();
4610                 invalidate();
4611             }
4612         }
4613     }
4614 
4615     /**
4616      * Returns the font feature settings. The format is the same as the CSS
4617      * font-feature-settings attribute:
4618      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4619      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4620      *
4621      * @return the currently set font feature settings.  Default is null.
4622      *
4623      * @see #setFontFeatureSettings(String)
4624      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
4625      */
4626     @InspectableProperty
4627     @Nullable
getFontFeatureSettings()4628     public String getFontFeatureSettings() {
4629         return mTextPaint.getFontFeatureSettings();
4630     }
4631 
4632     /**
4633      * Returns the font variation settings.
4634      *
4635      * @return the currently set font variation settings.  Returns null if no variation is
4636      * specified.
4637      *
4638      * @see #setFontVariationSettings(String)
4639      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
4640      */
4641     @Nullable
getFontVariationSettings()4642     public String getFontVariationSettings() {
4643         return mTextPaint.getFontVariationSettings();
4644     }
4645 
4646     /**
4647      * Sets the break strategy for breaking paragraphs into lines. The default value for
4648      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
4649      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
4650      * text "dancing" when being edited.
4651      * <p/>
4652      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4653      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4654      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4655      * improves the structure of text layout however has performance impact and requires more time
4656      * to do the text layout.
4657      *
4658      * @attr ref android.R.styleable#TextView_breakStrategy
4659      * @see #getBreakStrategy()
4660      * @see #setHyphenationFrequency(int)
4661      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4662     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
4663         mBreakStrategy = breakStrategy;
4664         if (mLayout != null) {
4665             nullLayouts();
4666             requestLayout();
4667             invalidate();
4668         }
4669     }
4670 
4671     /**
4672      * Gets the current strategy for breaking paragraphs into lines.
4673      * @return the current strategy for breaking paragraphs into lines.
4674      *
4675      * @attr ref android.R.styleable#TextView_breakStrategy
4676      * @see #setBreakStrategy(int)
4677      */
4678     @InspectableProperty(enumMapping = {
4679             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
4680             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
4681             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
4682     })
4683     @Layout.BreakStrategy
getBreakStrategy()4684     public int getBreakStrategy() {
4685         return mBreakStrategy;
4686     }
4687 
4688     /**
4689      * Sets the frequency of automatic hyphenation to use when determining word breaks.
4690      * The default value for both TextView and {@link EditText} is
4691      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
4692      * is set from the theme.
4693      * <p/>
4694      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4695      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4696      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4697      * improves the structure of text layout however has performance impact and requires more time
4698      * to do the text layout.
4699      * <p/>
4700      * Note: Before Android Q, in the theme hyphenation frequency is set to
4701      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
4702      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
4703      *
4704      * @param hyphenationFrequency the hyphenation frequency to use, one of
4705      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
4706      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
4707      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
4708      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4709      * @see #getHyphenationFrequency()
4710      * @see #getBreakStrategy()
4711      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4712     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
4713         mHyphenationFrequency = hyphenationFrequency;
4714         if (mLayout != null) {
4715             nullLayouts();
4716             requestLayout();
4717             invalidate();
4718         }
4719     }
4720 
4721     /**
4722      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
4723      * @return the current frequency of automatic hyphenation to be used when determining word
4724      * breaks.
4725      *
4726      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4727      * @see #setHyphenationFrequency(int)
4728      */
4729     @InspectableProperty(enumMapping = {
4730             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
4731             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
4732             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
4733     })
4734     @Layout.HyphenationFrequency
getHyphenationFrequency()4735     public int getHyphenationFrequency() {
4736         return mHyphenationFrequency;
4737     }
4738 
4739     /**
4740      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
4741      *
4742      * @return a current {@link PrecomputedText.Params}
4743      * @see PrecomputedText
4744      */
getTextMetricsParams()4745     public @NonNull PrecomputedText.Params getTextMetricsParams() {
4746         return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
4747                 mBreakStrategy, mHyphenationFrequency);
4748     }
4749 
4750     /**
4751      * Apply the text layout parameter.
4752      *
4753      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
4754      * @see PrecomputedText
4755      */
setTextMetricsParams(@onNull PrecomputedText.Params params)4756     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
4757         mTextPaint.set(params.getTextPaint());
4758         mUserSetTextScaleX = true;
4759         mTextDir = params.getTextDirection();
4760         mBreakStrategy = params.getBreakStrategy();
4761         mHyphenationFrequency = params.getHyphenationFrequency();
4762         if (mLayout != null) {
4763             nullLayouts();
4764             requestLayout();
4765             invalidate();
4766         }
4767     }
4768 
4769     /**
4770      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
4771      * last line is too short for justification, the last line will be displayed with the
4772      * alignment set by {@link android.view.View#setTextAlignment}.
4773      *
4774      * @see #getJustificationMode()
4775      */
4776     @Layout.JustificationMode
4777     @android.view.RemotableViewMethod
setJustificationMode(@ayout.JustificationMode int justificationMode)4778     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
4779         mJustificationMode = justificationMode;
4780         if (mLayout != null) {
4781             nullLayouts();
4782             requestLayout();
4783             invalidate();
4784         }
4785     }
4786 
4787     /**
4788      * @return true if currently paragraph justification mode.
4789      *
4790      * @see #setJustificationMode(int)
4791      */
4792     @InspectableProperty(enumMapping = {
4793             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
4794             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
4795     })
getJustificationMode()4796     public @Layout.JustificationMode int getJustificationMode() {
4797         return mJustificationMode;
4798     }
4799 
4800     /**
4801      * Sets font feature settings. The format is the same as the CSS
4802      * font-feature-settings attribute:
4803      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4804      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4805      *
4806      * @param fontFeatureSettings font feature settings represented as CSS compatible string
4807      *
4808      * @see #getFontFeatureSettings()
4809      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
4810      *
4811      * @attr ref android.R.styleable#TextView_fontFeatureSettings
4812      */
4813     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)4814     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
4815         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
4816             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
4817 
4818             if (mLayout != null) {
4819                 nullLayouts();
4820                 requestLayout();
4821                 invalidate();
4822             }
4823         }
4824     }
4825 
4826 
4827     /**
4828      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
4829      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
4830      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
4831      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
4832      * are invalid. If a specified axis name is not defined in the font, the settings will be
4833      * ignored.
4834      *
4835      * <p>
4836      * Examples,
4837      * <ul>
4838      * <li>Set font width to 150.
4839      * <pre>
4840      * <code>
4841      *   TextView textView = (TextView) findViewById(R.id.textView);
4842      *   textView.setFontVariationSettings("'wdth' 150");
4843      * </code>
4844      * </pre>
4845      * </li>
4846      *
4847      * <li>Set the font slant to 20 degrees and ask for italic style.
4848      * <pre>
4849      * <code>
4850      *   TextView textView = (TextView) findViewById(R.id.textView);
4851      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
4852      * </code>
4853      * </pre>
4854      * </p>
4855      * </li>
4856      * </ul>
4857      *
4858      * @param fontVariationSettings font variation settings. You can pass null or empty string as
4859      *                              no variation settings.
4860      * @return true if the given settings is effective to at least one font file underlying this
4861      *         TextView. This function also returns true for empty settings string. Otherwise
4862      *         returns false.
4863      *
4864      * @throws IllegalArgumentException If given string is not a valid font variation settings
4865      *                                  format.
4866      *
4867      * @see #getFontVariationSettings()
4868      * @see FontVariationAxis
4869      *
4870      * @attr ref android.R.styleable#TextView_fontVariationSettings
4871      */
setFontVariationSettings(@ullable String fontVariationSettings)4872     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
4873         final String existingSettings = mTextPaint.getFontVariationSettings();
4874         if (fontVariationSettings == existingSettings
4875                 || (fontVariationSettings != null
4876                         && fontVariationSettings.equals(existingSettings))) {
4877             return true;
4878         }
4879         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
4880 
4881         if (effective && mLayout != null) {
4882             nullLayouts();
4883             requestLayout();
4884             invalidate();
4885         }
4886         return effective;
4887     }
4888 
4889     /**
4890      * Sets the text color for all the states (normal, selected,
4891      * focused) to be this color.
4892      *
4893      * @param color A color value in the form 0xAARRGGBB.
4894      * Do not pass a resource ID. To get a color value from a resource ID, call
4895      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
4896      *
4897      * @see #setTextColor(ColorStateList)
4898      * @see #getTextColors()
4899      *
4900      * @attr ref android.R.styleable#TextView_textColor
4901      */
4902     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)4903     public void setTextColor(@ColorInt int color) {
4904         mTextColor = ColorStateList.valueOf(color);
4905         updateTextColors();
4906     }
4907 
4908     /**
4909      * Sets the text color.
4910      *
4911      * @see #setTextColor(int)
4912      * @see #getTextColors()
4913      * @see #setHintTextColor(ColorStateList)
4914      * @see #setLinkTextColor(ColorStateList)
4915      *
4916      * @attr ref android.R.styleable#TextView_textColor
4917      */
4918     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)4919     public void setTextColor(ColorStateList colors) {
4920         if (colors == null) {
4921             throw new NullPointerException();
4922         }
4923 
4924         mTextColor = colors;
4925         updateTextColors();
4926     }
4927 
4928     /**
4929      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
4930      *
4931      * @see #setTextColor(ColorStateList)
4932      * @see #setTextColor(int)
4933      *
4934      * @attr ref android.R.styleable#TextView_textColor
4935      */
4936     @InspectableProperty(name = "textColor")
getTextColors()4937     public final ColorStateList getTextColors() {
4938         return mTextColor;
4939     }
4940 
4941     /**
4942      * Return the current color selected for normal text.
4943      *
4944      * @return Returns the current text color.
4945      */
4946     @ColorInt
getCurrentTextColor()4947     public final int getCurrentTextColor() {
4948         return mCurTextColor;
4949     }
4950 
4951     /**
4952      * Sets the color used to display the selection highlight.
4953      *
4954      * @attr ref android.R.styleable#TextView_textColorHighlight
4955      */
4956     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)4957     public void setHighlightColor(@ColorInt int color) {
4958         if (mHighlightColor != color) {
4959             mHighlightColor = color;
4960             invalidate();
4961         }
4962     }
4963 
4964     /**
4965      * @return the color used to display the selection highlight
4966      *
4967      * @see #setHighlightColor(int)
4968      *
4969      * @attr ref android.R.styleable#TextView_textColorHighlight
4970      */
4971     @InspectableProperty(name = "textColorHighlight")
4972     @ColorInt
getHighlightColor()4973     public int getHighlightColor() {
4974         return mHighlightColor;
4975     }
4976 
4977     /**
4978      * Sets whether the soft input method will be made visible when this
4979      * TextView gets focused. The default is true.
4980      */
4981     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)4982     public final void setShowSoftInputOnFocus(boolean show) {
4983         createEditorIfNeeded();
4984         mEditor.mShowSoftInputOnFocus = show;
4985     }
4986 
4987     /**
4988      * Returns whether the soft input method will be made visible when this
4989      * TextView gets focused. The default is true.
4990      */
getShowSoftInputOnFocus()4991     public final boolean getShowSoftInputOnFocus() {
4992         // When there is no Editor, return default true value
4993         return mEditor == null || mEditor.mShowSoftInputOnFocus;
4994     }
4995 
4996     /**
4997      * Gives the text a shadow of the specified blur radius and color, the specified
4998      * distance from its drawn position.
4999      * <p>
5000      * The text shadow produced does not interact with the properties on view
5001      * that are responsible for real time shadows,
5002      * {@link View#getElevation() elevation} and
5003      * {@link View#getTranslationZ() translationZ}.
5004      *
5005      * @see Paint#setShadowLayer(float, float, float, int)
5006      *
5007      * @attr ref android.R.styleable#TextView_shadowColor
5008      * @attr ref android.R.styleable#TextView_shadowDx
5009      * @attr ref android.R.styleable#TextView_shadowDy
5010      * @attr ref android.R.styleable#TextView_shadowRadius
5011      */
setShadowLayer(float radius, float dx, float dy, int color)5012     public void setShadowLayer(float radius, float dx, float dy, int color) {
5013         mTextPaint.setShadowLayer(radius, dx, dy, color);
5014 
5015         mShadowRadius = radius;
5016         mShadowDx = dx;
5017         mShadowDy = dy;
5018         mShadowColor = color;
5019 
5020         // Will change text clip region
5021         if (mEditor != null) {
5022             mEditor.invalidateTextDisplayList();
5023             mEditor.invalidateHandlesAndActionMode();
5024         }
5025         invalidate();
5026     }
5027 
5028     /**
5029      * Gets the radius of the shadow layer.
5030      *
5031      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
5032      *
5033      * @see #setShadowLayer(float, float, float, int)
5034      *
5035      * @attr ref android.R.styleable#TextView_shadowRadius
5036      */
5037     @InspectableProperty
getShadowRadius()5038     public float getShadowRadius() {
5039         return mShadowRadius;
5040     }
5041 
5042     /**
5043      * @return the horizontal offset of the shadow layer
5044      *
5045      * @see #setShadowLayer(float, float, float, int)
5046      *
5047      * @attr ref android.R.styleable#TextView_shadowDx
5048      */
5049     @InspectableProperty
getShadowDx()5050     public float getShadowDx() {
5051         return mShadowDx;
5052     }
5053 
5054     /**
5055      * Gets the vertical offset of the shadow layer.
5056      * @return The vertical offset of the shadow layer.
5057      *
5058      * @see #setShadowLayer(float, float, float, int)
5059      *
5060      * @attr ref android.R.styleable#TextView_shadowDy
5061      */
5062     @InspectableProperty
getShadowDy()5063     public float getShadowDy() {
5064         return mShadowDy;
5065     }
5066 
5067     /**
5068      * Gets the color of the shadow layer.
5069      * @return the color of the shadow layer
5070      *
5071      * @see #setShadowLayer(float, float, float, int)
5072      *
5073      * @attr ref android.R.styleable#TextView_shadowColor
5074      */
5075     @InspectableProperty
5076     @ColorInt
getShadowColor()5077     public int getShadowColor() {
5078         return mShadowColor;
5079     }
5080 
5081     /**
5082      * Gets the {@link TextPaint} used for the text.
5083      * Use this only to consult the Paint's properties and not to change them.
5084      * @return The base paint used for the text.
5085      */
getPaint()5086     public TextPaint getPaint() {
5087         return mTextPaint;
5088     }
5089 
5090     /**
5091      * Sets the autolink mask of the text.  See {@link
5092      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
5093      * possible values.
5094      *
5095      * <p class="note"><b>Note:</b>
5096      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
5097      * is deprecated and should be avoided; see its documentation.
5098      *
5099      * @attr ref android.R.styleable#TextView_autoLink
5100      */
5101     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)5102     public final void setAutoLinkMask(int mask) {
5103         mAutoLinkMask = mask;
5104     }
5105 
5106     /**
5107      * Sets whether the movement method will automatically be set to
5108      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5109      * set to nonzero and links are detected in {@link #setText}.
5110      * The default is true.
5111      *
5112      * @attr ref android.R.styleable#TextView_linksClickable
5113      */
5114     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)5115     public final void setLinksClickable(boolean whether) {
5116         mLinksClickable = whether;
5117     }
5118 
5119     /**
5120      * Returns whether the movement method will automatically be set to
5121      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5122      * set to nonzero and links are detected in {@link #setText}.
5123      * The default is true.
5124      *
5125      * @attr ref android.R.styleable#TextView_linksClickable
5126      */
5127     @InspectableProperty
getLinksClickable()5128     public final boolean getLinksClickable() {
5129         return mLinksClickable;
5130     }
5131 
5132     /**
5133      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5134      * (by {@link Linkify} or otherwise) if any.  You can call
5135      * {@link URLSpan#getURL} on them to find where they link to
5136      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5137      * to find the region of the text they are attached to.
5138      */
getUrls()5139     public URLSpan[] getUrls() {
5140         if (mText instanceof Spanned) {
5141             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5142         } else {
5143             return new URLSpan[0];
5144         }
5145     }
5146 
5147     /**
5148      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5149      * TextView.
5150      *
5151      * @see #setHintTextColor(ColorStateList)
5152      * @see #getHintTextColors()
5153      * @see #setTextColor(int)
5154      *
5155      * @attr ref android.R.styleable#TextView_textColorHint
5156      */
5157     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)5158     public final void setHintTextColor(@ColorInt int color) {
5159         mHintTextColor = ColorStateList.valueOf(color);
5160         updateTextColors();
5161     }
5162 
5163     /**
5164      * Sets the color of the hint text.
5165      *
5166      * @see #getHintTextColors()
5167      * @see #setHintTextColor(int)
5168      * @see #setTextColor(ColorStateList)
5169      * @see #setLinkTextColor(ColorStateList)
5170      *
5171      * @attr ref android.R.styleable#TextView_textColorHint
5172      */
setHintTextColor(ColorStateList colors)5173     public final void setHintTextColor(ColorStateList colors) {
5174         mHintTextColor = colors;
5175         updateTextColors();
5176     }
5177 
5178     /**
5179      * @return the color of the hint text, for the different states of this TextView.
5180      *
5181      * @see #setHintTextColor(ColorStateList)
5182      * @see #setHintTextColor(int)
5183      * @see #setTextColor(ColorStateList)
5184      * @see #setLinkTextColor(ColorStateList)
5185      *
5186      * @attr ref android.R.styleable#TextView_textColorHint
5187      */
5188     @InspectableProperty(name = "textColorHint")
getHintTextColors()5189     public final ColorStateList getHintTextColors() {
5190         return mHintTextColor;
5191     }
5192 
5193     /**
5194      * <p>Return the current color selected to paint the hint text.</p>
5195      *
5196      * @return Returns the current hint text color.
5197      */
5198     @ColorInt
getCurrentHintTextColor()5199     public final int getCurrentHintTextColor() {
5200         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5201     }
5202 
5203     /**
5204      * Sets the color of links in the text.
5205      *
5206      * @see #setLinkTextColor(ColorStateList)
5207      * @see #getLinkTextColors()
5208      *
5209      * @attr ref android.R.styleable#TextView_textColorLink
5210      */
5211     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)5212     public final void setLinkTextColor(@ColorInt int color) {
5213         mLinkTextColor = ColorStateList.valueOf(color);
5214         updateTextColors();
5215     }
5216 
5217     /**
5218      * Sets the color of links in the text.
5219      *
5220      * @see #setLinkTextColor(int)
5221      * @see #getLinkTextColors()
5222      * @see #setTextColor(ColorStateList)
5223      * @see #setHintTextColor(ColorStateList)
5224      *
5225      * @attr ref android.R.styleable#TextView_textColorLink
5226      */
setLinkTextColor(ColorStateList colors)5227     public final void setLinkTextColor(ColorStateList colors) {
5228         mLinkTextColor = colors;
5229         updateTextColors();
5230     }
5231 
5232     /**
5233      * @return the list of colors used to paint the links in the text, for the different states of
5234      * this TextView
5235      *
5236      * @see #setLinkTextColor(ColorStateList)
5237      * @see #setLinkTextColor(int)
5238      *
5239      * @attr ref android.R.styleable#TextView_textColorLink
5240      */
5241     @InspectableProperty(name = "textColorLink")
getLinkTextColors()5242     public final ColorStateList getLinkTextColors() {
5243         return mLinkTextColor;
5244     }
5245 
5246     /**
5247      * Sets the horizontal alignment of the text and the
5248      * vertical gravity that will be used when there is extra space
5249      * in the TextView beyond what is required for the text itself.
5250      *
5251      * @see android.view.Gravity
5252      * @attr ref android.R.styleable#TextView_gravity
5253      */
5254     @android.view.RemotableViewMethod
setGravity(int gravity)5255     public void setGravity(int gravity) {
5256         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5257             gravity |= Gravity.START;
5258         }
5259         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5260             gravity |= Gravity.TOP;
5261         }
5262 
5263         boolean newLayout = false;
5264 
5265         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5266                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5267             newLayout = true;
5268         }
5269 
5270         if (gravity != mGravity) {
5271             invalidate();
5272         }
5273 
5274         mGravity = gravity;
5275 
5276         if (mLayout != null && newLayout) {
5277             // XXX this is heavy-handed because no actual content changes.
5278             int want = mLayout.getWidth();
5279             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5280 
5281             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5282                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5283         }
5284     }
5285 
5286     /**
5287      * Returns the horizontal and vertical alignment of this TextView.
5288      *
5289      * @see android.view.Gravity
5290      * @attr ref android.R.styleable#TextView_gravity
5291      */
5292     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()5293     public int getGravity() {
5294         return mGravity;
5295     }
5296 
5297     /**
5298      * Gets the flags on the Paint being used to display the text.
5299      * @return The flags on the Paint being used to display the text.
5300      * @see Paint#getFlags
5301      */
getPaintFlags()5302     public int getPaintFlags() {
5303         return mTextPaint.getFlags();
5304     }
5305 
5306     /**
5307      * Sets flags on the Paint being used to display the text and
5308      * reflows the text if they are different from the old flags.
5309      * @see Paint#setFlags
5310      */
5311     @android.view.RemotableViewMethod
setPaintFlags(int flags)5312     public void setPaintFlags(int flags) {
5313         if (mTextPaint.getFlags() != flags) {
5314             mTextPaint.setFlags(flags);
5315 
5316             if (mLayout != null) {
5317                 nullLayouts();
5318                 requestLayout();
5319                 invalidate();
5320             }
5321         }
5322     }
5323 
5324     /**
5325      * Sets whether the text should be allowed to be wider than the
5326      * View is.  If false, it will be wrapped to the width of the View.
5327      *
5328      * @attr ref android.R.styleable#TextView_scrollHorizontally
5329      */
setHorizontallyScrolling(boolean whether)5330     public void setHorizontallyScrolling(boolean whether) {
5331         if (mHorizontallyScrolling != whether) {
5332             mHorizontallyScrolling = whether;
5333 
5334             if (mLayout != null) {
5335                 nullLayouts();
5336                 requestLayout();
5337                 invalidate();
5338             }
5339         }
5340     }
5341 
5342     /**
5343      * Returns whether the text is allowed to be wider than the View.
5344      * If false, the text will be wrapped to the width of the View.
5345      *
5346      * @attr ref android.R.styleable#TextView_scrollHorizontally
5347      * @see #setHorizontallyScrolling(boolean)
5348      */
5349     @InspectableProperty(name = "scrollHorizontally")
isHorizontallyScrollable()5350     public final boolean isHorizontallyScrollable() {
5351         return mHorizontallyScrolling;
5352     }
5353 
5354     /**
5355      * Returns whether the text is allowed to be wider than the View.
5356      * If false, the text will be wrapped to the width of the View.
5357      *
5358      * @attr ref android.R.styleable#TextView_scrollHorizontally
5359      * @hide
5360      */
5361     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
getHorizontallyScrolling()5362     public boolean getHorizontallyScrolling() {
5363         return mHorizontallyScrolling;
5364     }
5365 
5366     /**
5367      * Sets the height of the TextView to be at least {@code minLines} 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 other previous minimum height configurations such
5371      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
5372      * this value to 1.
5373      *
5374      * @param minLines the minimum height of TextView in terms of number of lines
5375      *
5376      * @see #getMinLines()
5377      * @see #setLines(int)
5378      *
5379      * @attr ref android.R.styleable#TextView_minLines
5380      */
5381     @android.view.RemotableViewMethod
setMinLines(int minLines)5382     public void setMinLines(int minLines) {
5383         mMinimum = minLines;
5384         mMinMode = LINES;
5385 
5386         requestLayout();
5387         invalidate();
5388     }
5389 
5390     /**
5391      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
5392      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
5393      *
5394      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
5395      *         height is not defined in lines
5396      *
5397      * @see #setMinLines(int)
5398      * @see #setLines(int)
5399      *
5400      * @attr ref android.R.styleable#TextView_minLines
5401      */
5402     @InspectableProperty
getMinLines()5403     public int getMinLines() {
5404         return mMinMode == LINES ? mMinimum : -1;
5405     }
5406 
5407     /**
5408      * Sets the height of the TextView to be at least {@code minPixels} tall.
5409      * <p>
5410      * This value is used for height calculation if LayoutParams does not force TextView to have an
5411      * exact height. Setting this value overrides previous minimum height configurations such as
5412      * {@link #setMinLines(int)} or {@link #setLines(int)}.
5413      * <p>
5414      * The value given here is different than {@link #setMinimumHeight(int)}. Between
5415      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
5416      * used to decide the final height.
5417      *
5418      * @param minPixels the minimum height of TextView in terms of pixels
5419      *
5420      * @see #getMinHeight()
5421      * @see #setHeight(int)
5422      *
5423      * @attr ref android.R.styleable#TextView_minHeight
5424      */
5425     @android.view.RemotableViewMethod
setMinHeight(int minPixels)5426     public void setMinHeight(int minPixels) {
5427         mMinimum = minPixels;
5428         mMinMode = PIXELS;
5429 
5430         requestLayout();
5431         invalidate();
5432     }
5433 
5434     /**
5435      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
5436      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
5437      *
5438      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
5439      *         defined in pixels
5440      *
5441      * @see #setMinHeight(int)
5442      * @see #setHeight(int)
5443      *
5444      * @attr ref android.R.styleable#TextView_minHeight
5445      */
getMinHeight()5446     public int getMinHeight() {
5447         return mMinMode == PIXELS ? mMinimum : -1;
5448     }
5449 
5450     /**
5451      * Sets the height of the TextView to be at most {@code maxLines} tall.
5452      * <p>
5453      * This value is used for height calculation if LayoutParams does not force TextView to have an
5454      * exact height. Setting this value overrides previous maximum height configurations such as
5455      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
5456      *
5457      * @param maxLines the maximum height of TextView in terms of number of lines
5458      *
5459      * @see #getMaxLines()
5460      * @see #setLines(int)
5461      *
5462      * @attr ref android.R.styleable#TextView_maxLines
5463      */
5464     @android.view.RemotableViewMethod
setMaxLines(int maxLines)5465     public void setMaxLines(int maxLines) {
5466         mMaximum = maxLines;
5467         mMaxMode = LINES;
5468 
5469         requestLayout();
5470         invalidate();
5471     }
5472 
5473     /**
5474      * Returns the maximum height of TextView in terms of number of lines or -1 if the
5475      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
5476      *
5477      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
5478      *         is not defined in lines.
5479      *
5480      * @see #setMaxLines(int)
5481      * @see #setLines(int)
5482      *
5483      * @attr ref android.R.styleable#TextView_maxLines
5484      */
5485     @InspectableProperty
getMaxLines()5486     public int getMaxLines() {
5487         return mMaxMode == LINES ? mMaximum : -1;
5488     }
5489 
5490     /**
5491      * Sets the height of the TextView to be at most {@code maxPixels} tall.
5492      * <p>
5493      * This value is used for height calculation if LayoutParams does not force TextView to have an
5494      * exact height. Setting this value overrides previous maximum height configurations such as
5495      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
5496      *
5497      * @param maxPixels the maximum height of TextView in terms of pixels
5498      *
5499      * @see #getMaxHeight()
5500      * @see #setHeight(int)
5501      *
5502      * @attr ref android.R.styleable#TextView_maxHeight
5503      */
5504     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)5505     public void setMaxHeight(int maxPixels) {
5506         mMaximum = maxPixels;
5507         mMaxMode = PIXELS;
5508 
5509         requestLayout();
5510         invalidate();
5511     }
5512 
5513     /**
5514      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
5515      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
5516      *
5517      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
5518      *         is not defined in pixels
5519      *
5520      * @see #setMaxHeight(int)
5521      * @see #setHeight(int)
5522      *
5523      * @attr ref android.R.styleable#TextView_maxHeight
5524      */
5525     @InspectableProperty
getMaxHeight()5526     public int getMaxHeight() {
5527         return mMaxMode == PIXELS ? mMaximum : -1;
5528     }
5529 
5530     /**
5531      * Sets the height of the TextView to be exactly {@code lines} tall.
5532      * <p>
5533      * This value is used for height calculation if LayoutParams does not force TextView to have an
5534      * exact height. Setting this value overrides previous minimum/maximum height configurations
5535      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
5536      * set this value to 1.
5537      *
5538      * @param lines the exact height of the TextView in terms of lines
5539      *
5540      * @see #setHeight(int)
5541      *
5542      * @attr ref android.R.styleable#TextView_lines
5543      */
5544     @android.view.RemotableViewMethod
setLines(int lines)5545     public void setLines(int lines) {
5546         mMaximum = mMinimum = lines;
5547         mMaxMode = mMinMode = LINES;
5548 
5549         requestLayout();
5550         invalidate();
5551     }
5552 
5553     /**
5554      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
5555      * <p>
5556      * This value is used for height calculation if LayoutParams does not force TextView to have an
5557      * exact height. Setting this value overrides previous minimum/maximum height configurations
5558      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
5559      *
5560      * @param pixels the exact height of the TextView in terms of pixels
5561      *
5562      * @see #setLines(int)
5563      *
5564      * @attr ref android.R.styleable#TextView_height
5565      */
5566     @android.view.RemotableViewMethod
setHeight(int pixels)5567     public void setHeight(int pixels) {
5568         mMaximum = mMinimum = pixels;
5569         mMaxMode = mMinMode = PIXELS;
5570 
5571         requestLayout();
5572         invalidate();
5573     }
5574 
5575     /**
5576      * Sets the width of the TextView to be at least {@code minEms} 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 minimum width configurations such as
5580      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5581      *
5582      * @param minEms the minimum width of TextView in terms of ems
5583      *
5584      * @see #getMinEms()
5585      * @see #setEms(int)
5586      *
5587      * @attr ref android.R.styleable#TextView_minEms
5588      */
5589     @android.view.RemotableViewMethod
setMinEms(int minEms)5590     public void setMinEms(int minEms) {
5591         mMinWidth = minEms;
5592         mMinWidthMode = EMS;
5593 
5594         requestLayout();
5595         invalidate();
5596     }
5597 
5598     /**
5599      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
5600      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5601      *
5602      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
5603      *         defined in ems
5604      *
5605      * @see #setMinEms(int)
5606      * @see #setEms(int)
5607      *
5608      * @attr ref android.R.styleable#TextView_minEms
5609      */
5610     @InspectableProperty
getMinEms()5611     public int getMinEms() {
5612         return mMinWidthMode == EMS ? mMinWidth : -1;
5613     }
5614 
5615     /**
5616      * Sets the width of the TextView to be at least {@code minPixels} wide.
5617      * <p>
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 width configurations such as
5620      * {@link #setMinEms(int)} or {@link #setEms(int)}.
5621      * <p>
5622      * The value given here is different than {@link #setMinimumWidth(int)}. Between
5623      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
5624      * to decide the final width.
5625      *
5626      * @param minPixels the minimum width of TextView in terms of pixels
5627      *
5628      * @see #getMinWidth()
5629      * @see #setWidth(int)
5630      *
5631      * @attr ref android.R.styleable#TextView_minWidth
5632      */
5633     @android.view.RemotableViewMethod
setMinWidth(int minPixels)5634     public void setMinWidth(int minPixels) {
5635         mMinWidth = minPixels;
5636         mMinWidthMode = PIXELS;
5637 
5638         requestLayout();
5639         invalidate();
5640     }
5641 
5642     /**
5643      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
5644      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
5645      *
5646      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
5647      *         defined in pixels
5648      *
5649      * @see #setMinWidth(int)
5650      * @see #setWidth(int)
5651      *
5652      * @attr ref android.R.styleable#TextView_minWidth
5653      */
5654     @InspectableProperty
getMinWidth()5655     public int getMinWidth() {
5656         return mMinWidthMode == PIXELS ? mMinWidth : -1;
5657     }
5658 
5659     /**
5660      * Sets the width of the TextView to be at most {@code maxEms} wide.
5661      * <p>
5662      * This value is used for width calculation if LayoutParams does not force TextView to have an
5663      * exact width. Setting this value overrides previous maximum width configurations such as
5664      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5665      *
5666      * @param maxEms the maximum width of TextView in terms of ems
5667      *
5668      * @see #getMaxEms()
5669      * @see #setEms(int)
5670      *
5671      * @attr ref android.R.styleable#TextView_maxEms
5672      */
5673     @android.view.RemotableViewMethod
setMaxEms(int maxEms)5674     public void setMaxEms(int maxEms) {
5675         mMaxWidth = maxEms;
5676         mMaxWidthMode = EMS;
5677 
5678         requestLayout();
5679         invalidate();
5680     }
5681 
5682     /**
5683      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
5684      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5685      *
5686      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
5687      *         defined in ems
5688      *
5689      * @see #setMaxEms(int)
5690      * @see #setEms(int)
5691      *
5692      * @attr ref android.R.styleable#TextView_maxEms
5693      */
5694     @InspectableProperty
getMaxEms()5695     public int getMaxEms() {
5696         return mMaxWidthMode == EMS ? mMaxWidth : -1;
5697     }
5698 
5699     /**
5700      * Sets the width of the TextView to be at most {@code maxPixels} wide.
5701      * <p>
5702      * This value is used for width calculation if LayoutParams does not force TextView to have an
5703      * exact width. Setting this value overrides previous maximum width configurations such as
5704      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
5705      *
5706      * @param maxPixels the maximum width of TextView in terms of pixels
5707      *
5708      * @see #getMaxWidth()
5709      * @see #setWidth(int)
5710      *
5711      * @attr ref android.R.styleable#TextView_maxWidth
5712      */
5713     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)5714     public void setMaxWidth(int maxPixels) {
5715         mMaxWidth = maxPixels;
5716         mMaxWidthMode = PIXELS;
5717 
5718         requestLayout();
5719         invalidate();
5720     }
5721 
5722     /**
5723      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
5724      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
5725      *
5726      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
5727      *         defined in pixels
5728      *
5729      * @see #setMaxWidth(int)
5730      * @see #setWidth(int)
5731      *
5732      * @attr ref android.R.styleable#TextView_maxWidth
5733      */
5734     @InspectableProperty
getMaxWidth()5735     public int getMaxWidth() {
5736         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
5737     }
5738 
5739     /**
5740      * Sets the width of the TextView to be exactly {@code ems} wide.
5741      *
5742      * This value is used for width calculation if LayoutParams does not force TextView to have an
5743      * exact width. Setting this value overrides previous minimum/maximum configurations such as
5744      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
5745      *
5746      * @param ems the exact width of the TextView in terms of ems
5747      *
5748      * @see #setWidth(int)
5749      *
5750      * @attr ref android.R.styleable#TextView_ems
5751      */
5752     @android.view.RemotableViewMethod
setEms(int ems)5753     public void setEms(int ems) {
5754         mMaxWidth = mMinWidth = ems;
5755         mMaxWidthMode = mMinWidthMode = EMS;
5756 
5757         requestLayout();
5758         invalidate();
5759     }
5760 
5761     /**
5762      * Sets the width of the TextView to be exactly {@code pixels} wide.
5763      * <p>
5764      * This value is used for width calculation if LayoutParams does not force TextView to have an
5765      * exact width. Setting this value overrides previous minimum/maximum width configurations
5766      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
5767      *
5768      * @param pixels the exact width of the TextView in terms of pixels
5769      *
5770      * @see #setEms(int)
5771      *
5772      * @attr ref android.R.styleable#TextView_width
5773      */
5774     @android.view.RemotableViewMethod
setWidth(int pixels)5775     public void setWidth(int pixels) {
5776         mMaxWidth = mMinWidth = pixels;
5777         mMaxWidthMode = mMinWidthMode = PIXELS;
5778 
5779         requestLayout();
5780         invalidate();
5781     }
5782 
5783     /**
5784      * Sets line spacing for this TextView.  Each line other than the last line will have its height
5785      * multiplied by {@code mult} and have {@code add} added to it.
5786      *
5787      * @param add The value in pixels that should be added to each line other than the last line.
5788      *            This will be applied after the multiplier
5789      * @param mult The value by which each line height other than the last line will be multiplied
5790      *             by
5791      *
5792      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5793      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5794      */
setLineSpacing(float add, float mult)5795     public void setLineSpacing(float add, float mult) {
5796         if (mSpacingAdd != add || mSpacingMult != mult) {
5797             mSpacingAdd = add;
5798             mSpacingMult = mult;
5799 
5800             if (mLayout != null) {
5801                 nullLayouts();
5802                 requestLayout();
5803                 invalidate();
5804             }
5805         }
5806     }
5807 
5808     /**
5809      * Gets the line spacing multiplier
5810      *
5811      * @return the value by which each line's height is multiplied to get its actual height.
5812      *
5813      * @see #setLineSpacing(float, float)
5814      * @see #getLineSpacingExtra()
5815      *
5816      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5817      */
5818     @InspectableProperty
getLineSpacingMultiplier()5819     public float getLineSpacingMultiplier() {
5820         return mSpacingMult;
5821     }
5822 
5823     /**
5824      * Gets the line spacing extra space
5825      *
5826      * @return the extra space that is added to the height of each lines of this TextView.
5827      *
5828      * @see #setLineSpacing(float, float)
5829      * @see #getLineSpacingMultiplier()
5830      *
5831      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5832      */
5833     @InspectableProperty
getLineSpacingExtra()5834     public float getLineSpacingExtra() {
5835         return mSpacingAdd;
5836     }
5837 
5838     /**
5839      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
5840      * between subsequent baselines in the TextView.
5841      *
5842      * @param lineHeight the line height in pixels
5843      *
5844      * @see #setLineSpacing(float, float)
5845      * @see #getLineSpacingExtra()
5846      *
5847      * @attr ref android.R.styleable#TextView_lineHeight
5848      */
5849     @android.view.RemotableViewMethod
setLineHeight(@x @ntRangefrom = 0) int lineHeight)5850     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
5851         Preconditions.checkArgumentNonnegative(lineHeight);
5852 
5853         final int fontHeight = getPaint().getFontMetricsInt(null);
5854         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
5855         if (lineHeight != fontHeight) {
5856             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
5857             setLineSpacing(lineHeight - fontHeight, 1f);
5858         }
5859     }
5860 
5861     /**
5862      * Convenience method to append the specified text to the TextView's
5863      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5864      * if it was not already editable.
5865      *
5866      * @param text text to be appended to the already displayed text
5867      */
append(CharSequence text)5868     public final void append(CharSequence text) {
5869         append(text, 0, text.length());
5870     }
5871 
5872     /**
5873      * Convenience method to append the specified text slice to the TextView's
5874      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5875      * if it was not already editable.
5876      *
5877      * @param text text to be appended to the already displayed text
5878      * @param start the index of the first character in the {@code text}
5879      * @param end the index of the character following the last character in the {@code text}
5880      *
5881      * @see Appendable#append(CharSequence, int, int)
5882      */
append(CharSequence text, int start, int end)5883     public void append(CharSequence text, int start, int end) {
5884         if (!(mText instanceof Editable)) {
5885             setText(mText, BufferType.EDITABLE);
5886         }
5887 
5888         ((Editable) mText).append(text, start, end);
5889 
5890         if (mAutoLinkMask != 0) {
5891             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
5892             // Do not change the movement method for text that support text selection as it
5893             // would prevent an arbitrary cursor displacement.
5894             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
5895                 setMovementMethod(LinkMovementMethod.getInstance());
5896             }
5897         }
5898     }
5899 
updateTextColors()5900     private void updateTextColors() {
5901         boolean inval = false;
5902         final int[] drawableState = getDrawableState();
5903         int color = mTextColor.getColorForState(drawableState, 0);
5904         if (color != mCurTextColor) {
5905             mCurTextColor = color;
5906             inval = true;
5907         }
5908         if (mLinkTextColor != null) {
5909             color = mLinkTextColor.getColorForState(drawableState, 0);
5910             if (color != mTextPaint.linkColor) {
5911                 mTextPaint.linkColor = color;
5912                 inval = true;
5913             }
5914         }
5915         if (mHintTextColor != null) {
5916             color = mHintTextColor.getColorForState(drawableState, 0);
5917             if (color != mCurHintTextColor) {
5918                 mCurHintTextColor = color;
5919                 if (mText.length() == 0) {
5920                     inval = true;
5921                 }
5922             }
5923         }
5924         if (inval) {
5925             // Text needs to be redrawn with the new color
5926             if (mEditor != null) mEditor.invalidateTextDisplayList();
5927             invalidate();
5928         }
5929     }
5930 
5931     @Override
drawableStateChanged()5932     protected void drawableStateChanged() {
5933         super.drawableStateChanged();
5934 
5935         if (mTextColor != null && mTextColor.isStateful()
5936                 || (mHintTextColor != null && mHintTextColor.isStateful())
5937                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
5938             updateTextColors();
5939         }
5940 
5941         if (mDrawables != null) {
5942             final int[] state = getDrawableState();
5943             for (Drawable dr : mDrawables.mShowing) {
5944                 if (dr != null && dr.isStateful() && dr.setState(state)) {
5945                     invalidateDrawable(dr);
5946                 }
5947             }
5948         }
5949     }
5950 
5951     @Override
drawableHotspotChanged(float x, float y)5952     public void drawableHotspotChanged(float x, float y) {
5953         super.drawableHotspotChanged(x, y);
5954 
5955         if (mDrawables != null) {
5956             for (Drawable dr : mDrawables.mShowing) {
5957                 if (dr != null) {
5958                     dr.setHotspot(x, y);
5959                 }
5960             }
5961         }
5962     }
5963 
5964     @Override
onSaveInstanceState()5965     public Parcelable onSaveInstanceState() {
5966         Parcelable superState = super.onSaveInstanceState();
5967 
5968         // Save state if we are forced to
5969         final boolean freezesText = getFreezesText();
5970         boolean hasSelection = false;
5971         int start = -1;
5972         int end = -1;
5973 
5974         if (mText != null) {
5975             start = getSelectionStart();
5976             end = getSelectionEnd();
5977             if (start >= 0 || end >= 0) {
5978                 // Or save state if there is a selection
5979                 hasSelection = true;
5980             }
5981         }
5982 
5983         if (freezesText || hasSelection) {
5984             SavedState ss = new SavedState(superState);
5985 
5986             if (freezesText) {
5987                 if (mText instanceof Spanned) {
5988                     final Spannable sp = new SpannableStringBuilder(mText);
5989 
5990                     if (mEditor != null) {
5991                         removeMisspelledSpans(sp);
5992                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
5993                     }
5994 
5995                     ss.text = sp;
5996                 } else {
5997                     ss.text = mText.toString();
5998                 }
5999             }
6000 
6001             if (hasSelection) {
6002                 // XXX Should also save the current scroll position!
6003                 ss.selStart = start;
6004                 ss.selEnd = end;
6005             }
6006 
6007             if (isFocused() && start >= 0 && end >= 0) {
6008                 ss.frozenWithFocus = true;
6009             }
6010 
6011             ss.error = getError();
6012 
6013             if (mEditor != null) {
6014                 ss.editorState = mEditor.saveInstanceState();
6015             }
6016             return ss;
6017         }
6018 
6019         return superState;
6020     }
6021 
removeMisspelledSpans(Spannable spannable)6022     void removeMisspelledSpans(Spannable spannable) {
6023         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
6024                 SuggestionSpan.class);
6025         for (int i = 0; i < suggestionSpans.length; i++) {
6026             int flags = suggestionSpans[i].getFlags();
6027             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
6028                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
6029                 spannable.removeSpan(suggestionSpans[i]);
6030             }
6031         }
6032     }
6033 
6034     @Override
onRestoreInstanceState(Parcelable state)6035     public void onRestoreInstanceState(Parcelable state) {
6036         if (!(state instanceof SavedState)) {
6037             super.onRestoreInstanceState(state);
6038             return;
6039         }
6040 
6041         SavedState ss = (SavedState) state;
6042         super.onRestoreInstanceState(ss.getSuperState());
6043 
6044         // XXX restore buffer type too, as well as lots of other stuff
6045         if (ss.text != null) {
6046             setText(ss.text);
6047         }
6048 
6049         if (ss.selStart >= 0 && ss.selEnd >= 0) {
6050             if (mSpannable != null) {
6051                 int len = mText.length();
6052 
6053                 if (ss.selStart > len || ss.selEnd > len) {
6054                     String restored = "";
6055 
6056                     if (ss.text != null) {
6057                         restored = "(restored) ";
6058                     }
6059 
6060                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
6061                             + " out of range for " + restored + "text " + mText);
6062                 } else {
6063                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
6064 
6065                     if (ss.frozenWithFocus) {
6066                         createEditorIfNeeded();
6067                         mEditor.mFrozenWithFocus = true;
6068                     }
6069                 }
6070             }
6071         }
6072 
6073         if (ss.error != null) {
6074             final CharSequence error = ss.error;
6075             // Display the error later, after the first layout pass
6076             post(new Runnable() {
6077                 public void run() {
6078                     if (mEditor == null || !mEditor.mErrorWasChanged) {
6079                         setError(error);
6080                     }
6081                 }
6082             });
6083         }
6084 
6085         if (ss.editorState != null) {
6086             createEditorIfNeeded();
6087             mEditor.restoreInstanceState(ss.editorState);
6088         }
6089     }
6090 
6091     /**
6092      * Control whether this text view saves its entire text contents when
6093      * freezing to an icicle, in addition to dynamic state such as cursor
6094      * position.  By default this is false, not saving the text.  Set to true
6095      * if the text in the text view is not being saved somewhere else in
6096      * persistent storage (such as in a content provider) so that if the
6097      * view is later thawed the user will not lose their data. For
6098      * {@link android.widget.EditText} it is always enabled, regardless of
6099      * the value of the attribute.
6100      *
6101      * @param freezesText Controls whether a frozen icicle should include the
6102      * entire text data: true to include it, false to not.
6103      *
6104      * @attr ref android.R.styleable#TextView_freezesText
6105      */
6106     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)6107     public void setFreezesText(boolean freezesText) {
6108         mFreezesText = freezesText;
6109     }
6110 
6111     /**
6112      * Return whether this text view is including its entire text contents
6113      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
6114      *
6115      * @return Returns true if text is included, false if it isn't.
6116      *
6117      * @see #setFreezesText
6118      */
6119     @InspectableProperty
getFreezesText()6120     public boolean getFreezesText() {
6121         return mFreezesText;
6122     }
6123 
6124     ///////////////////////////////////////////////////////////////////////////
6125 
6126     /**
6127      * Sets the Factory used to create new {@link Editable Editables}.
6128      *
6129      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
6130      *
6131      * @see android.text.Editable.Factory
6132      * @see android.widget.TextView.BufferType#EDITABLE
6133      */
setEditableFactory(Editable.Factory factory)6134     public final void setEditableFactory(Editable.Factory factory) {
6135         mEditableFactory = factory;
6136         setText(mText);
6137     }
6138 
6139     /**
6140      * Sets the Factory used to create new {@link Spannable Spannables}.
6141      *
6142      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
6143      *
6144      * @see android.text.Spannable.Factory
6145      * @see android.widget.TextView.BufferType#SPANNABLE
6146      */
setSpannableFactory(Spannable.Factory factory)6147     public final void setSpannableFactory(Spannable.Factory factory) {
6148         mSpannableFactory = factory;
6149         setText(mText);
6150     }
6151 
6152     /**
6153      * Sets the text to be displayed. TextView <em>does not</em> accept
6154      * HTML-like formatting, which you can do with text strings in XML resource files.
6155      * To style your strings, attach android.text.style.* objects to a
6156      * {@link android.text.SpannableString}, or see the
6157      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
6158      * Available Resource Types</a> documentation for an example of setting
6159      * formatted text in the XML resource file.
6160      * <p/>
6161      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6162      * intermediate {@link Spannable Spannables}. Likewise it will use
6163      * {@link android.text.Editable.Factory} to create final or intermediate
6164      * {@link Editable Editables}.
6165      *
6166      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
6167      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
6168      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
6169      *
6170      * @param text text to be displayed
6171      *
6172      * @attr ref android.R.styleable#TextView_text
6173      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
6174      *                                  parameters used to create the PrecomputedText mismatches
6175      *                                  with this TextView.
6176      */
6177     @android.view.RemotableViewMethod
setText(CharSequence text)6178     public final void setText(CharSequence text) {
6179         setText(text, mBufferType);
6180     }
6181 
6182     /**
6183      * Sets the text to be displayed but retains the cursor position. Same as
6184      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
6185      * new text.
6186      * <p/>
6187      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6188      * intermediate {@link Spannable Spannables}. Likewise it will use
6189      * {@link android.text.Editable.Factory} to create final or intermediate
6190      * {@link Editable Editables}.
6191      *
6192      * @param text text to be displayed
6193      *
6194      * @see #setText(CharSequence)
6195      */
6196     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)6197     public final void setTextKeepState(CharSequence text) {
6198         setTextKeepState(text, mBufferType);
6199     }
6200 
6201     /**
6202      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
6203      * <p/>
6204      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6205      * intermediate {@link Spannable Spannables}. Likewise it will use
6206      * {@link android.text.Editable.Factory} to create final or intermediate
6207      * {@link Editable Editables}.
6208      *
6209      * Subclasses overriding this method should ensure that the following post condition holds,
6210      * in order to guarantee the safety of the view's measurement and layout operations:
6211      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
6212      * will be different from {@code null}.
6213      *
6214      * @param text text to be displayed
6215      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6216      *              stored as a static text, styleable/spannable text, or editable text
6217      *
6218      * @see #setText(CharSequence)
6219      * @see android.widget.TextView.BufferType
6220      * @see #setSpannableFactory(Spannable.Factory)
6221      * @see #setEditableFactory(Editable.Factory)
6222      *
6223      * @attr ref android.R.styleable#TextView_text
6224      * @attr ref android.R.styleable#TextView_bufferType
6225      */
setText(CharSequence text, BufferType type)6226     public void setText(CharSequence text, BufferType type) {
6227         setText(text, type, true, 0);
6228 
6229         if (mCharWrapper != null) {
6230             mCharWrapper.mChars = null;
6231         }
6232     }
6233 
6234     @UnsupportedAppUsage
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6235     private void setText(CharSequence text, BufferType type,
6236                          boolean notifyBefore, int oldlen) {
6237         mTextSetFromXmlOrResourceId = false;
6238         if (text == null) {
6239             text = "";
6240         }
6241 
6242         // If suggestions are not enabled, remove the suggestion spans from the text
6243         if (!isSuggestionsEnabled()) {
6244             text = removeSuggestionSpans(text);
6245         }
6246 
6247         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
6248 
6249         if (text instanceof Spanned
6250                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
6251             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
6252                 setHorizontalFadingEdgeEnabled(true);
6253                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
6254             } else {
6255                 setHorizontalFadingEdgeEnabled(false);
6256                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
6257             }
6258             setEllipsize(TextUtils.TruncateAt.MARQUEE);
6259         }
6260 
6261         int n = mFilters.length;
6262         for (int i = 0; i < n; i++) {
6263             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
6264             if (out != null) {
6265                 text = out;
6266             }
6267         }
6268 
6269         if (notifyBefore) {
6270             if (mText != null) {
6271                 oldlen = mText.length();
6272                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
6273             } else {
6274                 sendBeforeTextChanged("", 0, 0, text.length());
6275             }
6276         }
6277 
6278         boolean needEditableForNotification = false;
6279 
6280         if (mListeners != null && mListeners.size() != 0) {
6281             needEditableForNotification = true;
6282         }
6283 
6284         PrecomputedText precomputed =
6285                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
6286         if (type == BufferType.EDITABLE || getKeyListener() != null
6287                 || needEditableForNotification) {
6288             createEditorIfNeeded();
6289             mEditor.forgetUndoRedo();
6290             mEditor.scheduleRestartInputForSetText();
6291             Editable t = mEditableFactory.newEditable(text);
6292             text = t;
6293             setFilters(t, mFilters);
6294         } else if (precomputed != null) {
6295             if (mTextDir == null) {
6296                 mTextDir = getTextDirectionHeuristic();
6297             }
6298             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
6299                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
6300                             mHyphenationFrequency);
6301             switch (checkResult) {
6302                 case PrecomputedText.Params.UNUSABLE:
6303                     throw new IllegalArgumentException(
6304                         "PrecomputedText's Parameters don't match the parameters of this TextView."
6305                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
6306                         + "to override the settings of this TextView: "
6307                         + "PrecomputedText: " + precomputed.getParams()
6308                         + "TextView: " + getTextMetricsParams());
6309                 case PrecomputedText.Params.NEED_RECOMPUTE:
6310                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
6311                     break;
6312                 case PrecomputedText.Params.USABLE:
6313                     // pass through
6314             }
6315         } else if (type == BufferType.SPANNABLE || mMovement != null) {
6316             text = mSpannableFactory.newSpannable(text);
6317         } else if (!(text instanceof CharWrapper)) {
6318             text = TextUtils.stringOrSpannedString(text);
6319         }
6320 
6321         if (mAutoLinkMask != 0) {
6322             Spannable s2;
6323 
6324             if (type == BufferType.EDITABLE || text instanceof Spannable) {
6325                 s2 = (Spannable) text;
6326             } else {
6327                 s2 = mSpannableFactory.newSpannable(text);
6328             }
6329 
6330             if (Linkify.addLinks(s2, mAutoLinkMask)) {
6331                 text = s2;
6332                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
6333 
6334                 /*
6335                  * We must go ahead and set the text before changing the
6336                  * movement method, because setMovementMethod() may call
6337                  * setText() again to try to upgrade the buffer type.
6338                  */
6339                 setTextInternal(text);
6340 
6341                 // Do not change the movement method for text that support text selection as it
6342                 // would prevent an arbitrary cursor displacement.
6343                 if (mLinksClickable && !textCanBeSelected()) {
6344                     setMovementMethod(LinkMovementMethod.getInstance());
6345                 }
6346             }
6347         }
6348 
6349         mBufferType = type;
6350         setTextInternal(text);
6351 
6352         if (mTransformation == null) {
6353             mTransformed = text;
6354         } else {
6355             mTransformed = mTransformation.getTransformation(text, this);
6356         }
6357         if (mTransformed == null) {
6358             // Should not happen if the transformation method follows the non-null postcondition.
6359             mTransformed = "";
6360         }
6361 
6362         final int textLength = text.length();
6363 
6364         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
6365             Spannable sp = (Spannable) text;
6366 
6367             // Remove any ChangeWatchers that might have come from other TextViews.
6368             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
6369             final int count = watchers.length;
6370             for (int i = 0; i < count; i++) {
6371                 sp.removeSpan(watchers[i]);
6372             }
6373 
6374             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
6375 
6376             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
6377                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
6378 
6379             if (mEditor != null) mEditor.addSpanWatchers(sp);
6380 
6381             if (mTransformation != null) {
6382                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
6383             }
6384 
6385             if (mMovement != null) {
6386                 mMovement.initialize(this, (Spannable) text);
6387 
6388                 /*
6389                  * Initializing the movement method will have set the
6390                  * selection, so reset mSelectionMoved to keep that from
6391                  * interfering with the normal on-focus selection-setting.
6392                  */
6393                 if (mEditor != null) mEditor.mSelectionMoved = false;
6394             }
6395         }
6396 
6397         if (mLayout != null) {
6398             checkForRelayout();
6399         }
6400 
6401         sendOnTextChanged(text, 0, oldlen, textLength);
6402         onTextChanged(text, 0, oldlen, textLength);
6403 
6404         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
6405 
6406         if (needEditableForNotification) {
6407             sendAfterTextChanged((Editable) text);
6408         } else {
6409             notifyListeningManagersAfterTextChanged();
6410         }
6411 
6412         if (mEditor != null) {
6413             // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
6414             mEditor.prepareCursorControllers();
6415 
6416             mEditor.maybeFireScheduledRestartInputForSetText();
6417         }
6418     }
6419 
6420     /**
6421      * Sets the TextView to display the specified slice of the specified
6422      * char array. You must promise that you will not change the contents
6423      * of the array except for right before another call to setText(),
6424      * since the TextView has no way to know that the text
6425      * has changed and that it needs to invalidate and re-layout.
6426      *
6427      * @param text char array to be displayed
6428      * @param start start index in the char array
6429      * @param len length of char count after {@code start}
6430      */
setText(char[] text, int start, int len)6431     public final void setText(char[] text, int start, int len) {
6432         int oldlen = 0;
6433 
6434         if (start < 0 || len < 0 || start + len > text.length) {
6435             throw new IndexOutOfBoundsException(start + ", " + len);
6436         }
6437 
6438         /*
6439          * We must do the before-notification here ourselves because if
6440          * the old text is a CharWrapper we destroy it before calling
6441          * into the normal path.
6442          */
6443         if (mText != null) {
6444             oldlen = mText.length();
6445             sendBeforeTextChanged(mText, 0, oldlen, len);
6446         } else {
6447             sendBeforeTextChanged("", 0, 0, len);
6448         }
6449 
6450         if (mCharWrapper == null) {
6451             mCharWrapper = new CharWrapper(text, start, len);
6452         } else {
6453             mCharWrapper.set(text, start, len);
6454         }
6455 
6456         setText(mCharWrapper, mBufferType, false, oldlen);
6457     }
6458 
6459     /**
6460      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
6461      * the cursor position. Same as
6462      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
6463      * position (if any) is retained in the new text.
6464      * <p/>
6465      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6466      * intermediate {@link Spannable Spannables}. Likewise it will use
6467      * {@link android.text.Editable.Factory} to create final or intermediate
6468      * {@link Editable Editables}.
6469      *
6470      * @param text text to be displayed
6471      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6472      *              stored as a static text, styleable/spannable text, or editable text
6473      *
6474      * @see #setText(CharSequence, android.widget.TextView.BufferType)
6475      */
setTextKeepState(CharSequence text, BufferType type)6476     public final void setTextKeepState(CharSequence text, BufferType type) {
6477         int start = getSelectionStart();
6478         int end = getSelectionEnd();
6479         int len = text.length();
6480 
6481         setText(text, type);
6482 
6483         if (start >= 0 || end >= 0) {
6484             if (mSpannable != null) {
6485                 Selection.setSelection(mSpannable,
6486                                        Math.max(0, Math.min(start, len)),
6487                                        Math.max(0, Math.min(end, len)));
6488             }
6489         }
6490     }
6491 
6492     /**
6493      * Sets the text to be displayed using a string resource identifier.
6494      *
6495      * @param resid the resource identifier of the string resource to be displayed
6496      *
6497      * @see #setText(CharSequence)
6498      *
6499      * @attr ref android.R.styleable#TextView_text
6500      */
6501     @android.view.RemotableViewMethod
setText(@tringRes int resid)6502     public final void setText(@StringRes int resid) {
6503         setText(getContext().getResources().getText(resid));
6504         mTextSetFromXmlOrResourceId = true;
6505         mTextId = resid;
6506     }
6507 
6508     /**
6509      * Sets the text to be displayed using a string resource identifier and the
6510      * {@link android.widget.TextView.BufferType}.
6511      * <p/>
6512      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6513      * intermediate {@link Spannable Spannables}. Likewise it will use
6514      * {@link android.text.Editable.Factory} to create final or intermediate
6515      * {@link Editable Editables}.
6516      *
6517      * @param resid the resource identifier of the string resource to be displayed
6518      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6519      *              stored as a static text, styleable/spannable text, or editable text
6520      *
6521      * @see #setText(int)
6522      * @see #setText(CharSequence)
6523      * @see android.widget.TextView.BufferType
6524      * @see #setSpannableFactory(Spannable.Factory)
6525      * @see #setEditableFactory(Editable.Factory)
6526      *
6527      * @attr ref android.R.styleable#TextView_text
6528      * @attr ref android.R.styleable#TextView_bufferType
6529      */
setText(@tringRes int resid, BufferType type)6530     public final void setText(@StringRes int resid, BufferType type) {
6531         setText(getContext().getResources().getText(resid), type);
6532         mTextSetFromXmlOrResourceId = true;
6533         mTextId = resid;
6534     }
6535 
6536     /**
6537      * Sets the text to be displayed when the text of the TextView is empty.
6538      * Null means to use the normal empty text. The hint does not currently
6539      * participate in determining the size of the view.
6540      *
6541      * @attr ref android.R.styleable#TextView_hint
6542      */
6543     @android.view.RemotableViewMethod
setHint(CharSequence hint)6544     public final void setHint(CharSequence hint) {
6545         setHintInternal(hint);
6546 
6547         if (mEditor != null && isInputMethodTarget()) {
6548             mEditor.reportExtractedText();
6549         }
6550     }
6551 
setHintInternal(CharSequence hint)6552     private void setHintInternal(CharSequence hint) {
6553         mHint = TextUtils.stringOrSpannedString(hint);
6554 
6555         if (mLayout != null) {
6556             checkForRelayout();
6557         }
6558 
6559         if (mText.length() == 0) {
6560             invalidate();
6561         }
6562 
6563         // Invalidate display list if hint is currently used
6564         if (mEditor != null && mText.length() == 0 && mHint != null) {
6565             mEditor.invalidateTextDisplayList();
6566         }
6567     }
6568 
6569     /**
6570      * Sets the text to be displayed when the text of the TextView is empty,
6571      * from a resource.
6572      *
6573      * @attr ref android.R.styleable#TextView_hint
6574      */
6575     @android.view.RemotableViewMethod
setHint(@tringRes int resid)6576     public final void setHint(@StringRes int resid) {
6577         mHintId = resid;
6578         setHint(getContext().getResources().getText(resid));
6579     }
6580 
6581     /**
6582      * Returns the hint that is displayed when the text of the TextView
6583      * is empty.
6584      *
6585      * @attr ref android.R.styleable#TextView_hint
6586      */
6587     @InspectableProperty
6588     @ViewDebug.CapturedViewProperty
getHint()6589     public CharSequence getHint() {
6590         return mHint;
6591     }
6592 
6593     /**
6594      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
6595      * line characters instead of letting it wrap onto multiple lines.
6596      *
6597      * @attr ref android.R.styleable#TextView_singleLine
6598      */
6599     @InspectableProperty
isSingleLine()6600     public boolean isSingleLine() {
6601         return mSingleLine;
6602     }
6603 
isMultilineInputType(int type)6604     private static boolean isMultilineInputType(int type) {
6605         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
6606                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
6607     }
6608 
6609     /**
6610      * Removes the suggestion spans.
6611      */
removeSuggestionSpans(CharSequence text)6612     CharSequence removeSuggestionSpans(CharSequence text) {
6613         if (text instanceof Spanned) {
6614             Spannable spannable;
6615             if (text instanceof Spannable) {
6616                 spannable = (Spannable) text;
6617             } else {
6618                 spannable = mSpannableFactory.newSpannable(text);
6619             }
6620 
6621             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
6622             if (spans.length == 0) {
6623                 return text;
6624             } else {
6625                 text = spannable;
6626             }
6627 
6628             for (int i = 0; i < spans.length; i++) {
6629                 spannable.removeSpan(spans[i]);
6630             }
6631         }
6632         return text;
6633     }
6634 
6635     /**
6636      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
6637      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
6638      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
6639      * then a soft keyboard will not be displayed for this text view.
6640      *
6641      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
6642      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
6643      * type.
6644      *
6645      * @see #getInputType()
6646      * @see #setRawInputType(int)
6647      * @see android.text.InputType
6648      * @attr ref android.R.styleable#TextView_inputType
6649      */
setInputType(int type)6650     public void setInputType(int type) {
6651         final boolean wasPassword = isPasswordInputType(getInputType());
6652         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
6653         setInputType(type, false);
6654         final boolean isPassword = isPasswordInputType(type);
6655         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
6656         boolean forceUpdate = false;
6657         if (isPassword) {
6658             setTransformationMethod(PasswordTransformationMethod.getInstance());
6659             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6660                     Typeface.NORMAL, -1 /* weight, not specifeid */);
6661         } else if (isVisiblePassword) {
6662             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6663                 forceUpdate = true;
6664             }
6665             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6666                     Typeface.NORMAL, -1 /* weight, not specified */);
6667         } else if (wasPassword || wasVisiblePassword) {
6668             // not in password mode, clean up typeface and transformation
6669             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
6670                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
6671                     -1 /* weight, not specified */);
6672             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6673                 forceUpdate = true;
6674             }
6675         }
6676 
6677         boolean singleLine = !isMultilineInputType(type);
6678 
6679         // We need to update the single line mode if it has changed or we
6680         // were previously in password mode.
6681         if (mSingleLine != singleLine || forceUpdate) {
6682             // Change single line mode, but only change the transformation if
6683             // we are not in password mode.
6684             applySingleLine(singleLine, !isPassword, true, true);
6685         }
6686 
6687         if (!isSuggestionsEnabled()) {
6688             setTextInternal(removeSuggestionSpans(mText));
6689         }
6690 
6691         InputMethodManager imm = getInputMethodManager();
6692         if (imm != null) imm.restartInput(this);
6693     }
6694 
6695     /**
6696      * It would be better to rely on the input type for everything. A password inputType should have
6697      * a password transformation. We should hence use isPasswordInputType instead of this method.
6698      *
6699      * We should:
6700      * - Call setInputType in setKeyListener instead of changing the input type directly (which
6701      * would install the correct transformation).
6702      * - Refuse the installation of a non-password transformation in setTransformation if the input
6703      * type is password.
6704      *
6705      * However, this is like this for legacy reasons and we cannot break existing apps. This method
6706      * is useful since it matches what the user can see (obfuscated text or not).
6707      *
6708      * @return true if the current transformation method is of the password type.
6709      */
hasPasswordTransformationMethod()6710     boolean hasPasswordTransformationMethod() {
6711         return mTransformation instanceof PasswordTransformationMethod;
6712     }
6713 
6714     /**
6715      * Returns true if the current inputType is any type of password.
6716      *
6717      * @hide
6718      */
isAnyPasswordInputType()6719     public boolean isAnyPasswordInputType() {
6720         final int inputType = getInputType();
6721         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
6722     }
6723 
isPasswordInputType(int inputType)6724     static boolean isPasswordInputType(int inputType) {
6725         final int variation =
6726                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6727         return variation
6728                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
6729                 || variation
6730                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
6731                 || variation
6732                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
6733     }
6734 
isVisiblePasswordInputType(int inputType)6735     private static boolean isVisiblePasswordInputType(int inputType) {
6736         final int variation =
6737                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6738         return variation
6739                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
6740     }
6741 
6742     /**
6743      * Directly change the content type integer of the text view, without
6744      * modifying any other state.
6745      * @see #setInputType(int)
6746      * @see android.text.InputType
6747      * @attr ref android.R.styleable#TextView_inputType
6748      */
setRawInputType(int type)6749     public void setRawInputType(int type) {
6750         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
6751         createEditorIfNeeded();
6752         mEditor.mInputType = type;
6753     }
6754 
6755     /**
6756      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
6757      *         a {@code Locale} object that can be used to customize key various listeners.
6758      * @see DateKeyListener#getInstance(Locale)
6759      * @see DateTimeKeyListener#getInstance(Locale)
6760      * @see DigitsKeyListener#getInstance(Locale)
6761      * @see TimeKeyListener#getInstance(Locale)
6762      */
6763     @Nullable
getCustomLocaleForKeyListenerOrNull()6764     private Locale getCustomLocaleForKeyListenerOrNull() {
6765         if (!mUseInternationalizedInput) {
6766             // If the application does not target O, stick to the previous behavior.
6767             return null;
6768         }
6769         final LocaleList locales = getImeHintLocales();
6770         if (locales == null) {
6771             // If the application does not explicitly specify IME hint locale, also stick to the
6772             // previous behavior.
6773             return null;
6774         }
6775         return locales.get(0);
6776     }
6777 
6778     @UnsupportedAppUsage
setInputType(int type, boolean direct)6779     private void setInputType(int type, boolean direct) {
6780         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
6781         KeyListener input;
6782         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
6783             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
6784             TextKeyListener.Capitalize cap;
6785             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
6786                 cap = TextKeyListener.Capitalize.CHARACTERS;
6787             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
6788                 cap = TextKeyListener.Capitalize.WORDS;
6789             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
6790                 cap = TextKeyListener.Capitalize.SENTENCES;
6791             } else {
6792                 cap = TextKeyListener.Capitalize.NONE;
6793             }
6794             input = TextKeyListener.getInstance(autotext, cap);
6795         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
6796             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6797             input = DigitsKeyListener.getInstance(
6798                     locale,
6799                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
6800                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
6801             if (locale != null) {
6802                 // Override type, if necessary for i18n.
6803                 int newType = input.getInputType();
6804                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
6805                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
6806                     // The class is different from the original class. So we need to override
6807                     // 'type'. But we want to keep the password flag if it's there.
6808                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
6809                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
6810                     }
6811                     type = newType;
6812                 }
6813             }
6814         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
6815             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6816             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
6817                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
6818                     input = DateKeyListener.getInstance(locale);
6819                     break;
6820                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
6821                     input = TimeKeyListener.getInstance(locale);
6822                     break;
6823                 default:
6824                     input = DateTimeKeyListener.getInstance(locale);
6825                     break;
6826             }
6827             if (mUseInternationalizedInput) {
6828                 type = input.getInputType(); // Override type, if necessary for i18n.
6829             }
6830         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
6831             input = DialerKeyListener.getInstance();
6832         } else {
6833             input = TextKeyListener.getInstance();
6834         }
6835         setRawInputType(type);
6836         mListenerChanged = false;
6837         if (direct) {
6838             createEditorIfNeeded();
6839             mEditor.mKeyListener = input;
6840         } else {
6841             setKeyListenerOnly(input);
6842         }
6843     }
6844 
6845     /**
6846      * Get the type of the editable content.
6847      *
6848      * @see #setInputType(int)
6849      * @see android.text.InputType
6850      */
6851     @InspectableProperty(flagMapping = {
6852             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
6853             @FlagEntry(
6854                     name = "text",
6855                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6856                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
6857             @FlagEntry(
6858                     name = "textUri",
6859                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6860                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
6861             @FlagEntry(
6862                     name = "textEmailAddress",
6863                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6864                     target = InputType.TYPE_CLASS_TEXT
6865                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
6866             @FlagEntry(
6867                     name = "textEmailSubject",
6868                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6869                     target = InputType.TYPE_CLASS_TEXT
6870                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
6871             @FlagEntry(
6872                     name = "textShortMessage",
6873                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6874                     target = InputType.TYPE_CLASS_TEXT
6875                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
6876             @FlagEntry(
6877                     name = "textLongMessage",
6878                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6879                     target = InputType.TYPE_CLASS_TEXT
6880                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
6881             @FlagEntry(
6882                     name = "textPersonName",
6883                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6884                     target = InputType.TYPE_CLASS_TEXT
6885                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
6886             @FlagEntry(
6887                     name = "textPostalAddress",
6888                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6889                     target = InputType.TYPE_CLASS_TEXT
6890                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
6891             @FlagEntry(
6892                     name = "textPassword",
6893                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6894                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
6895             @FlagEntry(
6896                     name = "textVisiblePassword",
6897                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6898                     target = InputType.TYPE_CLASS_TEXT
6899                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
6900             @FlagEntry(
6901                     name = "textWebEditText",
6902                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6903                     target = InputType.TYPE_CLASS_TEXT
6904                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
6905             @FlagEntry(
6906                     name = "textFilter",
6907                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6908                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
6909             @FlagEntry(
6910                     name = "textPhonetic",
6911                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6912                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
6913             @FlagEntry(
6914                     name = "textWebEmailAddress",
6915                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6916                     target = InputType.TYPE_CLASS_TEXT
6917                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
6918             @FlagEntry(
6919                     name = "textWebPassword",
6920                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6921                     target = InputType.TYPE_CLASS_TEXT
6922                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
6923             @FlagEntry(
6924                     name = "number",
6925                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6926                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
6927             @FlagEntry(
6928                     name = "numberPassword",
6929                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6930                     target = InputType.TYPE_CLASS_NUMBER
6931                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
6932             @FlagEntry(
6933                     name = "phone",
6934                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6935                     target = InputType.TYPE_CLASS_PHONE),
6936             @FlagEntry(
6937                     name = "datetime",
6938                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6939                     target = InputType.TYPE_CLASS_DATETIME
6940                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
6941             @FlagEntry(
6942                     name = "date",
6943                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6944                     target = InputType.TYPE_CLASS_DATETIME
6945                             | InputType.TYPE_DATETIME_VARIATION_DATE),
6946             @FlagEntry(
6947                     name = "time",
6948                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6949                     target = InputType.TYPE_CLASS_DATETIME
6950                             | InputType.TYPE_DATETIME_VARIATION_TIME),
6951             @FlagEntry(
6952                     name = "textCapCharacters",
6953                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6954                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
6955             @FlagEntry(
6956                     name = "textCapWords",
6957                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6958                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
6959             @FlagEntry(
6960                     name = "textCapSentences",
6961                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6962                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
6963             @FlagEntry(
6964                     name = "textAutoCorrect",
6965                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6966                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
6967             @FlagEntry(
6968                     name = "textAutoComplete",
6969                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6970                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
6971             @FlagEntry(
6972                     name = "textMultiLine",
6973                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6974                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
6975             @FlagEntry(
6976                     name = "textImeMultiLine",
6977                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6978                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
6979             @FlagEntry(
6980                     name = "textNoSuggestions",
6981                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6982                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
6983             @FlagEntry(
6984                     name = "numberSigned",
6985                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6986                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
6987             @FlagEntry(
6988                     name = "numberDecimal",
6989                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6990                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
6991     })
getInputType()6992     public int getInputType() {
6993         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
6994     }
6995 
6996     /**
6997      * Change the editor type integer associated with the text view, which
6998      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
6999      * when it has focus.
7000      * @see #getImeOptions
7001      * @see android.view.inputmethod.EditorInfo
7002      * @attr ref android.R.styleable#TextView_imeOptions
7003      */
setImeOptions(int imeOptions)7004     public void setImeOptions(int imeOptions) {
7005         createEditorIfNeeded();
7006         mEditor.createInputContentTypeIfNeeded();
7007         mEditor.mInputContentType.imeOptions = imeOptions;
7008     }
7009 
7010     /**
7011      * Get the type of the Input Method Editor (IME).
7012      * @return the type of the IME
7013      * @see #setImeOptions(int)
7014      * @see EditorInfo
7015      */
7016     @InspectableProperty(flagMapping = {
7017             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
7018             @FlagEntry(
7019                     name = "actionUnspecified",
7020                     mask = EditorInfo.IME_MASK_ACTION,
7021                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
7022             @FlagEntry(
7023                     name = "actionNone",
7024                     mask = EditorInfo.IME_MASK_ACTION,
7025                     target = EditorInfo.IME_ACTION_NONE),
7026             @FlagEntry(
7027                     name = "actionGo",
7028                     mask = EditorInfo.IME_MASK_ACTION,
7029                     target = EditorInfo.IME_ACTION_GO),
7030             @FlagEntry(
7031                     name = "actionSearch",
7032                     mask = EditorInfo.IME_MASK_ACTION,
7033                     target = EditorInfo.IME_ACTION_SEARCH),
7034             @FlagEntry(
7035                     name = "actionSend",
7036                     mask = EditorInfo.IME_MASK_ACTION,
7037                     target = EditorInfo.IME_ACTION_SEND),
7038             @FlagEntry(
7039                     name = "actionNext",
7040                     mask = EditorInfo.IME_MASK_ACTION,
7041                     target = EditorInfo.IME_ACTION_NEXT),
7042             @FlagEntry(
7043                     name = "actionDone",
7044                     mask = EditorInfo.IME_MASK_ACTION,
7045                     target = EditorInfo.IME_ACTION_DONE),
7046             @FlagEntry(
7047                     name = "actionPrevious",
7048                     mask = EditorInfo.IME_MASK_ACTION,
7049                     target = EditorInfo.IME_ACTION_PREVIOUS),
7050             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
7051             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
7052             @FlagEntry(
7053                     name = "flagNavigatePrevious",
7054                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
7055             @FlagEntry(
7056                     name = "flagNoAccessoryAction",
7057                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
7058             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
7059             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
7060             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
7061             @FlagEntry(
7062                     name = "flagNoPersonalizedLearning",
7063                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
7064     })
getImeOptions()7065     public int getImeOptions() {
7066         return mEditor != null && mEditor.mInputContentType != null
7067                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
7068     }
7069 
7070     /**
7071      * Change the custom IME action associated with the text view, which
7072      * will be reported to an IME with {@link EditorInfo#actionLabel}
7073      * and {@link EditorInfo#actionId} when it has focus.
7074      * @see #getImeActionLabel
7075      * @see #getImeActionId
7076      * @see android.view.inputmethod.EditorInfo
7077      * @attr ref android.R.styleable#TextView_imeActionLabel
7078      * @attr ref android.R.styleable#TextView_imeActionId
7079      */
setImeActionLabel(CharSequence label, int actionId)7080     public void setImeActionLabel(CharSequence label, int actionId) {
7081         createEditorIfNeeded();
7082         mEditor.createInputContentTypeIfNeeded();
7083         mEditor.mInputContentType.imeActionLabel = label;
7084         mEditor.mInputContentType.imeActionId = actionId;
7085     }
7086 
7087     /**
7088      * Get the IME action label previous set with {@link #setImeActionLabel}.
7089      *
7090      * @see #setImeActionLabel
7091      * @see android.view.inputmethod.EditorInfo
7092      */
7093     @InspectableProperty
getImeActionLabel()7094     public CharSequence getImeActionLabel() {
7095         return mEditor != null && mEditor.mInputContentType != null
7096                 ? mEditor.mInputContentType.imeActionLabel : null;
7097     }
7098 
7099     /**
7100      * Get the IME action ID previous set with {@link #setImeActionLabel}.
7101      *
7102      * @see #setImeActionLabel
7103      * @see android.view.inputmethod.EditorInfo
7104      */
7105     @InspectableProperty
getImeActionId()7106     public int getImeActionId() {
7107         return mEditor != null && mEditor.mInputContentType != null
7108                 ? mEditor.mInputContentType.imeActionId : 0;
7109     }
7110 
7111     /**
7112      * Set a special listener to be called when an action is performed
7113      * on the text view.  This will be called when the enter key is pressed,
7114      * or when an action supplied to the IME is selected by the user.  Setting
7115      * this means that the normal hard key event will not insert a newline
7116      * into the text view, even if it is multi-line; holding down the ALT
7117      * modifier will, however, allow the user to insert a newline character.
7118      */
setOnEditorActionListener(OnEditorActionListener l)7119     public void setOnEditorActionListener(OnEditorActionListener l) {
7120         createEditorIfNeeded();
7121         mEditor.createInputContentTypeIfNeeded();
7122         mEditor.mInputContentType.onEditorActionListener = l;
7123     }
7124 
7125     /**
7126      * Called when an attached input method calls
7127      * {@link InputConnection#performEditorAction(int)
7128      * InputConnection.performEditorAction()}
7129      * for this text view.  The default implementation will call your action
7130      * listener supplied to {@link #setOnEditorActionListener}, or perform
7131      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
7132      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
7133      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
7134      * EditorInfo.IME_ACTION_DONE}.
7135      *
7136      * <p>For backwards compatibility, if no IME options have been set and the
7137      * text view would not normally advance focus on enter, then
7138      * the NEXT and DONE actions received here will be turned into an enter
7139      * key down/up pair to go through the normal key handling.
7140      *
7141      * @param actionCode The code of the action being performed.
7142      *
7143      * @see #setOnEditorActionListener
7144      */
onEditorAction(int actionCode)7145     public void onEditorAction(int actionCode) {
7146         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
7147         if (ict != null) {
7148             if (ict.onEditorActionListener != null) {
7149                 if (ict.onEditorActionListener.onEditorAction(this,
7150                         actionCode, null)) {
7151                     return;
7152                 }
7153             }
7154 
7155             // This is the handling for some default action.
7156             // Note that for backwards compatibility we don't do this
7157             // default handling if explicit ime options have not been given,
7158             // instead turning this into the normal enter key codes that an
7159             // app may be expecting.
7160             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
7161                 View v = focusSearch(FOCUS_FORWARD);
7162                 if (v != null) {
7163                     if (!v.requestFocus(FOCUS_FORWARD)) {
7164                         throw new IllegalStateException("focus search returned a view "
7165                                 + "that wasn't able to take focus!");
7166                     }
7167                 }
7168                 return;
7169 
7170             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
7171                 View v = focusSearch(FOCUS_BACKWARD);
7172                 if (v != null) {
7173                     if (!v.requestFocus(FOCUS_BACKWARD)) {
7174                         throw new IllegalStateException("focus search returned a view "
7175                                 + "that wasn't able to take focus!");
7176                     }
7177                 }
7178                 return;
7179 
7180             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
7181                 InputMethodManager imm = getInputMethodManager();
7182                 if (imm != null && imm.isActive(this)) {
7183                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7184                 }
7185                 return;
7186             }
7187         }
7188 
7189         ViewRootImpl viewRootImpl = getViewRootImpl();
7190         if (viewRootImpl != null) {
7191             long eventTime = SystemClock.uptimeMillis();
7192             viewRootImpl.dispatchKeyFromIme(
7193                     new KeyEvent(eventTime, eventTime,
7194                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
7195                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7196                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7197                     | KeyEvent.FLAG_EDITOR_ACTION));
7198             viewRootImpl.dispatchKeyFromIme(
7199                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
7200                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
7201                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7202                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7203                     | KeyEvent.FLAG_EDITOR_ACTION));
7204         }
7205     }
7206 
7207     /**
7208      * Set the private content type of the text, which is the
7209      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
7210      * field that will be filled in when creating an input connection.
7211      *
7212      * @see #getPrivateImeOptions()
7213      * @see EditorInfo#privateImeOptions
7214      * @attr ref android.R.styleable#TextView_privateImeOptions
7215      */
setPrivateImeOptions(String type)7216     public void setPrivateImeOptions(String type) {
7217         createEditorIfNeeded();
7218         mEditor.createInputContentTypeIfNeeded();
7219         mEditor.mInputContentType.privateImeOptions = type;
7220     }
7221 
7222     /**
7223      * Get the private type of the content.
7224      *
7225      * @see #setPrivateImeOptions(String)
7226      * @see EditorInfo#privateImeOptions
7227      */
7228     @InspectableProperty
getPrivateImeOptions()7229     public String getPrivateImeOptions() {
7230         return mEditor != null && mEditor.mInputContentType != null
7231                 ? mEditor.mInputContentType.privateImeOptions : null;
7232     }
7233 
7234     /**
7235      * Set the extra input data of the text, which is the
7236      * {@link EditorInfo#extras TextBoxAttribute.extras}
7237      * Bundle that will be filled in when creating an input connection.  The
7238      * given integer is the resource identifier of an XML resource holding an
7239      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
7240      *
7241      * @see #getInputExtras(boolean)
7242      * @see EditorInfo#extras
7243      * @attr ref android.R.styleable#TextView_editorExtras
7244      */
setInputExtras(@mlRes int xmlResId)7245     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
7246         createEditorIfNeeded();
7247         XmlResourceParser parser = getResources().getXml(xmlResId);
7248         mEditor.createInputContentTypeIfNeeded();
7249         mEditor.mInputContentType.extras = new Bundle();
7250         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
7251     }
7252 
7253     /**
7254      * Retrieve the input extras currently associated with the text view, which
7255      * can be viewed as well as modified.
7256      *
7257      * @param create If true, the extras will be created if they don't already
7258      * exist.  Otherwise, null will be returned if none have been created.
7259      * @see #setInputExtras(int)
7260      * @see EditorInfo#extras
7261      * @attr ref android.R.styleable#TextView_editorExtras
7262      */
getInputExtras(boolean create)7263     public Bundle getInputExtras(boolean create) {
7264         if (mEditor == null && !create) return null;
7265         createEditorIfNeeded();
7266         if (mEditor.mInputContentType == null) {
7267             if (!create) return null;
7268             mEditor.createInputContentTypeIfNeeded();
7269         }
7270         if (mEditor.mInputContentType.extras == null) {
7271             if (!create) return null;
7272             mEditor.mInputContentType.extras = new Bundle();
7273         }
7274         return mEditor.mInputContentType.extras;
7275     }
7276 
7277     /**
7278      * Change "hint" locales associated with the text view, which will be reported to an IME with
7279      * {@link EditorInfo#hintLocales} when it has focus.
7280      *
7281      * Starting with Android O, this also causes internationalized listeners to be created (or
7282      * change locale) based on the first locale in the input locale list.
7283      *
7284      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
7285      * call {@link InputMethodManager#restartInput(View)}.</p>
7286      * @param hintLocales List of the languages that the user is supposed to switch to no matter
7287      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
7288      * @see #getImeHintLocales()
7289      * @see android.view.inputmethod.EditorInfo#hintLocales
7290      */
setImeHintLocales(@ullable LocaleList hintLocales)7291     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
7292         createEditorIfNeeded();
7293         mEditor.createInputContentTypeIfNeeded();
7294         mEditor.mInputContentType.imeHintLocales = hintLocales;
7295         if (mUseInternationalizedInput) {
7296             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
7297         }
7298     }
7299 
7300     /**
7301      * @return The current languages list "hint". {@code null} when no "hint" is available.
7302      * @see #setImeHintLocales(LocaleList)
7303      * @see android.view.inputmethod.EditorInfo#hintLocales
7304      */
7305     @Nullable
getImeHintLocales()7306     public LocaleList getImeHintLocales() {
7307         if (mEditor == null) {
7308             return null;
7309         }
7310         if (mEditor.mInputContentType == null) {
7311             return null;
7312         }
7313         return mEditor.mInputContentType.imeHintLocales;
7314     }
7315 
7316     /**
7317      * Returns the error message that was set to be displayed with
7318      * {@link #setError}, or <code>null</code> if no error was set
7319      * or if it the error was cleared by the widget after user input.
7320      */
getError()7321     public CharSequence getError() {
7322         return mEditor == null ? null : mEditor.mError;
7323     }
7324 
7325     /**
7326      * Sets the right-hand compound drawable of the TextView to the "error"
7327      * icon and sets an error message that will be displayed in a popup when
7328      * the TextView has focus.  The icon and error message will be reset to
7329      * null when any key events cause changes to the TextView's text.  If the
7330      * <code>error</code> is <code>null</code>, the error message and icon
7331      * will be cleared.
7332      */
7333     @android.view.RemotableViewMethod
setError(CharSequence error)7334     public void setError(CharSequence error) {
7335         if (error == null) {
7336             setError(null, null);
7337         } else {
7338             Drawable dr = getContext().getDrawable(
7339                     com.android.internal.R.drawable.indicator_input_error);
7340 
7341             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
7342             setError(error, dr);
7343         }
7344     }
7345 
7346     /**
7347      * Sets the right-hand compound drawable of the TextView to the specified
7348      * icon and sets an error message that will be displayed in a popup when
7349      * the TextView has focus.  The icon and error message will be reset to
7350      * null when any key events cause changes to the TextView's text.  The
7351      * drawable must already have had {@link Drawable#setBounds} set on it.
7352      * If the <code>error</code> is <code>null</code>, the error message will
7353      * be cleared (and you should provide a <code>null</code> icon as well).
7354      */
setError(CharSequence error, Drawable icon)7355     public void setError(CharSequence error, Drawable icon) {
7356         createEditorIfNeeded();
7357         mEditor.setError(error, icon);
7358         notifyViewAccessibilityStateChangedIfNeeded(
7359                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7360     }
7361 
7362     @Override
setFrame(int l, int t, int r, int b)7363     protected boolean setFrame(int l, int t, int r, int b) {
7364         boolean result = super.setFrame(l, t, r, b);
7365 
7366         if (mEditor != null) mEditor.setFrame();
7367 
7368         restartMarqueeIfNeeded();
7369 
7370         return result;
7371     }
7372 
restartMarqueeIfNeeded()7373     private void restartMarqueeIfNeeded() {
7374         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7375             mRestartMarquee = false;
7376             startMarquee();
7377         }
7378     }
7379 
7380     /**
7381      * Sets the list of input filters that will be used if the buffer is
7382      * Editable. Has no effect otherwise.
7383      *
7384      * @attr ref android.R.styleable#TextView_maxLength
7385      */
setFilters(InputFilter[] filters)7386     public void setFilters(InputFilter[] filters) {
7387         if (filters == null) {
7388             throw new IllegalArgumentException();
7389         }
7390 
7391         mFilters = filters;
7392 
7393         if (mText instanceof Editable) {
7394             setFilters((Editable) mText, filters);
7395         }
7396     }
7397 
7398     /**
7399      * Sets the list of input filters on the specified Editable,
7400      * and includes mInput in the list if it is an InputFilter.
7401      */
setFilters(Editable e, InputFilter[] filters)7402     private void setFilters(Editable e, InputFilter[] filters) {
7403         if (mEditor != null) {
7404             final boolean undoFilter = mEditor.mUndoInputFilter != null;
7405             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
7406             int num = 0;
7407             if (undoFilter) num++;
7408             if (keyFilter) num++;
7409             if (num > 0) {
7410                 InputFilter[] nf = new InputFilter[filters.length + num];
7411 
7412                 System.arraycopy(filters, 0, nf, 0, filters.length);
7413                 num = 0;
7414                 if (undoFilter) {
7415                     nf[filters.length] = mEditor.mUndoInputFilter;
7416                     num++;
7417                 }
7418                 if (keyFilter) {
7419                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
7420                 }
7421 
7422                 e.setFilters(nf);
7423                 return;
7424             }
7425         }
7426         e.setFilters(filters);
7427     }
7428 
7429     /**
7430      * Returns the current list of input filters.
7431      *
7432      * @attr ref android.R.styleable#TextView_maxLength
7433      */
getFilters()7434     public InputFilter[] getFilters() {
7435         return mFilters;
7436     }
7437 
7438     /////////////////////////////////////////////////////////////////////////
7439 
getBoxHeight(Layout l)7440     private int getBoxHeight(Layout l) {
7441         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
7442         int padding = (l == mHintLayout)
7443                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
7444                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
7445         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
7446     }
7447 
7448     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)7449     int getVerticalOffset(boolean forceNormal) {
7450         int voffset = 0;
7451         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7452 
7453         Layout l = mLayout;
7454         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7455             l = mHintLayout;
7456         }
7457 
7458         if (gravity != Gravity.TOP) {
7459             int boxht = getBoxHeight(l);
7460             int textht = l.getHeight();
7461 
7462             if (textht < boxht) {
7463                 if (gravity == Gravity.BOTTOM) {
7464                     voffset = boxht - textht;
7465                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7466                     voffset = (boxht - textht) >> 1;
7467                 }
7468             }
7469         }
7470         return voffset;
7471     }
7472 
getBottomVerticalOffset(boolean forceNormal)7473     private int getBottomVerticalOffset(boolean forceNormal) {
7474         int voffset = 0;
7475         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7476 
7477         Layout l = mLayout;
7478         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7479             l = mHintLayout;
7480         }
7481 
7482         if (gravity != Gravity.BOTTOM) {
7483             int boxht = getBoxHeight(l);
7484             int textht = l.getHeight();
7485 
7486             if (textht < boxht) {
7487                 if (gravity == Gravity.TOP) {
7488                     voffset = boxht - textht;
7489                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7490                     voffset = (boxht - textht) >> 1;
7491                 }
7492             }
7493         }
7494         return voffset;
7495     }
7496 
invalidateCursorPath()7497     void invalidateCursorPath() {
7498         if (mHighlightPathBogus) {
7499             invalidateCursor();
7500         } else {
7501             final int horizontalPadding = getCompoundPaddingLeft();
7502             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7503 
7504             if (mEditor.mDrawableForCursor == null) {
7505                 synchronized (TEMP_RECTF) {
7506                     /*
7507                      * The reason for this concern about the thickness of the
7508                      * cursor and doing the floor/ceil on the coordinates is that
7509                      * some EditTexts (notably textfields in the Browser) have
7510                      * anti-aliased text where not all the characters are
7511                      * necessarily at integer-multiple locations.  This should
7512                      * make sure the entire cursor gets invalidated instead of
7513                      * sometimes missing half a pixel.
7514                      */
7515                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
7516                     if (thick < 1.0f) {
7517                         thick = 1.0f;
7518                     }
7519 
7520                     thick /= 2.0f;
7521 
7522                     // mHighlightPath is guaranteed to be non null at that point.
7523                     mHighlightPath.computeBounds(TEMP_RECTF, false);
7524 
7525                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
7526                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
7527                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
7528                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
7529                 }
7530             } else {
7531                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7532                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
7533                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
7534             }
7535         }
7536     }
7537 
invalidateCursor()7538     void invalidateCursor() {
7539         int where = getSelectionEnd();
7540 
7541         invalidateCursor(where, where, where);
7542     }
7543 
invalidateCursor(int a, int b, int c)7544     private void invalidateCursor(int a, int b, int c) {
7545         if (a >= 0 || b >= 0 || c >= 0) {
7546             int start = Math.min(Math.min(a, b), c);
7547             int end = Math.max(Math.max(a, b), c);
7548             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
7549         }
7550     }
7551 
7552     /**
7553      * Invalidates the region of text enclosed between the start and end text offsets.
7554      */
invalidateRegion(int start, int end, boolean invalidateCursor)7555     void invalidateRegion(int start, int end, boolean invalidateCursor) {
7556         if (mLayout == null) {
7557             invalidate();
7558         } else {
7559             int lineStart = mLayout.getLineForOffset(start);
7560             int top = mLayout.getLineTop(lineStart);
7561 
7562             // This is ridiculous, but the descent from the line above
7563             // can hang down into the line we really want to redraw,
7564             // so we have to invalidate part of the line above to make
7565             // sure everything that needs to be redrawn really is.
7566             // (But not the whole line above, because that would cause
7567             // the same problem with the descenders on the line above it!)
7568             if (lineStart > 0) {
7569                 top -= mLayout.getLineDescent(lineStart - 1);
7570             }
7571 
7572             int lineEnd;
7573 
7574             if (start == end) {
7575                 lineEnd = lineStart;
7576             } else {
7577                 lineEnd = mLayout.getLineForOffset(end);
7578             }
7579 
7580             int bottom = mLayout.getLineBottom(lineEnd);
7581 
7582             // mEditor can be null in case selection is set programmatically.
7583             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
7584                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7585                 top = Math.min(top, bounds.top);
7586                 bottom = Math.max(bottom, bounds.bottom);
7587             }
7588 
7589             final int compoundPaddingLeft = getCompoundPaddingLeft();
7590             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7591 
7592             int left, right;
7593             if (lineStart == lineEnd && !invalidateCursor) {
7594                 left = (int) mLayout.getPrimaryHorizontal(start);
7595                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
7596                 left += compoundPaddingLeft;
7597                 right += compoundPaddingLeft;
7598             } else {
7599                 // Rectangle bounding box when the region spans several lines
7600                 left = compoundPaddingLeft;
7601                 right = getWidth() - getCompoundPaddingRight();
7602             }
7603 
7604             invalidate(mScrollX + left, verticalPadding + top,
7605                     mScrollX + right, verticalPadding + bottom);
7606         }
7607     }
7608 
registerForPreDraw()7609     private void registerForPreDraw() {
7610         if (!mPreDrawRegistered) {
7611             getViewTreeObserver().addOnPreDrawListener(this);
7612             mPreDrawRegistered = true;
7613         }
7614     }
7615 
unregisterForPreDraw()7616     private void unregisterForPreDraw() {
7617         getViewTreeObserver().removeOnPreDrawListener(this);
7618         mPreDrawRegistered = false;
7619         mPreDrawListenerDetached = false;
7620     }
7621 
7622     /**
7623      * {@inheritDoc}
7624      */
7625     @Override
onPreDraw()7626     public boolean onPreDraw() {
7627         if (mLayout == null) {
7628             assumeLayout();
7629         }
7630 
7631         if (mMovement != null) {
7632             /* This code also provides auto-scrolling when a cursor is moved using a
7633              * CursorController (insertion point or selection limits).
7634              * For selection, ensure start or end is visible depending on controller's state.
7635              */
7636             int curs = getSelectionEnd();
7637             // Do not create the controller if it is not already created.
7638             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
7639                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
7640                 curs = getSelectionStart();
7641             }
7642 
7643             /*
7644              * TODO: This should really only keep the end in view if
7645              * it already was before the text changed.  I'm not sure
7646              * of a good way to tell from here if it was.
7647              */
7648             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7649                 curs = mText.length();
7650             }
7651 
7652             if (curs >= 0) {
7653                 bringPointIntoView(curs);
7654             }
7655         } else {
7656             bringTextIntoView();
7657         }
7658 
7659         // This has to be checked here since:
7660         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
7661         //   a screen rotation) since layout is not yet initialized at that point.
7662         if (mEditor != null && mEditor.mCreatedWithASelection) {
7663             mEditor.refreshTextActionMode();
7664             mEditor.mCreatedWithASelection = false;
7665         }
7666 
7667         unregisterForPreDraw();
7668 
7669         return true;
7670     }
7671 
7672     @Override
onAttachedToWindow()7673     protected void onAttachedToWindow() {
7674         super.onAttachedToWindow();
7675 
7676         if (mEditor != null) mEditor.onAttachedToWindow();
7677 
7678         if (mPreDrawListenerDetached) {
7679             getViewTreeObserver().addOnPreDrawListener(this);
7680             mPreDrawListenerDetached = false;
7681         }
7682     }
7683 
7684     /** @hide */
7685     @Override
onDetachedFromWindowInternal()7686     protected void onDetachedFromWindowInternal() {
7687         if (mPreDrawRegistered) {
7688             getViewTreeObserver().removeOnPreDrawListener(this);
7689             mPreDrawListenerDetached = true;
7690         }
7691 
7692         resetResolvedDrawables();
7693 
7694         if (mEditor != null) mEditor.onDetachedFromWindow();
7695 
7696         super.onDetachedFromWindowInternal();
7697     }
7698 
7699     @Override
onScreenStateChanged(int screenState)7700     public void onScreenStateChanged(int screenState) {
7701         super.onScreenStateChanged(screenState);
7702         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
7703     }
7704 
7705     @Override
isPaddingOffsetRequired()7706     protected boolean isPaddingOffsetRequired() {
7707         return mShadowRadius != 0 || mDrawables != null;
7708     }
7709 
7710     @Override
getLeftPaddingOffset()7711     protected int getLeftPaddingOffset() {
7712         return getCompoundPaddingLeft() - mPaddingLeft
7713                 + (int) Math.min(0, mShadowDx - mShadowRadius);
7714     }
7715 
7716     @Override
getTopPaddingOffset()7717     protected int getTopPaddingOffset() {
7718         return (int) Math.min(0, mShadowDy - mShadowRadius);
7719     }
7720 
7721     @Override
getBottomPaddingOffset()7722     protected int getBottomPaddingOffset() {
7723         return (int) Math.max(0, mShadowDy + mShadowRadius);
7724     }
7725 
7726     @Override
getRightPaddingOffset()7727     protected int getRightPaddingOffset() {
7728         return -(getCompoundPaddingRight() - mPaddingRight)
7729                 + (int) Math.max(0, mShadowDx + mShadowRadius);
7730     }
7731 
7732     @Override
verifyDrawable(@onNull Drawable who)7733     protected boolean verifyDrawable(@NonNull Drawable who) {
7734         final boolean verified = super.verifyDrawable(who);
7735         if (!verified && mDrawables != null) {
7736             for (Drawable dr : mDrawables.mShowing) {
7737                 if (who == dr) {
7738                     return true;
7739                 }
7740             }
7741         }
7742         return verified;
7743     }
7744 
7745     @Override
jumpDrawablesToCurrentState()7746     public void jumpDrawablesToCurrentState() {
7747         super.jumpDrawablesToCurrentState();
7748         if (mDrawables != null) {
7749             for (Drawable dr : mDrawables.mShowing) {
7750                 if (dr != null) {
7751                     dr.jumpToCurrentState();
7752                 }
7753             }
7754         }
7755     }
7756 
7757     @Override
invalidateDrawable(@onNull Drawable drawable)7758     public void invalidateDrawable(@NonNull Drawable drawable) {
7759         boolean handled = false;
7760 
7761         if (verifyDrawable(drawable)) {
7762             final Rect dirty = drawable.getBounds();
7763             int scrollX = mScrollX;
7764             int scrollY = mScrollY;
7765 
7766             // IMPORTANT: The coordinates below are based on the coordinates computed
7767             // for each compound drawable in onDraw(). Make sure to update each section
7768             // accordingly.
7769             final TextView.Drawables drawables = mDrawables;
7770             if (drawables != null) {
7771                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
7772                     final int compoundPaddingTop = getCompoundPaddingTop();
7773                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7774                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7775 
7776                     scrollX += mPaddingLeft;
7777                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
7778                     handled = true;
7779                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
7780                     final int compoundPaddingTop = getCompoundPaddingTop();
7781                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7782                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7783 
7784                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
7785                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
7786                     handled = true;
7787                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
7788                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7789                     final int compoundPaddingRight = getCompoundPaddingRight();
7790                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7791 
7792                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
7793                     scrollY += mPaddingTop;
7794                     handled = true;
7795                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
7796                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7797                     final int compoundPaddingRight = getCompoundPaddingRight();
7798                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7799 
7800                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
7801                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
7802                     handled = true;
7803                 }
7804             }
7805 
7806             if (handled) {
7807                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
7808                         dirty.right + scrollX, dirty.bottom + scrollY);
7809             }
7810         }
7811 
7812         if (!handled) {
7813             super.invalidateDrawable(drawable);
7814         }
7815     }
7816 
7817     @Override
hasOverlappingRendering()7818     public boolean hasOverlappingRendering() {
7819         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
7820         return ((getBackground() != null && getBackground().getCurrent() != null)
7821                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
7822                 || mShadowColor != 0);
7823     }
7824 
7825     /**
7826      *
7827      * Returns the state of the {@code textIsSelectable} flag (See
7828      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
7829      * to allow users to select and copy text in a non-editable TextView, the content of an
7830      * {@link EditText} can always be selected, independently of the value of this flag.
7831      * <p>
7832      *
7833      * @return True if the text displayed in this TextView can be selected by the user.
7834      *
7835      * @attr ref android.R.styleable#TextView_textIsSelectable
7836      */
7837     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()7838     public boolean isTextSelectable() {
7839         return mEditor == null ? false : mEditor.mTextIsSelectable;
7840     }
7841 
7842     /**
7843      * Sets whether the content of this view is selectable by the user. The default is
7844      * {@code false}, meaning that the content is not selectable.
7845      * <p>
7846      * When you use a TextView to display a useful piece of information to the user (such as a
7847      * contact's address), make it selectable, so that the user can select and copy its
7848      * content. You can also use set the XML attribute
7849      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
7850      * <p>
7851      * When you call this method to set the value of {@code textIsSelectable}, it sets
7852      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
7853      * and {@code longClickable} to the same value. These flags correspond to the attributes
7854      * {@link android.R.styleable#View_focusable android:focusable},
7855      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
7856      * {@link android.R.styleable#View_clickable android:clickable}, and
7857      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
7858      * flags to a state you had set previously, call one or more of the following methods:
7859      * {@link #setFocusable(boolean) setFocusable()},
7860      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
7861      * {@link #setClickable(boolean) setClickable()} or
7862      * {@link #setLongClickable(boolean) setLongClickable()}.
7863      *
7864      * @param selectable Whether the content of this TextView should be selectable.
7865      */
setTextIsSelectable(boolean selectable)7866     public void setTextIsSelectable(boolean selectable) {
7867         if (!selectable && mEditor == null) return; // false is default value with no edit data
7868 
7869         createEditorIfNeeded();
7870         if (mEditor.mTextIsSelectable == selectable) return;
7871 
7872         mEditor.mTextIsSelectable = selectable;
7873         setFocusableInTouchMode(selectable);
7874         setFocusable(FOCUSABLE_AUTO);
7875         setClickable(selectable);
7876         setLongClickable(selectable);
7877 
7878         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
7879 
7880         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
7881         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
7882 
7883         // Called by setText above, but safer in case of future code changes
7884         mEditor.prepareCursorControllers();
7885     }
7886 
7887     @Override
onCreateDrawableState(int extraSpace)7888     protected int[] onCreateDrawableState(int extraSpace) {
7889         final int[] drawableState;
7890 
7891         if (mSingleLine) {
7892             drawableState = super.onCreateDrawableState(extraSpace);
7893         } else {
7894             drawableState = super.onCreateDrawableState(extraSpace + 1);
7895             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
7896         }
7897 
7898         if (isTextSelectable()) {
7899             // Disable pressed state, which was introduced when TextView was made clickable.
7900             // Prevents text color change.
7901             // setClickable(false) would have a similar effect, but it also disables focus changes
7902             // and long press actions, which are both needed by text selection.
7903             final int length = drawableState.length;
7904             for (int i = 0; i < length; i++) {
7905                 if (drawableState[i] == R.attr.state_pressed) {
7906                     final int[] nonPressedState = new int[length - 1];
7907                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
7908                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
7909                     return nonPressedState;
7910                 }
7911             }
7912         }
7913 
7914         return drawableState;
7915     }
7916 
7917     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getUpdatedHighlightPath()7918     private Path getUpdatedHighlightPath() {
7919         Path highlight = null;
7920         Paint highlightPaint = mHighlightPaint;
7921 
7922         final int selStart = getSelectionStart();
7923         final int selEnd = getSelectionEnd();
7924         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
7925             if (selStart == selEnd) {
7926                 if (mEditor != null && mEditor.shouldRenderCursor()) {
7927                     if (mHighlightPathBogus) {
7928                         if (mHighlightPath == null) mHighlightPath = new Path();
7929                         mHighlightPath.reset();
7930                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
7931                         mEditor.updateCursorPosition();
7932                         mHighlightPathBogus = false;
7933                     }
7934 
7935                     // XXX should pass to skin instead of drawing directly
7936                     highlightPaint.setColor(mCurTextColor);
7937                     highlightPaint.setStyle(Paint.Style.STROKE);
7938                     highlight = mHighlightPath;
7939                 }
7940             } else {
7941                 if (mHighlightPathBogus) {
7942                     if (mHighlightPath == null) mHighlightPath = new Path();
7943                     mHighlightPath.reset();
7944                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7945                     mHighlightPathBogus = false;
7946                 }
7947 
7948                 // XXX should pass to skin instead of drawing directly
7949                 highlightPaint.setColor(mHighlightColor);
7950                 highlightPaint.setStyle(Paint.Style.FILL);
7951 
7952                 highlight = mHighlightPath;
7953             }
7954         }
7955         return highlight;
7956     }
7957 
7958     /**
7959      * @hide
7960      */
getHorizontalOffsetForDrawables()7961     public int getHorizontalOffsetForDrawables() {
7962         return 0;
7963     }
7964 
7965     @Override
onDraw(Canvas canvas)7966     protected void onDraw(Canvas canvas) {
7967         restartMarqueeIfNeeded();
7968 
7969         // Draw the background for this view
7970         super.onDraw(canvas);
7971 
7972         final int compoundPaddingLeft = getCompoundPaddingLeft();
7973         final int compoundPaddingTop = getCompoundPaddingTop();
7974         final int compoundPaddingRight = getCompoundPaddingRight();
7975         final int compoundPaddingBottom = getCompoundPaddingBottom();
7976         final int scrollX = mScrollX;
7977         final int scrollY = mScrollY;
7978         final int right = mRight;
7979         final int left = mLeft;
7980         final int bottom = mBottom;
7981         final int top = mTop;
7982         final boolean isLayoutRtl = isLayoutRtl();
7983         final int offset = getHorizontalOffsetForDrawables();
7984         final int leftOffset = isLayoutRtl ? 0 : offset;
7985         final int rightOffset = isLayoutRtl ? offset : 0;
7986 
7987         final Drawables dr = mDrawables;
7988         if (dr != null) {
7989             /*
7990              * Compound, not extended, because the icon is not clipped
7991              * if the text height is smaller.
7992              */
7993 
7994             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
7995             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
7996 
7997             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7998             // Make sure to update invalidateDrawable() when changing this code.
7999             if (dr.mShowing[Drawables.LEFT] != null) {
8000                 canvas.save();
8001                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
8002                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
8003                 dr.mShowing[Drawables.LEFT].draw(canvas);
8004                 canvas.restore();
8005             }
8006 
8007             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8008             // Make sure to update invalidateDrawable() when changing this code.
8009             if (dr.mShowing[Drawables.RIGHT] != null) {
8010                 canvas.save();
8011                 canvas.translate(scrollX + right - left - mPaddingRight
8012                         - dr.mDrawableSizeRight - rightOffset,
8013                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
8014                 dr.mShowing[Drawables.RIGHT].draw(canvas);
8015                 canvas.restore();
8016             }
8017 
8018             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8019             // Make sure to update invalidateDrawable() when changing this code.
8020             if (dr.mShowing[Drawables.TOP] != null) {
8021                 canvas.save();
8022                 canvas.translate(scrollX + compoundPaddingLeft
8023                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
8024                 dr.mShowing[Drawables.TOP].draw(canvas);
8025                 canvas.restore();
8026             }
8027 
8028             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8029             // Make sure to update invalidateDrawable() when changing this code.
8030             if (dr.mShowing[Drawables.BOTTOM] != null) {
8031                 canvas.save();
8032                 canvas.translate(scrollX + compoundPaddingLeft
8033                         + (hspace - dr.mDrawableWidthBottom) / 2,
8034                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
8035                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
8036                 canvas.restore();
8037             }
8038         }
8039 
8040         int color = mCurTextColor;
8041 
8042         if (mLayout == null) {
8043             assumeLayout();
8044         }
8045 
8046         Layout layout = mLayout;
8047 
8048         if (mHint != null && mText.length() == 0) {
8049             if (mHintTextColor != null) {
8050                 color = mCurHintTextColor;
8051             }
8052 
8053             layout = mHintLayout;
8054         }
8055 
8056         mTextPaint.setColor(color);
8057         mTextPaint.drawableState = getDrawableState();
8058 
8059         canvas.save();
8060         /*  Would be faster if we didn't have to do this. Can we chop the
8061             (displayable) text so that we don't need to do this ever?
8062         */
8063 
8064         int extendedPaddingTop = getExtendedPaddingTop();
8065         int extendedPaddingBottom = getExtendedPaddingBottom();
8066 
8067         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8068         final int maxScrollY = mLayout.getHeight() - vspace;
8069 
8070         float clipLeft = compoundPaddingLeft + scrollX;
8071         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
8072         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
8073         float clipBottom = bottom - top + scrollY
8074                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
8075 
8076         if (mShadowRadius != 0) {
8077             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
8078             clipRight += Math.max(0, mShadowDx + mShadowRadius);
8079 
8080             clipTop += Math.min(0, mShadowDy - mShadowRadius);
8081             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
8082         }
8083 
8084         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
8085 
8086         int voffsetText = 0;
8087         int voffsetCursor = 0;
8088 
8089         // translate in by our padding
8090         /* shortcircuit calling getVerticaOffset() */
8091         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8092             voffsetText = getVerticalOffset(false);
8093             voffsetCursor = getVerticalOffset(true);
8094         }
8095         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
8096 
8097         final int layoutDirection = getLayoutDirection();
8098         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8099         if (isMarqueeFadeEnabled()) {
8100             if (!mSingleLine && getLineCount() == 1 && canMarquee()
8101                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
8102                 final int width = mRight - mLeft;
8103                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
8104                 final float dx = mLayout.getLineRight(0) - (width - padding);
8105                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8106             }
8107 
8108             if (mMarquee != null && mMarquee.isRunning()) {
8109                 final float dx = -mMarquee.getScroll();
8110                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8111             }
8112         }
8113 
8114         final int cursorOffsetVertical = voffsetCursor - voffsetText;
8115 
8116         Path highlight = getUpdatedHighlightPath();
8117         if (mEditor != null) {
8118             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
8119         } else {
8120             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
8121         }
8122 
8123         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
8124             final float dx = mMarquee.getGhostOffset();
8125             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8126             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
8127         }
8128 
8129         canvas.restore();
8130     }
8131 
8132     @Override
getFocusedRect(Rect r)8133     public void getFocusedRect(Rect r) {
8134         if (mLayout == null) {
8135             super.getFocusedRect(r);
8136             return;
8137         }
8138 
8139         int selEnd = getSelectionEnd();
8140         if (selEnd < 0) {
8141             super.getFocusedRect(r);
8142             return;
8143         }
8144 
8145         int selStart = getSelectionStart();
8146         if (selStart < 0 || selStart >= selEnd) {
8147             int line = mLayout.getLineForOffset(selEnd);
8148             r.top = mLayout.getLineTop(line);
8149             r.bottom = mLayout.getLineBottom(line);
8150             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
8151             r.right = r.left + 4;
8152         } else {
8153             int lineStart = mLayout.getLineForOffset(selStart);
8154             int lineEnd = mLayout.getLineForOffset(selEnd);
8155             r.top = mLayout.getLineTop(lineStart);
8156             r.bottom = mLayout.getLineBottom(lineEnd);
8157             if (lineStart == lineEnd) {
8158                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
8159                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
8160             } else {
8161                 // Selection extends across multiple lines -- make the focused
8162                 // rect cover the entire width.
8163                 if (mHighlightPathBogus) {
8164                     if (mHighlightPath == null) mHighlightPath = new Path();
8165                     mHighlightPath.reset();
8166                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8167                     mHighlightPathBogus = false;
8168                 }
8169                 synchronized (TEMP_RECTF) {
8170                     mHighlightPath.computeBounds(TEMP_RECTF, true);
8171                     r.left = (int) TEMP_RECTF.left - 1;
8172                     r.right = (int) TEMP_RECTF.right + 1;
8173                 }
8174             }
8175         }
8176 
8177         // Adjust for padding and gravity.
8178         int paddingLeft = getCompoundPaddingLeft();
8179         int paddingTop = getExtendedPaddingTop();
8180         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8181             paddingTop += getVerticalOffset(false);
8182         }
8183         r.offset(paddingLeft, paddingTop);
8184         int paddingBottom = getExtendedPaddingBottom();
8185         r.bottom += paddingBottom;
8186     }
8187 
8188     /**
8189      * Return the number of lines of text, or 0 if the internal Layout has not
8190      * been built.
8191      */
getLineCount()8192     public int getLineCount() {
8193         return mLayout != null ? mLayout.getLineCount() : 0;
8194     }
8195 
8196     /**
8197      * Return the baseline for the specified line (0...getLineCount() - 1)
8198      * If bounds is not null, return the top, left, right, bottom extents
8199      * of the specified line in it. If the internal Layout has not been built,
8200      * return 0 and set bounds to (0, 0, 0, 0)
8201      * @param line which line to examine (0..getLineCount() - 1)
8202      * @param bounds Optional. If not null, it returns the extent of the line
8203      * @return the Y-coordinate of the baseline
8204      */
getLineBounds(int line, Rect bounds)8205     public int getLineBounds(int line, Rect bounds) {
8206         if (mLayout == null) {
8207             if (bounds != null) {
8208                 bounds.set(0, 0, 0, 0);
8209             }
8210             return 0;
8211         } else {
8212             int baseline = mLayout.getLineBounds(line, bounds);
8213 
8214             int voffset = getExtendedPaddingTop();
8215             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8216                 voffset += getVerticalOffset(true);
8217             }
8218             if (bounds != null) {
8219                 bounds.offset(getCompoundPaddingLeft(), voffset);
8220             }
8221             return baseline + voffset;
8222         }
8223     }
8224 
8225     @Override
getBaseline()8226     public int getBaseline() {
8227         if (mLayout == null) {
8228             return super.getBaseline();
8229         }
8230 
8231         return getBaselineOffset() + mLayout.getLineBaseline(0);
8232     }
8233 
getBaselineOffset()8234     int getBaselineOffset() {
8235         int voffset = 0;
8236         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8237             voffset = getVerticalOffset(true);
8238         }
8239 
8240         if (isLayoutModeOptical(mParent)) {
8241             voffset -= getOpticalInsets().top;
8242         }
8243 
8244         return getExtendedPaddingTop() + voffset;
8245     }
8246 
8247     /**
8248      * @hide
8249      */
8250     @Override
getFadeTop(boolean offsetRequired)8251     protected int getFadeTop(boolean offsetRequired) {
8252         if (mLayout == null) return 0;
8253 
8254         int voffset = 0;
8255         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8256             voffset = getVerticalOffset(true);
8257         }
8258 
8259         if (offsetRequired) voffset += getTopPaddingOffset();
8260 
8261         return getExtendedPaddingTop() + voffset;
8262     }
8263 
8264     /**
8265      * @hide
8266      */
8267     @Override
getFadeHeight(boolean offsetRequired)8268     protected int getFadeHeight(boolean offsetRequired) {
8269         return mLayout != null ? mLayout.getHeight() : 0;
8270     }
8271 
8272     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)8273     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
8274         if (mSpannable != null && mLinksClickable) {
8275             final float x = event.getX(pointerIndex);
8276             final float y = event.getY(pointerIndex);
8277             final int offset = getOffsetForPosition(x, y);
8278             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
8279                     ClickableSpan.class);
8280             if (clickables.length > 0) {
8281                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
8282             }
8283         }
8284         if (isTextSelectable() || isTextEditable()) {
8285             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
8286         }
8287         return super.onResolvePointerIcon(event, pointerIndex);
8288     }
8289 
8290     @Override
onKeyPreIme(int keyCode, KeyEvent event)8291     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
8292         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
8293         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
8294         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
8295         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
8296             return true;
8297         }
8298         return super.onKeyPreIme(keyCode, event);
8299     }
8300 
8301     /**
8302      * @hide
8303      */
handleBackInTextActionModeIfNeeded(KeyEvent event)8304     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
8305         // Do nothing unless mEditor is in text action mode.
8306         if (mEditor == null || mEditor.getTextActionMode() == null) {
8307             return false;
8308         }
8309 
8310         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
8311             KeyEvent.DispatcherState state = getKeyDispatcherState();
8312             if (state != null) {
8313                 state.startTracking(event, this);
8314             }
8315             return true;
8316         } else if (event.getAction() == KeyEvent.ACTION_UP) {
8317             KeyEvent.DispatcherState state = getKeyDispatcherState();
8318             if (state != null) {
8319                 state.handleUpEvent(event);
8320             }
8321             if (event.isTracking() && !event.isCanceled()) {
8322                 stopTextActionMode();
8323                 return true;
8324             }
8325         }
8326         return false;
8327     }
8328 
8329     @Override
onKeyDown(int keyCode, KeyEvent event)8330     public boolean onKeyDown(int keyCode, KeyEvent event) {
8331         final int which = doKeyDown(keyCode, event, null);
8332         if (which == KEY_EVENT_NOT_HANDLED) {
8333             return super.onKeyDown(keyCode, event);
8334         }
8335 
8336         return true;
8337     }
8338 
8339     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8340     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
8341         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
8342         final int which = doKeyDown(keyCode, down, event);
8343         if (which == KEY_EVENT_NOT_HANDLED) {
8344             // Go through default dispatching.
8345             return super.onKeyMultiple(keyCode, repeatCount, event);
8346         }
8347         if (which == KEY_EVENT_HANDLED) {
8348             // Consumed the whole thing.
8349             return true;
8350         }
8351 
8352         repeatCount--;
8353 
8354         // We are going to dispatch the remaining events to either the input
8355         // or movement method.  To do this, we will just send a repeated stream
8356         // of down and up events until we have done the complete repeatCount.
8357         // It would be nice if those interfaces had an onKeyMultiple() method,
8358         // but adding that is a more complicated change.
8359         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
8360         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
8361             // mEditor and mEditor.mInput are not null from doKeyDown
8362             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8363             while (--repeatCount > 0) {
8364                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
8365                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8366             }
8367             hideErrorIfUnchanged();
8368 
8369         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
8370             // mMovement is not null from doKeyDown
8371             mMovement.onKeyUp(this, mSpannable, keyCode, up);
8372             while (--repeatCount > 0) {
8373                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
8374                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
8375             }
8376         }
8377 
8378         return true;
8379     }
8380 
8381     /**
8382      * Returns true if pressing ENTER in this field advances focus instead
8383      * of inserting the character.  This is true mostly in single-line fields,
8384      * but also in mail addresses and subjects which will display on multiple
8385      * lines but where it doesn't make sense to insert newlines.
8386      */
shouldAdvanceFocusOnEnter()8387     private boolean shouldAdvanceFocusOnEnter() {
8388         if (getKeyListener() == null) {
8389             return false;
8390         }
8391 
8392         if (mSingleLine) {
8393             return true;
8394         }
8395 
8396         if (mEditor != null
8397                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8398                         == EditorInfo.TYPE_CLASS_TEXT) {
8399             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8400             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
8401                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
8402                 return true;
8403             }
8404         }
8405 
8406         return false;
8407     }
8408 
isDirectionalNavigationKey(int keyCode)8409     private boolean isDirectionalNavigationKey(int keyCode) {
8410         switch(keyCode) {
8411             case KeyEvent.KEYCODE_DPAD_UP:
8412             case KeyEvent.KEYCODE_DPAD_DOWN:
8413             case KeyEvent.KEYCODE_DPAD_LEFT:
8414             case KeyEvent.KEYCODE_DPAD_RIGHT:
8415                 return true;
8416         }
8417         return false;
8418     }
8419 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8420     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
8421         if (!isEnabled()) {
8422             return KEY_EVENT_NOT_HANDLED;
8423         }
8424 
8425         // If this is the initial keydown, we don't want to prevent a movement away from this view.
8426         // While this shouldn't be necessary because any time we're preventing default movement we
8427         // should be restricting the focus to remain within this view, thus we'll also receive
8428         // the key up event, occasionally key up events will get dropped and we don't want to
8429         // prevent the user from traversing out of this on the next key down.
8430         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8431             mPreventDefaultMovement = false;
8432         }
8433 
8434         switch (keyCode) {
8435             case KeyEvent.KEYCODE_ENTER:
8436             case KeyEvent.KEYCODE_NUMPAD_ENTER:
8437                 if (event.hasNoModifiers()) {
8438                     // When mInputContentType is set, we know that we are
8439                     // running in a "modern" cupcake environment, so don't need
8440                     // to worry about the application trying to capture
8441                     // enter key events.
8442                     if (mEditor != null && mEditor.mInputContentType != null) {
8443                         // If there is an action listener, given them a
8444                         // chance to consume the event.
8445                         if (mEditor.mInputContentType.onEditorActionListener != null
8446                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8447                                         this, EditorInfo.IME_NULL, event)) {
8448                             mEditor.mInputContentType.enterDown = true;
8449                             // We are consuming the enter key for them.
8450                             return KEY_EVENT_HANDLED;
8451                         }
8452                     }
8453 
8454                     // If our editor should move focus when enter is pressed, or
8455                     // this is a generated event from an IME action button, then
8456                     // don't let it be inserted into the text.
8457                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8458                             || shouldAdvanceFocusOnEnter()) {
8459                         if (hasOnClickListeners()) {
8460                             return KEY_EVENT_NOT_HANDLED;
8461                         }
8462                         return KEY_EVENT_HANDLED;
8463                     }
8464                 }
8465                 break;
8466 
8467             case KeyEvent.KEYCODE_DPAD_CENTER:
8468                 if (event.hasNoModifiers()) {
8469                     if (shouldAdvanceFocusOnEnter()) {
8470                         return KEY_EVENT_NOT_HANDLED;
8471                     }
8472                 }
8473                 break;
8474 
8475             case KeyEvent.KEYCODE_TAB:
8476                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
8477                     // Tab is used to move focus.
8478                     return KEY_EVENT_NOT_HANDLED;
8479                 }
8480                 break;
8481 
8482                 // Has to be done on key down (and not on key up) to correctly be intercepted.
8483             case KeyEvent.KEYCODE_BACK:
8484                 if (mEditor != null && mEditor.getTextActionMode() != null) {
8485                     stopTextActionMode();
8486                     return KEY_EVENT_HANDLED;
8487                 }
8488                 break;
8489 
8490             case KeyEvent.KEYCODE_CUT:
8491                 if (event.hasNoModifiers() && canCut()) {
8492                     if (onTextContextMenuItem(ID_CUT)) {
8493                         return KEY_EVENT_HANDLED;
8494                     }
8495                 }
8496                 break;
8497 
8498             case KeyEvent.KEYCODE_COPY:
8499                 if (event.hasNoModifiers() && canCopy()) {
8500                     if (onTextContextMenuItem(ID_COPY)) {
8501                         return KEY_EVENT_HANDLED;
8502                     }
8503                 }
8504                 break;
8505 
8506             case KeyEvent.KEYCODE_PASTE:
8507                 if (event.hasNoModifiers() && canPaste()) {
8508                     if (onTextContextMenuItem(ID_PASTE)) {
8509                         return KEY_EVENT_HANDLED;
8510                     }
8511                 }
8512                 break;
8513 
8514             case KeyEvent.KEYCODE_FORWARD_DEL:
8515                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
8516                     if (onTextContextMenuItem(ID_CUT)) {
8517                         return KEY_EVENT_HANDLED;
8518                     }
8519                 }
8520                 break;
8521 
8522             case KeyEvent.KEYCODE_INSERT:
8523                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
8524                     if (onTextContextMenuItem(ID_COPY)) {
8525                         return KEY_EVENT_HANDLED;
8526                     }
8527                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
8528                     if (onTextContextMenuItem(ID_PASTE)) {
8529                         return KEY_EVENT_HANDLED;
8530                     }
8531                 }
8532                 break;
8533         }
8534 
8535         if (mEditor != null && mEditor.mKeyListener != null) {
8536             boolean doDown = true;
8537             if (otherEvent != null) {
8538                 try {
8539                     beginBatchEdit();
8540                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
8541                             otherEvent);
8542                     hideErrorIfUnchanged();
8543                     doDown = false;
8544                     if (handled) {
8545                         return KEY_EVENT_HANDLED;
8546                     }
8547                 } catch (AbstractMethodError e) {
8548                     // onKeyOther was added after 1.0, so if it isn't
8549                     // implemented we need to try to dispatch as a regular down.
8550                 } finally {
8551                     endBatchEdit();
8552                 }
8553             }
8554 
8555             if (doDown) {
8556                 beginBatchEdit();
8557                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
8558                         keyCode, event);
8559                 endBatchEdit();
8560                 hideErrorIfUnchanged();
8561                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
8562             }
8563         }
8564 
8565         // bug 650865: sometimes we get a key event before a layout.
8566         // don't try to move around if we don't know the layout.
8567 
8568         if (mMovement != null && mLayout != null) {
8569             boolean doDown = true;
8570             if (otherEvent != null) {
8571                 try {
8572                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
8573                     doDown = false;
8574                     if (handled) {
8575                         return KEY_EVENT_HANDLED;
8576                     }
8577                 } catch (AbstractMethodError e) {
8578                     // onKeyOther was added after 1.0, so if it isn't
8579                     // implemented we need to try to dispatch as a regular down.
8580                 }
8581             }
8582             if (doDown) {
8583                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
8584                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8585                         mPreventDefaultMovement = true;
8586                     }
8587                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
8588                 }
8589             }
8590             // Consume arrows from keyboard devices to prevent focus leaving the editor.
8591             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
8592             // to move focus with arrows.
8593             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
8594                     && isDirectionalNavigationKey(keyCode)) {
8595                 return KEY_EVENT_HANDLED;
8596             }
8597         }
8598 
8599         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
8600                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
8601     }
8602 
8603     /**
8604      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
8605      * can be recorded.
8606      * @hide
8607      */
resetErrorChangedFlag()8608     public void resetErrorChangedFlag() {
8609         /*
8610          * Keep track of what the error was before doing the input
8611          * so that if an input filter changed the error, we leave
8612          * that error showing.  Otherwise, we take down whatever
8613          * error was showing when the user types something.
8614          */
8615         if (mEditor != null) mEditor.mErrorWasChanged = false;
8616     }
8617 
8618     /**
8619      * @hide
8620      */
hideErrorIfUnchanged()8621     public void hideErrorIfUnchanged() {
8622         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
8623             setError(null, null);
8624         }
8625     }
8626 
8627     @Override
onKeyUp(int keyCode, KeyEvent event)8628     public boolean onKeyUp(int keyCode, KeyEvent event) {
8629         if (!isEnabled()) {
8630             return super.onKeyUp(keyCode, event);
8631         }
8632 
8633         if (!KeyEvent.isModifierKey(keyCode)) {
8634             mPreventDefaultMovement = false;
8635         }
8636 
8637         switch (keyCode) {
8638             case KeyEvent.KEYCODE_DPAD_CENTER:
8639                 if (event.hasNoModifiers()) {
8640                     /*
8641                      * If there is a click listener, just call through to
8642                      * super, which will invoke it.
8643                      *
8644                      * If there isn't a click listener, try to show the soft
8645                      * input method.  (It will also
8646                      * call performClick(), but that won't do anything in
8647                      * this case.)
8648                      */
8649                     if (!hasOnClickListeners()) {
8650                         if (mMovement != null && mText instanceof Editable
8651                                 && mLayout != null && onCheckIsTextEditor()) {
8652                             InputMethodManager imm = getInputMethodManager();
8653                             viewClicked(imm);
8654                             if (imm != null && getShowSoftInputOnFocus()) {
8655                                 imm.showSoftInput(this, 0);
8656                             }
8657                         }
8658                     }
8659                 }
8660                 return super.onKeyUp(keyCode, event);
8661 
8662             case KeyEvent.KEYCODE_ENTER:
8663             case KeyEvent.KEYCODE_NUMPAD_ENTER:
8664                 if (event.hasNoModifiers()) {
8665                     if (mEditor != null && mEditor.mInputContentType != null
8666                             && mEditor.mInputContentType.onEditorActionListener != null
8667                             && mEditor.mInputContentType.enterDown) {
8668                         mEditor.mInputContentType.enterDown = false;
8669                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8670                                 this, EditorInfo.IME_NULL, event)) {
8671                             return true;
8672                         }
8673                     }
8674 
8675                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8676                             || shouldAdvanceFocusOnEnter()) {
8677                         /*
8678                          * If there is a click listener, just call through to
8679                          * super, which will invoke it.
8680                          *
8681                          * If there isn't a click listener, try to advance focus,
8682                          * but still call through to super, which will reset the
8683                          * pressed state and longpress state.  (It will also
8684                          * call performClick(), but that won't do anything in
8685                          * this case.)
8686                          */
8687                         if (!hasOnClickListeners()) {
8688                             View v = focusSearch(FOCUS_DOWN);
8689 
8690                             if (v != null) {
8691                                 if (!v.requestFocus(FOCUS_DOWN)) {
8692                                     throw new IllegalStateException("focus search returned a view "
8693                                             + "that wasn't able to take focus!");
8694                                 }
8695 
8696                                 /*
8697                                  * Return true because we handled the key; super
8698                                  * will return false because there was no click
8699                                  * listener.
8700                                  */
8701                                 super.onKeyUp(keyCode, event);
8702                                 return true;
8703                             } else if ((event.getFlags()
8704                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
8705                                 // No target for next focus, but make sure the IME
8706                                 // if this came from it.
8707                                 InputMethodManager imm = getInputMethodManager();
8708                                 if (imm != null && imm.isActive(this)) {
8709                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
8710                                 }
8711                             }
8712                         }
8713                     }
8714                     return super.onKeyUp(keyCode, event);
8715                 }
8716                 break;
8717         }
8718 
8719         if (mEditor != null && mEditor.mKeyListener != null) {
8720             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
8721                 return true;
8722             }
8723         }
8724 
8725         if (mMovement != null && mLayout != null) {
8726             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
8727                 return true;
8728             }
8729         }
8730 
8731         return super.onKeyUp(keyCode, event);
8732     }
8733 
8734     @Override
onCheckIsTextEditor()8735     public boolean onCheckIsTextEditor() {
8736         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
8737     }
8738 
8739     @Override
onCreateInputConnection(EditorInfo outAttrs)8740     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
8741         if (onCheckIsTextEditor() && isEnabled()) {
8742             mEditor.createInputMethodStateIfNeeded();
8743             outAttrs.inputType = getInputType();
8744             if (mEditor.mInputContentType != null) {
8745                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
8746                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
8747                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
8748                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
8749                 outAttrs.extras = mEditor.mInputContentType.extras;
8750                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
8751             } else {
8752                 outAttrs.imeOptions = EditorInfo.IME_NULL;
8753                 outAttrs.hintLocales = null;
8754             }
8755             if (focusSearch(FOCUS_DOWN) != null) {
8756                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
8757             }
8758             if (focusSearch(FOCUS_UP) != null) {
8759                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
8760             }
8761             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
8762                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
8763                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
8764                     // An action has not been set, but the enter key will move to
8765                     // the next focus, so set the action to that.
8766                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
8767                 } else {
8768                     // An action has not been set, and there is no focus to move
8769                     // to, so let's just supply a "done" action.
8770                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
8771                 }
8772                 if (!shouldAdvanceFocusOnEnter()) {
8773                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8774                 }
8775             }
8776             if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) {
8777                 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT;
8778             }
8779             if (isMultilineInputType(outAttrs.inputType)) {
8780                 // Multi-line text editors should always show an enter key.
8781                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8782             }
8783             outAttrs.hintText = mHint;
8784             outAttrs.targetInputMethodUser = mTextOperationUser;
8785             if (mText instanceof Editable) {
8786                 InputConnection ic = new EditableInputConnection(this);
8787                 outAttrs.initialSelStart = getSelectionStart();
8788                 outAttrs.initialSelEnd = getSelectionEnd();
8789                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
8790                 outAttrs.setInitialSurroundingText(mText);
8791                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
8792                 return ic;
8793             }
8794         }
8795         return null;
8796     }
8797 
8798     /**
8799      * If this TextView contains editable content, extract a portion of it
8800      * based on the information in <var>request</var> in to <var>outText</var>.
8801      * @return Returns true if the text was successfully extracted, else false.
8802      */
extractText(ExtractedTextRequest request, ExtractedText outText)8803     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
8804         createEditorIfNeeded();
8805         return mEditor.extractText(request, outText);
8806     }
8807 
8808     /**
8809      * This is used to remove all style-impacting spans from text before new
8810      * extracted text is being replaced into it, so that we don't have any
8811      * lingering spans applied during the replace.
8812      */
removeParcelableSpans(Spannable spannable, int start, int end)8813     static void removeParcelableSpans(Spannable spannable, int start, int end) {
8814         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
8815         int i = spans.length;
8816         while (i > 0) {
8817             i--;
8818             spannable.removeSpan(spans[i]);
8819         }
8820     }
8821 
8822     /**
8823      * Apply to this text view the given extracted text, as previously
8824      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
8825      */
setExtractedText(ExtractedText text)8826     public void setExtractedText(ExtractedText text) {
8827         Editable content = getEditableText();
8828         if (text.text != null) {
8829             if (content == null) {
8830                 setText(text.text, TextView.BufferType.EDITABLE);
8831             } else {
8832                 int start = 0;
8833                 int end = content.length();
8834 
8835                 if (text.partialStartOffset >= 0) {
8836                     final int N = content.length();
8837                     start = text.partialStartOffset;
8838                     if (start > N) start = N;
8839                     end = text.partialEndOffset;
8840                     if (end > N) end = N;
8841                 }
8842 
8843                 removeParcelableSpans(content, start, end);
8844                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
8845                     if (text.text instanceof Spanned) {
8846                         // OK to copy spans only.
8847                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
8848                                 Object.class, content, start);
8849                     }
8850                 } else {
8851                     content.replace(start, end, text.text);
8852                 }
8853             }
8854         }
8855 
8856         // Now set the selection position...  make sure it is in range, to
8857         // avoid crashes.  If this is a partial update, it is possible that
8858         // the underlying text may have changed, causing us problems here.
8859         // Also we just don't want to trust clients to do the right thing.
8860         Spannable sp = (Spannable) getText();
8861         final int N = sp.length();
8862         int start = text.selectionStart;
8863         if (start < 0) {
8864             start = 0;
8865         } else if (start > N) {
8866             start = N;
8867         }
8868         int end = text.selectionEnd;
8869         if (end < 0) {
8870             end = 0;
8871         } else if (end > N) {
8872             end = N;
8873         }
8874         Selection.setSelection(sp, start, end);
8875 
8876         // Finally, update the selection mode.
8877         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
8878             MetaKeyKeyListener.startSelecting(this, sp);
8879         } else {
8880             MetaKeyKeyListener.stopSelecting(this, sp);
8881         }
8882 
8883         setHintInternal(text.hint);
8884     }
8885 
8886     /**
8887      * @hide
8888      */
setExtracting(ExtractedTextRequest req)8889     public void setExtracting(ExtractedTextRequest req) {
8890         if (mEditor.mInputMethodState != null) {
8891             mEditor.mInputMethodState.mExtractedTextRequest = req;
8892         }
8893         // This would stop a possible selection mode, but no such mode is started in case
8894         // extracted mode will start. Some text is selected though, and will trigger an action mode
8895         // in the extracted view.
8896         mEditor.hideCursorAndSpanControllers();
8897         stopTextActionMode();
8898         if (mEditor.mSelectionModifierCursorController != null) {
8899             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
8900         }
8901     }
8902 
8903     /**
8904      * Called by the framework in response to a text completion from
8905      * the current input method, provided by it calling
8906      * {@link InputConnection#commitCompletion
8907      * InputConnection.commitCompletion()}.  The default implementation does
8908      * nothing; text views that are supporting auto-completion should override
8909      * this to do their desired behavior.
8910      *
8911      * @param text The auto complete text the user has selected.
8912      */
onCommitCompletion(CompletionInfo text)8913     public void onCommitCompletion(CompletionInfo text) {
8914         // intentionally empty
8915     }
8916 
8917     /**
8918      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
8919      * dictionary) from the current input method, provided by it calling
8920      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
8921      * The default implementation flashes the background of the corrected word to provide
8922      * feedback to the user.
8923      *
8924      * @param info The auto correct info about the text that was corrected.
8925      */
onCommitCorrection(CorrectionInfo info)8926     public void onCommitCorrection(CorrectionInfo info) {
8927         if (mEditor != null) mEditor.onCommitCorrection(info);
8928     }
8929 
beginBatchEdit()8930     public void beginBatchEdit() {
8931         if (mEditor != null) mEditor.beginBatchEdit();
8932     }
8933 
endBatchEdit()8934     public void endBatchEdit() {
8935         if (mEditor != null) mEditor.endBatchEdit();
8936     }
8937 
8938     /**
8939      * Called by the framework in response to a request to begin a batch
8940      * of edit operations through a call to link {@link #beginBatchEdit()}.
8941      */
onBeginBatchEdit()8942     public void onBeginBatchEdit() {
8943         // intentionally empty
8944     }
8945 
8946     /**
8947      * Called by the framework in response to a request to end a batch
8948      * of edit operations through a call to link {@link #endBatchEdit}.
8949      */
onEndBatchEdit()8950     public void onEndBatchEdit() {
8951         // intentionally empty
8952     }
8953 
8954     /** @hide */
onPerformSpellCheck()8955     public void onPerformSpellCheck() {
8956         if (mEditor != null && mEditor.mSpellChecker != null) {
8957             mEditor.mSpellChecker.onPerformSpellCheck();
8958         }
8959     }
8960 
8961     /**
8962      * Called by the framework in response to a private command from the
8963      * current method, provided by it calling
8964      * {@link InputConnection#performPrivateCommand
8965      * InputConnection.performPrivateCommand()}.
8966      *
8967      * @param action The action name of the command.
8968      * @param data Any additional data for the command.  This may be null.
8969      * @return Return true if you handled the command, else false.
8970      */
onPrivateIMECommand(String action, Bundle data)8971     public boolean onPrivateIMECommand(String action, Bundle data) {
8972         return false;
8973     }
8974 
8975     /** @hide */
8976     @VisibleForTesting
8977     @UnsupportedAppUsage
nullLayouts()8978     public void nullLayouts() {
8979         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
8980             mSavedLayout = (BoringLayout) mLayout;
8981         }
8982         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
8983             mSavedHintLayout = (BoringLayout) mHintLayout;
8984         }
8985 
8986         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
8987 
8988         mBoring = mHintBoring = null;
8989 
8990         // Since it depends on the value of mLayout
8991         if (mEditor != null) mEditor.prepareCursorControllers();
8992     }
8993 
8994     /**
8995      * Make a new Layout based on the already-measured size of the view,
8996      * on the assumption that it was measured correctly at some point.
8997      */
8998     @UnsupportedAppUsage
assumeLayout()8999     private void assumeLayout() {
9000         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9001 
9002         if (width < 1) {
9003             width = 0;
9004         }
9005 
9006         int physicalWidth = width;
9007 
9008         if (mHorizontallyScrolling) {
9009             width = VERY_WIDE;
9010         }
9011 
9012         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
9013                       physicalWidth, false);
9014     }
9015 
9016     @UnsupportedAppUsage
getLayoutAlignment()9017     private Layout.Alignment getLayoutAlignment() {
9018         Layout.Alignment alignment;
9019         switch (getTextAlignment()) {
9020             case TEXT_ALIGNMENT_GRAVITY:
9021                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
9022                     case Gravity.START:
9023                         alignment = Layout.Alignment.ALIGN_NORMAL;
9024                         break;
9025                     case Gravity.END:
9026                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
9027                         break;
9028                     case Gravity.LEFT:
9029                         alignment = Layout.Alignment.ALIGN_LEFT;
9030                         break;
9031                     case Gravity.RIGHT:
9032                         alignment = Layout.Alignment.ALIGN_RIGHT;
9033                         break;
9034                     case Gravity.CENTER_HORIZONTAL:
9035                         alignment = Layout.Alignment.ALIGN_CENTER;
9036                         break;
9037                     default:
9038                         alignment = Layout.Alignment.ALIGN_NORMAL;
9039                         break;
9040                 }
9041                 break;
9042             case TEXT_ALIGNMENT_TEXT_START:
9043                 alignment = Layout.Alignment.ALIGN_NORMAL;
9044                 break;
9045             case TEXT_ALIGNMENT_TEXT_END:
9046                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
9047                 break;
9048             case TEXT_ALIGNMENT_CENTER:
9049                 alignment = Layout.Alignment.ALIGN_CENTER;
9050                 break;
9051             case TEXT_ALIGNMENT_VIEW_START:
9052                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
9053                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
9054                 break;
9055             case TEXT_ALIGNMENT_VIEW_END:
9056                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
9057                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
9058                 break;
9059             case TEXT_ALIGNMENT_INHERIT:
9060                 // This should never happen as we have already resolved the text alignment
9061                 // but better safe than sorry so we just fall through
9062             default:
9063                 alignment = Layout.Alignment.ALIGN_NORMAL;
9064                 break;
9065         }
9066         return alignment;
9067     }
9068 
9069     /**
9070      * The width passed in is now the desired layout width,
9071      * not the full view width with padding.
9072      * {@hide}
9073      */
9074     @VisibleForTesting
9075     @UnsupportedAppUsage
makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)9076     public void makeNewLayout(int wantWidth, int hintWidth,
9077                                  BoringLayout.Metrics boring,
9078                                  BoringLayout.Metrics hintBoring,
9079                                  int ellipsisWidth, boolean bringIntoView) {
9080         stopMarquee();
9081 
9082         // Update "old" cached values
9083         mOldMaximum = mMaximum;
9084         mOldMaxMode = mMaxMode;
9085 
9086         mHighlightPathBogus = true;
9087 
9088         if (wantWidth < 0) {
9089             wantWidth = 0;
9090         }
9091         if (hintWidth < 0) {
9092             hintWidth = 0;
9093         }
9094 
9095         Layout.Alignment alignment = getLayoutAlignment();
9096         final boolean testDirChange = mSingleLine && mLayout != null
9097                 && (alignment == Layout.Alignment.ALIGN_NORMAL
9098                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
9099         int oldDir = 0;
9100         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
9101         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
9102         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
9103                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
9104         TruncateAt effectiveEllipsize = mEllipsize;
9105         if (mEllipsize == TruncateAt.MARQUEE
9106                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
9107             effectiveEllipsize = TruncateAt.END_SMALL;
9108         }
9109 
9110         if (mTextDir == null) {
9111             mTextDir = getTextDirectionHeuristic();
9112         }
9113 
9114         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
9115                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
9116         if (switchEllipsize) {
9117             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
9118                     ? TruncateAt.END : TruncateAt.MARQUEE;
9119             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
9120                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
9121         }
9122 
9123         shouldEllipsize = mEllipsize != null;
9124         mHintLayout = null;
9125 
9126         if (mHint != null) {
9127             if (shouldEllipsize) hintWidth = wantWidth;
9128 
9129             if (hintBoring == UNKNOWN_BORING) {
9130                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
9131                                                    mHintBoring);
9132                 if (hintBoring != null) {
9133                     mHintBoring = hintBoring;
9134                 }
9135             }
9136 
9137             if (hintBoring != null) {
9138                 if (hintBoring.width <= hintWidth
9139                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
9140                     if (mSavedHintLayout != null) {
9141                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9142                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9143                                 hintBoring, mIncludePad);
9144                     } else {
9145                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9146                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9147                                 hintBoring, mIncludePad);
9148                     }
9149 
9150                     mSavedHintLayout = (BoringLayout) mHintLayout;
9151                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
9152                     if (mSavedHintLayout != null) {
9153                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9154                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9155                                 hintBoring, mIncludePad, mEllipsize,
9156                                 ellipsisWidth);
9157                     } else {
9158                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9159                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9160                                 hintBoring, mIncludePad, mEllipsize,
9161                                 ellipsisWidth);
9162                     }
9163                 }
9164             }
9165             // TODO: code duplication with makeSingleLayout()
9166             if (mHintLayout == null) {
9167                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
9168                         mHint.length(), mTextPaint, hintWidth)
9169                         .setAlignment(alignment)
9170                         .setTextDirection(mTextDir)
9171                         .setLineSpacing(mSpacingAdd, mSpacingMult)
9172                         .setIncludePad(mIncludePad)
9173                         .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9174                         .setBreakStrategy(mBreakStrategy)
9175                         .setHyphenationFrequency(mHyphenationFrequency)
9176                         .setJustificationMode(mJustificationMode)
9177                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9178                 if (shouldEllipsize) {
9179                     builder.setEllipsize(mEllipsize)
9180                             .setEllipsizedWidth(ellipsisWidth);
9181                 }
9182                 mHintLayout = builder.build();
9183             }
9184         }
9185 
9186         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
9187             registerForPreDraw();
9188         }
9189 
9190         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9191             if (!compressText(ellipsisWidth)) {
9192                 final int height = mLayoutParams.height;
9193                 // If the size of the view does not depend on the size of the text, try to
9194                 // start the marquee immediately
9195                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
9196                     startMarquee();
9197                 } else {
9198                     // Defer the start of the marquee until we know our width (see setFrame())
9199                     mRestartMarquee = true;
9200                 }
9201             }
9202         }
9203 
9204         // CursorControllers need a non-null mLayout
9205         if (mEditor != null) mEditor.prepareCursorControllers();
9206     }
9207 
9208     /**
9209      * Returns true if DynamicLayout is required
9210      *
9211      * @hide
9212      */
9213     @VisibleForTesting
useDynamicLayout()9214     public boolean useDynamicLayout() {
9215         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
9216     }
9217 
9218     /**
9219      * @hide
9220      */
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9221     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
9222             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
9223             boolean useSaved) {
9224         Layout result = null;
9225         if (useDynamicLayout()) {
9226             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
9227                     wantWidth)
9228                     .setDisplayText(mTransformed)
9229                     .setAlignment(alignment)
9230                     .setTextDirection(mTextDir)
9231                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9232                     .setIncludePad(mIncludePad)
9233                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9234                     .setBreakStrategy(mBreakStrategy)
9235                     .setHyphenationFrequency(mHyphenationFrequency)
9236                     .setJustificationMode(mJustificationMode)
9237                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
9238                     .setEllipsizedWidth(ellipsisWidth);
9239             result = builder.build();
9240         } else {
9241             if (boring == UNKNOWN_BORING) {
9242                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9243                 if (boring != null) {
9244                     mBoring = boring;
9245                 }
9246             }
9247 
9248             if (boring != null) {
9249                 if (boring.width <= wantWidth
9250                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
9251                     if (useSaved && mSavedLayout != null) {
9252                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9253                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9254                                 boring, mIncludePad);
9255                     } else {
9256                         result = BoringLayout.make(mTransformed, mTextPaint,
9257                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9258                                 boring, mIncludePad);
9259                     }
9260 
9261                     if (useSaved) {
9262                         mSavedLayout = (BoringLayout) result;
9263                     }
9264                 } else if (shouldEllipsize && boring.width <= wantWidth) {
9265                     if (useSaved && mSavedLayout != null) {
9266                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9267                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9268                                 boring, mIncludePad, effectiveEllipsize,
9269                                 ellipsisWidth);
9270                     } else {
9271                         result = BoringLayout.make(mTransformed, mTextPaint,
9272                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9273                                 boring, mIncludePad, effectiveEllipsize,
9274                                 ellipsisWidth);
9275                     }
9276                 }
9277             }
9278         }
9279         if (result == null) {
9280             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
9281                     0, mTransformed.length(), mTextPaint, wantWidth)
9282                     .setAlignment(alignment)
9283                     .setTextDirection(mTextDir)
9284                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9285                     .setIncludePad(mIncludePad)
9286                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9287                     .setBreakStrategy(mBreakStrategy)
9288                     .setHyphenationFrequency(mHyphenationFrequency)
9289                     .setJustificationMode(mJustificationMode)
9290                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9291             if (shouldEllipsize) {
9292                 builder.setEllipsize(effectiveEllipsize)
9293                         .setEllipsizedWidth(ellipsisWidth);
9294             }
9295             result = builder.build();
9296         }
9297         return result;
9298     }
9299 
9300     @UnsupportedAppUsage
compressText(float width)9301     private boolean compressText(float width) {
9302         if (isHardwareAccelerated()) return false;
9303 
9304         // Only compress the text if it hasn't been compressed by the previous pass
9305         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
9306                 && mTextPaint.getTextScaleX() == 1.0f) {
9307             final float textWidth = mLayout.getLineWidth(0);
9308             final float overflow = (textWidth + 1.0f - width) / width;
9309             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
9310                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
9311                 post(new Runnable() {
9312                     public void run() {
9313                         requestLayout();
9314                     }
9315                 });
9316                 return true;
9317             }
9318         }
9319 
9320         return false;
9321     }
9322 
desired(Layout layout)9323     private static int desired(Layout layout) {
9324         int n = layout.getLineCount();
9325         CharSequence text = layout.getText();
9326         float max = 0;
9327 
9328         // if any line was wrapped, we can't use it.
9329         // but it's ok for the last line not to have a newline
9330 
9331         for (int i = 0; i < n - 1; i++) {
9332             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
9333                 return -1;
9334             }
9335         }
9336 
9337         for (int i = 0; i < n; i++) {
9338             max = Math.max(max, layout.getLineMax(i));
9339         }
9340 
9341         return (int) Math.ceil(max);
9342     }
9343 
9344     /**
9345      * Set whether the TextView includes extra top and bottom padding to make
9346      * room for accents that go above the normal ascent and descent.
9347      * The default is true.
9348      *
9349      * @see #getIncludeFontPadding()
9350      *
9351      * @attr ref android.R.styleable#TextView_includeFontPadding
9352      */
setIncludeFontPadding(boolean includepad)9353     public void setIncludeFontPadding(boolean includepad) {
9354         if (mIncludePad != includepad) {
9355             mIncludePad = includepad;
9356 
9357             if (mLayout != null) {
9358                 nullLayouts();
9359                 requestLayout();
9360                 invalidate();
9361             }
9362         }
9363     }
9364 
9365     /**
9366      * Gets whether the TextView includes extra top and bottom padding to make
9367      * room for accents that go above the normal ascent and descent.
9368      *
9369      * @see #setIncludeFontPadding(boolean)
9370      *
9371      * @attr ref android.R.styleable#TextView_includeFontPadding
9372      */
9373     @InspectableProperty
getIncludeFontPadding()9374     public boolean getIncludeFontPadding() {
9375         return mIncludePad;
9376     }
9377 
9378     /** @hide */
9379     @VisibleForTesting
9380     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
9381 
9382     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)9383     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
9384         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
9385         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
9386         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
9387         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
9388 
9389         int width;
9390         int height;
9391 
9392         BoringLayout.Metrics boring = UNKNOWN_BORING;
9393         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
9394 
9395         if (mTextDir == null) {
9396             mTextDir = getTextDirectionHeuristic();
9397         }
9398 
9399         int des = -1;
9400         boolean fromexisting = false;
9401         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
9402                 ?  (float) widthSize : Float.MAX_VALUE;
9403 
9404         if (widthMode == MeasureSpec.EXACTLY) {
9405             // Parent has told us how big to be. So be it.
9406             width = widthSize;
9407         } else {
9408             if (mLayout != null && mEllipsize == null) {
9409                 des = desired(mLayout);
9410             }
9411 
9412             if (des < 0) {
9413                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9414                 if (boring != null) {
9415                     mBoring = boring;
9416                 }
9417             } else {
9418                 fromexisting = true;
9419             }
9420 
9421             if (boring == null || boring == UNKNOWN_BORING) {
9422                 if (des < 0) {
9423                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
9424                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
9425                 }
9426                 width = des;
9427             } else {
9428                 width = boring.width;
9429             }
9430 
9431             final Drawables dr = mDrawables;
9432             if (dr != null) {
9433                 width = Math.max(width, dr.mDrawableWidthTop);
9434                 width = Math.max(width, dr.mDrawableWidthBottom);
9435             }
9436 
9437             if (mHint != null) {
9438                 int hintDes = -1;
9439                 int hintWidth;
9440 
9441                 if (mHintLayout != null && mEllipsize == null) {
9442                     hintDes = desired(mHintLayout);
9443                 }
9444 
9445                 if (hintDes < 0) {
9446                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
9447                     if (hintBoring != null) {
9448                         mHintBoring = hintBoring;
9449                     }
9450                 }
9451 
9452                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
9453                     if (hintDes < 0) {
9454                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
9455                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
9456                     }
9457                     hintWidth = hintDes;
9458                 } else {
9459                     hintWidth = hintBoring.width;
9460                 }
9461 
9462                 if (hintWidth > width) {
9463                     width = hintWidth;
9464                 }
9465             }
9466 
9467             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
9468 
9469             if (mMaxWidthMode == EMS) {
9470                 width = Math.min(width, mMaxWidth * getLineHeight());
9471             } else {
9472                 width = Math.min(width, mMaxWidth);
9473             }
9474 
9475             if (mMinWidthMode == EMS) {
9476                 width = Math.max(width, mMinWidth * getLineHeight());
9477             } else {
9478                 width = Math.max(width, mMinWidth);
9479             }
9480 
9481             // Check against our minimum width
9482             width = Math.max(width, getSuggestedMinimumWidth());
9483 
9484             if (widthMode == MeasureSpec.AT_MOST) {
9485                 width = Math.min(widthSize, width);
9486             }
9487         }
9488 
9489         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
9490         int unpaddedWidth = want;
9491 
9492         if (mHorizontallyScrolling) want = VERY_WIDE;
9493 
9494         int hintWant = want;
9495         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
9496 
9497         if (mLayout == null) {
9498             makeNewLayout(want, hintWant, boring, hintBoring,
9499                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9500         } else {
9501             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
9502                     || (mLayout.getEllipsizedWidth()
9503                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
9504 
9505             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
9506                     && (want > mLayout.getWidth())
9507                     && (mLayout instanceof BoringLayout
9508                             || (fromexisting && des >= 0 && des <= want));
9509 
9510             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
9511 
9512             if (layoutChanged || maximumChanged) {
9513                 if (!maximumChanged && widthChanged) {
9514                     mLayout.increaseWidthTo(want);
9515                 } else {
9516                     makeNewLayout(want, hintWant, boring, hintBoring,
9517                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9518                 }
9519             } else {
9520                 // Nothing has changed
9521             }
9522         }
9523 
9524         if (heightMode == MeasureSpec.EXACTLY) {
9525             // Parent has told us how big to be. So be it.
9526             height = heightSize;
9527             mDesiredHeightAtMeasure = -1;
9528         } else {
9529             int desired = getDesiredHeight();
9530 
9531             height = desired;
9532             mDesiredHeightAtMeasure = desired;
9533 
9534             if (heightMode == MeasureSpec.AT_MOST) {
9535                 height = Math.min(desired, heightSize);
9536             }
9537         }
9538 
9539         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
9540         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
9541             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
9542         }
9543 
9544         /*
9545          * We didn't let makeNewLayout() register to bring the cursor into view,
9546          * so do it here if there is any possibility that it is needed.
9547          */
9548         if (mMovement != null
9549                 || mLayout.getWidth() > unpaddedWidth
9550                 || mLayout.getHeight() > unpaddedHeight) {
9551             registerForPreDraw();
9552         } else {
9553             scrollTo(0, 0);
9554         }
9555 
9556         setMeasuredDimension(width, height);
9557     }
9558 
9559     /**
9560      * Automatically computes and sets the text size.
9561      */
autoSizeText()9562     private void autoSizeText() {
9563         if (!isAutoSizeEnabled()) {
9564             return;
9565         }
9566 
9567         if (mNeedsAutoSizeText) {
9568             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
9569                 return;
9570             }
9571 
9572             final int availableWidth = mHorizontallyScrolling
9573                     ? VERY_WIDE
9574                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
9575             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
9576                     - getExtendedPaddingTop();
9577 
9578             if (availableWidth <= 0 || availableHeight <= 0) {
9579                 return;
9580             }
9581 
9582             synchronized (TEMP_RECTF) {
9583                 TEMP_RECTF.setEmpty();
9584                 TEMP_RECTF.right = availableWidth;
9585                 TEMP_RECTF.bottom = availableHeight;
9586                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
9587 
9588                 if (optimalTextSize != getTextSize()) {
9589                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
9590                             false /* shouldRequestLayout */);
9591 
9592                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
9593                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9594                             false /* bringIntoView */);
9595                 }
9596             }
9597         }
9598         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
9599         // after the next layout pass should set this to false.
9600         mNeedsAutoSizeText = true;
9601     }
9602 
9603     /**
9604      * Performs a binary search to find the largest text size that will still fit within the size
9605      * available to this view.
9606      */
findLargestTextSizeWhichFits(RectF availableSpace)9607     private int findLargestTextSizeWhichFits(RectF availableSpace) {
9608         final int sizesCount = mAutoSizeTextSizesInPx.length;
9609         if (sizesCount == 0) {
9610             throw new IllegalStateException("No available text sizes to choose from.");
9611         }
9612 
9613         int bestSizeIndex = 0;
9614         int lowIndex = bestSizeIndex + 1;
9615         int highIndex = sizesCount - 1;
9616         int sizeToTryIndex;
9617         while (lowIndex <= highIndex) {
9618             sizeToTryIndex = (lowIndex + highIndex) / 2;
9619             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
9620                 bestSizeIndex = lowIndex;
9621                 lowIndex = sizeToTryIndex + 1;
9622             } else {
9623                 highIndex = sizeToTryIndex - 1;
9624                 bestSizeIndex = highIndex;
9625             }
9626         }
9627 
9628         return mAutoSizeTextSizesInPx[bestSizeIndex];
9629     }
9630 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9631     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
9632         final CharSequence text = mTransformed != null
9633                 ? mTransformed
9634                 : getText();
9635         final int maxLines = getMaxLines();
9636         if (mTempTextPaint == null) {
9637             mTempTextPaint = new TextPaint();
9638         } else {
9639             mTempTextPaint.reset();
9640         }
9641         mTempTextPaint.set(getPaint());
9642         mTempTextPaint.setTextSize(suggestedSizeInPx);
9643 
9644         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
9645                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
9646 
9647         layoutBuilder.setAlignment(getLayoutAlignment())
9648                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
9649                 .setIncludePad(getIncludeFontPadding())
9650                 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9651                 .setBreakStrategy(getBreakStrategy())
9652                 .setHyphenationFrequency(getHyphenationFrequency())
9653                 .setJustificationMode(getJustificationMode())
9654                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9655                 .setTextDirection(getTextDirectionHeuristic());
9656 
9657         final StaticLayout layout = layoutBuilder.build();
9658 
9659         // Lines overflow.
9660         if (maxLines != -1 && layout.getLineCount() > maxLines) {
9661             return false;
9662         }
9663 
9664         // Height overflow.
9665         if (layout.getHeight() > availableSpace.bottom) {
9666             return false;
9667         }
9668 
9669         return true;
9670     }
9671 
getDesiredHeight()9672     private int getDesiredHeight() {
9673         return Math.max(
9674                 getDesiredHeight(mLayout, true),
9675                 getDesiredHeight(mHintLayout, mEllipsize != null));
9676     }
9677 
getDesiredHeight(Layout layout, boolean cap)9678     private int getDesiredHeight(Layout layout, boolean cap) {
9679         if (layout == null) {
9680             return 0;
9681         }
9682 
9683         /*
9684         * Don't cap the hint to a certain number of lines.
9685         * (Do cap it, though, if we have a maximum pixel height.)
9686         */
9687         int desired = layout.getHeight(cap);
9688 
9689         final Drawables dr = mDrawables;
9690         if (dr != null) {
9691             desired = Math.max(desired, dr.mDrawableHeightLeft);
9692             desired = Math.max(desired, dr.mDrawableHeightRight);
9693         }
9694 
9695         int linecount = layout.getLineCount();
9696         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
9697         desired += padding;
9698 
9699         if (mMaxMode != LINES) {
9700             desired = Math.min(desired, mMaximum);
9701         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
9702                 || layout instanceof BoringLayout)) {
9703             desired = layout.getLineTop(mMaximum);
9704 
9705             if (dr != null) {
9706                 desired = Math.max(desired, dr.mDrawableHeightLeft);
9707                 desired = Math.max(desired, dr.mDrawableHeightRight);
9708             }
9709 
9710             desired += padding;
9711             linecount = mMaximum;
9712         }
9713 
9714         if (mMinMode == LINES) {
9715             if (linecount < mMinimum) {
9716                 desired += getLineHeight() * (mMinimum - linecount);
9717             }
9718         } else {
9719             desired = Math.max(desired, mMinimum);
9720         }
9721 
9722         // Check against our minimum height
9723         desired = Math.max(desired, getSuggestedMinimumHeight());
9724 
9725         return desired;
9726     }
9727 
9728     /**
9729      * Check whether a change to the existing text layout requires a
9730      * new view layout.
9731      */
checkForResize()9732     private void checkForResize() {
9733         boolean sizeChanged = false;
9734 
9735         if (mLayout != null) {
9736             // Check if our width changed
9737             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
9738                 sizeChanged = true;
9739                 invalidate();
9740             }
9741 
9742             // Check if our height changed
9743             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
9744                 int desiredHeight = getDesiredHeight();
9745 
9746                 if (desiredHeight != this.getHeight()) {
9747                     sizeChanged = true;
9748                 }
9749             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
9750                 if (mDesiredHeightAtMeasure >= 0) {
9751                     int desiredHeight = getDesiredHeight();
9752 
9753                     if (desiredHeight != mDesiredHeightAtMeasure) {
9754                         sizeChanged = true;
9755                     }
9756                 }
9757             }
9758         }
9759 
9760         if (sizeChanged) {
9761             requestLayout();
9762             // caller will have already invalidated
9763         }
9764     }
9765 
9766     /**
9767      * Check whether entirely new text requires a new view layout
9768      * or merely a new text layout.
9769      */
9770     @UnsupportedAppUsage
checkForRelayout()9771     private void checkForRelayout() {
9772         // If we have a fixed width, we can just swap in a new text layout
9773         // if the text height stays the same or if the view height is fixed.
9774 
9775         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
9776                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
9777                 && (mHint == null || mHintLayout != null)
9778                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
9779             // Static width, so try making a new text layout.
9780 
9781             int oldht = mLayout.getHeight();
9782             int want = mLayout.getWidth();
9783             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
9784 
9785             /*
9786              * No need to bring the text into view, since the size is not
9787              * changing (unless we do the requestLayout(), in which case it
9788              * will happen at measure).
9789              */
9790             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
9791                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9792                           false);
9793 
9794             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
9795                 // In a fixed-height view, so use our new text layout.
9796                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
9797                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
9798                     autoSizeText();
9799                     invalidate();
9800                     return;
9801                 }
9802 
9803                 // Dynamic height, but height has stayed the same,
9804                 // so use our new text layout.
9805                 if (mLayout.getHeight() == oldht
9806                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
9807                     autoSizeText();
9808                     invalidate();
9809                     return;
9810                 }
9811             }
9812 
9813             // We lose: the height has changed and we have a dynamic height.
9814             // Request a new view layout using our new text layout.
9815             requestLayout();
9816             invalidate();
9817         } else {
9818             // Dynamic width, so we have no choice but to request a new
9819             // view layout with a new text layout.
9820             nullLayouts();
9821             requestLayout();
9822             invalidate();
9823         }
9824     }
9825 
9826     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)9827     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
9828         super.onLayout(changed, left, top, right, bottom);
9829         if (mDeferScroll >= 0) {
9830             int curs = mDeferScroll;
9831             mDeferScroll = -1;
9832             bringPointIntoView(Math.min(curs, mText.length()));
9833         }
9834         // Call auto-size after the width and height have been calculated.
9835         autoSizeText();
9836     }
9837 
isShowingHint()9838     private boolean isShowingHint() {
9839         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
9840     }
9841 
9842     /**
9843      * Returns true if anything changed.
9844      */
9845     @UnsupportedAppUsage
bringTextIntoView()9846     private boolean bringTextIntoView() {
9847         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9848         int line = 0;
9849         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9850             line = layout.getLineCount() - 1;
9851         }
9852 
9853         Layout.Alignment a = layout.getParagraphAlignment(line);
9854         int dir = layout.getParagraphDirection(line);
9855         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9856         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9857         int ht = layout.getHeight();
9858 
9859         int scrollx, scrolly;
9860 
9861         // Convert to left, center, or right alignment.
9862         if (a == Layout.Alignment.ALIGN_NORMAL) {
9863             a = dir == Layout.DIR_LEFT_TO_RIGHT
9864                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
9865         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
9866             a = dir == Layout.DIR_LEFT_TO_RIGHT
9867                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
9868         }
9869 
9870         if (a == Layout.Alignment.ALIGN_CENTER) {
9871             /*
9872              * Keep centered if possible, or, if it is too wide to fit,
9873              * keep leading edge in view.
9874              */
9875 
9876             int left = (int) Math.floor(layout.getLineLeft(line));
9877             int right = (int) Math.ceil(layout.getLineRight(line));
9878 
9879             if (right - left < hspace) {
9880                 scrollx = (right + left) / 2 - hspace / 2;
9881             } else {
9882                 if (dir < 0) {
9883                     scrollx = right - hspace;
9884                 } else {
9885                     scrollx = left;
9886                 }
9887             }
9888         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
9889             int right = (int) Math.ceil(layout.getLineRight(line));
9890             scrollx = right - hspace;
9891         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
9892             scrollx = (int) Math.floor(layout.getLineLeft(line));
9893         }
9894 
9895         if (ht < vspace) {
9896             scrolly = 0;
9897         } else {
9898             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9899                 scrolly = ht - vspace;
9900             } else {
9901                 scrolly = 0;
9902             }
9903         }
9904 
9905         if (scrollx != mScrollX || scrolly != mScrollY) {
9906             scrollTo(scrollx, scrolly);
9907             return true;
9908         } else {
9909             return false;
9910         }
9911     }
9912 
9913     /**
9914      * Move the point, specified by the offset, into the view if it is needed.
9915      * This has to be called after layout. Returns true if anything changed.
9916      */
bringPointIntoView(int offset)9917     public boolean bringPointIntoView(int offset) {
9918         if (isLayoutRequested()) {
9919             mDeferScroll = offset;
9920             return false;
9921         }
9922         boolean changed = false;
9923 
9924         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9925 
9926         if (layout == null) return changed;
9927 
9928         int line = layout.getLineForOffset(offset);
9929 
9930         int grav;
9931 
9932         switch (layout.getParagraphAlignment(line)) {
9933             case ALIGN_LEFT:
9934                 grav = 1;
9935                 break;
9936             case ALIGN_RIGHT:
9937                 grav = -1;
9938                 break;
9939             case ALIGN_NORMAL:
9940                 grav = layout.getParagraphDirection(line);
9941                 break;
9942             case ALIGN_OPPOSITE:
9943                 grav = -layout.getParagraphDirection(line);
9944                 break;
9945             case ALIGN_CENTER:
9946             default:
9947                 grav = 0;
9948                 break;
9949         }
9950 
9951         // We only want to clamp the cursor to fit within the layout width
9952         // in left-to-right modes, because in a right to left alignment,
9953         // we want to scroll to keep the line-right on the screen, as other
9954         // lines are likely to have text flush with the right margin, which
9955         // we want to keep visible.
9956         // A better long-term solution would probably be to measure both
9957         // the full line and a blank-trimmed version, and, for example, use
9958         // the latter measurement for centering and right alignment, but for
9959         // the time being we only implement the cursor clamping in left to
9960         // right where it is most likely to be annoying.
9961         final boolean clamped = grav > 0;
9962         // FIXME: Is it okay to truncate this, or should we round?
9963         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
9964         final int top = layout.getLineTop(line);
9965         final int bottom = layout.getLineTop(line + 1);
9966 
9967         int left = (int) Math.floor(layout.getLineLeft(line));
9968         int right = (int) Math.ceil(layout.getLineRight(line));
9969         int ht = layout.getHeight();
9970 
9971         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9972         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9973         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
9974             // If cursor has been clamped, make sure we don't scroll.
9975             right = Math.max(x, left + hspace);
9976         }
9977 
9978         int hslack = (bottom - top) / 2;
9979         int vslack = hslack;
9980 
9981         if (vslack > vspace / 4) {
9982             vslack = vspace / 4;
9983         }
9984         if (hslack > hspace / 4) {
9985             hslack = hspace / 4;
9986         }
9987 
9988         int hs = mScrollX;
9989         int vs = mScrollY;
9990 
9991         if (top - vs < vslack) {
9992             vs = top - vslack;
9993         }
9994         if (bottom - vs > vspace - vslack) {
9995             vs = bottom - (vspace - vslack);
9996         }
9997         if (ht - vs < vspace) {
9998             vs = ht - vspace;
9999         }
10000         if (0 - vs > 0) {
10001             vs = 0;
10002         }
10003 
10004         if (grav != 0) {
10005             if (x - hs < hslack) {
10006                 hs = x - hslack;
10007             }
10008             if (x - hs > hspace - hslack) {
10009                 hs = x - (hspace - hslack);
10010             }
10011         }
10012 
10013         if (grav < 0) {
10014             if (left - hs > 0) {
10015                 hs = left;
10016             }
10017             if (right - hs < hspace) {
10018                 hs = right - hspace;
10019             }
10020         } else if (grav > 0) {
10021             if (right - hs < hspace) {
10022                 hs = right - hspace;
10023             }
10024             if (left - hs > 0) {
10025                 hs = left;
10026             }
10027         } else /* grav == 0 */ {
10028             if (right - left <= hspace) {
10029                 /*
10030                  * If the entire text fits, center it exactly.
10031                  */
10032                 hs = left - (hspace - (right - left)) / 2;
10033             } else if (x > right - hslack) {
10034                 /*
10035                  * If we are near the right edge, keep the right edge
10036                  * at the edge of the view.
10037                  */
10038                 hs = right - hspace;
10039             } else if (x < left + hslack) {
10040                 /*
10041                  * If we are near the left edge, keep the left edge
10042                  * at the edge of the view.
10043                  */
10044                 hs = left;
10045             } else if (left > hs) {
10046                 /*
10047                  * Is there whitespace visible at the left?  Fix it if so.
10048                  */
10049                 hs = left;
10050             } else if (right < hs + hspace) {
10051                 /*
10052                  * Is there whitespace visible at the right?  Fix it if so.
10053                  */
10054                 hs = right - hspace;
10055             } else {
10056                 /*
10057                  * Otherwise, float as needed.
10058                  */
10059                 if (x - hs < hslack) {
10060                     hs = x - hslack;
10061                 }
10062                 if (x - hs > hspace - hslack) {
10063                     hs = x - (hspace - hslack);
10064                 }
10065             }
10066         }
10067 
10068         if (hs != mScrollX || vs != mScrollY) {
10069             if (mScroller == null) {
10070                 scrollTo(hs, vs);
10071             } else {
10072                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
10073                 int dx = hs - mScrollX;
10074                 int dy = vs - mScrollY;
10075 
10076                 if (duration > ANIMATED_SCROLL_GAP) {
10077                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
10078                     awakenScrollBars(mScroller.getDuration());
10079                     invalidate();
10080                 } else {
10081                     if (!mScroller.isFinished()) {
10082                         mScroller.abortAnimation();
10083                     }
10084 
10085                     scrollBy(dx, dy);
10086                 }
10087 
10088                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
10089             }
10090 
10091             changed = true;
10092         }
10093 
10094         if (isFocused()) {
10095             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
10096             // requestRectangleOnScreen() is in terms of content coordinates.
10097 
10098             // The offsets here are to ensure the rectangle we are using is
10099             // within our view bounds, in case the cursor is on the far left
10100             // or right.  If it isn't withing the bounds, then this request
10101             // will be ignored.
10102             if (mTempRect == null) mTempRect = new Rect();
10103             mTempRect.set(x - 2, top, x + 2, bottom);
10104             getInterestingRect(mTempRect, line);
10105             mTempRect.offset(mScrollX, mScrollY);
10106 
10107             if (requestRectangleOnScreen(mTempRect)) {
10108                 changed = true;
10109             }
10110         }
10111 
10112         return changed;
10113     }
10114 
10115     /**
10116      * Move the cursor, if needed, so that it is at an offset that is visible
10117      * to the user.  This will not move the cursor if it represents more than
10118      * one character (a selection range).  This will only work if the
10119      * TextView contains spannable text; otherwise it will do nothing.
10120      *
10121      * @return True if the cursor was actually moved, false otherwise.
10122      */
moveCursorToVisibleOffset()10123     public boolean moveCursorToVisibleOffset() {
10124         if (!(mText instanceof Spannable)) {
10125             return false;
10126         }
10127         int start = getSelectionStart();
10128         int end = getSelectionEnd();
10129         if (start != end) {
10130             return false;
10131         }
10132 
10133         // First: make sure the line is visible on screen:
10134 
10135         int line = mLayout.getLineForOffset(start);
10136 
10137         final int top = mLayout.getLineTop(line);
10138         final int bottom = mLayout.getLineTop(line + 1);
10139         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10140         int vslack = (bottom - top) / 2;
10141         if (vslack > vspace / 4) {
10142             vslack = vspace / 4;
10143         }
10144         final int vs = mScrollY;
10145 
10146         if (top < (vs + vslack)) {
10147             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
10148         } else if (bottom > (vspace + vs - vslack)) {
10149             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
10150         }
10151 
10152         // Next: make sure the character is visible on screen:
10153 
10154         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10155         final int hs = mScrollX;
10156         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
10157         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
10158 
10159         // line might contain bidirectional text
10160         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
10161         final int highChar = leftChar > rightChar ? leftChar : rightChar;
10162 
10163         int newStart = start;
10164         if (newStart < lowChar) {
10165             newStart = lowChar;
10166         } else if (newStart > highChar) {
10167             newStart = highChar;
10168         }
10169 
10170         if (newStart != start) {
10171             Selection.setSelection(mSpannable, newStart);
10172             return true;
10173         }
10174 
10175         return false;
10176     }
10177 
10178     @Override
computeScroll()10179     public void computeScroll() {
10180         if (mScroller != null) {
10181             if (mScroller.computeScrollOffset()) {
10182                 mScrollX = mScroller.getCurrX();
10183                 mScrollY = mScroller.getCurrY();
10184                 invalidateParentCaches();
10185                 postInvalidate();  // So we draw again
10186             }
10187         }
10188     }
10189 
getInterestingRect(Rect r, int line)10190     private void getInterestingRect(Rect r, int line) {
10191         convertFromViewportToContentCoordinates(r);
10192 
10193         // Rectangle can can be expanded on first and last line to take
10194         // padding into account.
10195         // TODO Take left/right padding into account too?
10196         if (line == 0) r.top -= getExtendedPaddingTop();
10197         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
10198     }
10199 
convertFromViewportToContentCoordinates(Rect r)10200     private void convertFromViewportToContentCoordinates(Rect r) {
10201         final int horizontalOffset = viewportToContentHorizontalOffset();
10202         r.left += horizontalOffset;
10203         r.right += horizontalOffset;
10204 
10205         final int verticalOffset = viewportToContentVerticalOffset();
10206         r.top += verticalOffset;
10207         r.bottom += verticalOffset;
10208     }
10209 
viewportToContentHorizontalOffset()10210     int viewportToContentHorizontalOffset() {
10211         return getCompoundPaddingLeft() - mScrollX;
10212     }
10213 
10214     @UnsupportedAppUsage
viewportToContentVerticalOffset()10215     int viewportToContentVerticalOffset() {
10216         int offset = getExtendedPaddingTop() - mScrollY;
10217         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
10218             offset += getVerticalOffset(false);
10219         }
10220         return offset;
10221     }
10222 
10223     @Override
debug(int depth)10224     public void debug(int depth) {
10225         super.debug(depth);
10226 
10227         String output = debugIndent(depth);
10228         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
10229                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
10230                 + "} ";
10231 
10232         if (mText != null) {
10233 
10234             output += "mText=\"" + mText + "\" ";
10235             if (mLayout != null) {
10236                 output += "mLayout width=" + mLayout.getWidth()
10237                         + " height=" + mLayout.getHeight();
10238             }
10239         } else {
10240             output += "mText=NULL";
10241         }
10242         Log.d(VIEW_LOG_TAG, output);
10243     }
10244 
10245     /**
10246      * Convenience for {@link Selection#getSelectionStart}.
10247      */
10248     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()10249     public int getSelectionStart() {
10250         return Selection.getSelectionStart(getText());
10251     }
10252 
10253     /**
10254      * Convenience for {@link Selection#getSelectionEnd}.
10255      */
10256     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()10257     public int getSelectionEnd() {
10258         return Selection.getSelectionEnd(getText());
10259     }
10260 
10261     /**
10262      * Return true iff there is a selection of nonzero length inside this text view.
10263      */
hasSelection()10264     public boolean hasSelection() {
10265         final int selectionStart = getSelectionStart();
10266         final int selectionEnd = getSelectionEnd();
10267 
10268         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
10269     }
10270 
getSelectedText()10271     String getSelectedText() {
10272         if (!hasSelection()) {
10273             return null;
10274         }
10275 
10276         final int start = getSelectionStart();
10277         final int end = getSelectionEnd();
10278         return String.valueOf(
10279                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
10280     }
10281 
10282     /**
10283      * Sets the properties of this field (lines, horizontally scrolling,
10284      * transformation method) to be for a single-line input.
10285      *
10286      * @attr ref android.R.styleable#TextView_singleLine
10287      */
setSingleLine()10288     public void setSingleLine() {
10289         setSingleLine(true);
10290     }
10291 
10292     /**
10293      * Sets the properties of this field to transform input to ALL CAPS
10294      * display. This may use a "small caps" formatting if available.
10295      * This setting will be ignored if this field is editable or selectable.
10296      *
10297      * This call replaces the current transformation method. Disabling this
10298      * will not necessarily restore the previous behavior from before this
10299      * was enabled.
10300      *
10301      * @see #setTransformationMethod(TransformationMethod)
10302      * @attr ref android.R.styleable#TextView_textAllCaps
10303      */
10304     @android.view.RemotableViewMethod
setAllCaps(boolean allCaps)10305     public void setAllCaps(boolean allCaps) {
10306         if (allCaps) {
10307             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
10308         } else {
10309             setTransformationMethod(null);
10310         }
10311     }
10312 
10313     /**
10314      *
10315      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
10316      * @return Whether the current transformation method is for ALL CAPS.
10317      *
10318      * @see #setAllCaps(boolean)
10319      * @see #setTransformationMethod(TransformationMethod)
10320      */
10321     @InspectableProperty(name = "textAllCaps")
isAllCaps()10322     public boolean isAllCaps() {
10323         final TransformationMethod method = getTransformationMethod();
10324         return method != null && method instanceof AllCapsTransformationMethod;
10325     }
10326 
10327     /**
10328      * If true, sets the properties of this field (number of lines, horizontally scrolling,
10329      * transformation method) to be for a single-line input; if false, restores these to the default
10330      * conditions.
10331      *
10332      * Note that the default conditions are not necessarily those that were in effect prior this
10333      * method, and you may want to reset these properties to your custom values.
10334      *
10335      * Note that due to performance reasons, by setting single line for the EditText, the maximum
10336      * text length is set to 5000 if no other character limitation are applied.
10337      *
10338      * @attr ref android.R.styleable#TextView_singleLine
10339      */
10340     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)10341     public void setSingleLine(boolean singleLine) {
10342         // Could be used, but may break backward compatibility.
10343         // if (mSingleLine == singleLine) return;
10344         setInputTypeSingleLine(singleLine);
10345         applySingleLine(singleLine, true, true, true);
10346     }
10347 
10348     /**
10349      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
10350      * @param singleLine
10351      */
setInputTypeSingleLine(boolean singleLine)10352     private void setInputTypeSingleLine(boolean singleLine) {
10353         if (mEditor != null
10354                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
10355                         == EditorInfo.TYPE_CLASS_TEXT) {
10356             if (singleLine) {
10357                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10358             } else {
10359                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10360             }
10361         }
10362     }
10363 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)10364     private void applySingleLine(boolean singleLine, boolean applyTransformation,
10365             boolean changeMaxLines, boolean changeMaxLength) {
10366         mSingleLine = singleLine;
10367 
10368         if (singleLine) {
10369             setLines(1);
10370             setHorizontallyScrolling(true);
10371             if (applyTransformation) {
10372                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
10373             }
10374 
10375             if (!changeMaxLength) return;
10376 
10377             // Single line length filter is only applicable editable text.
10378             if (mBufferType != BufferType.EDITABLE) return;
10379 
10380             final InputFilter[] prevFilters = getFilters();
10381             for (InputFilter filter: getFilters()) {
10382                 // We don't add LengthFilter if already there.
10383                 if (filter instanceof InputFilter.LengthFilter) return;
10384             }
10385 
10386             if (mSingleLineLengthFilter == null) {
10387                 mSingleLineLengthFilter = new InputFilter.LengthFilter(
10388                     MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
10389             }
10390 
10391             final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1];
10392             System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length);
10393             newFilters[prevFilters.length] = mSingleLineLengthFilter;
10394 
10395             setFilters(newFilters);
10396 
10397             // Since filter doesn't apply to existing text, trigger filter by setting text.
10398             setText(getText());
10399         } else {
10400             if (changeMaxLines) {
10401                 setMaxLines(Integer.MAX_VALUE);
10402             }
10403             setHorizontallyScrolling(false);
10404             if (applyTransformation) {
10405                 setTransformationMethod(null);
10406             }
10407 
10408             if (!changeMaxLength) return;
10409 
10410             // Single line length filter is only applicable editable text.
10411             if (mBufferType != BufferType.EDITABLE) return;
10412 
10413             final InputFilter[] prevFilters = getFilters();
10414             if (prevFilters.length == 0) return;
10415 
10416             // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated
10417             // single line char limit filter.
10418             if (mSingleLineLengthFilter == null) return;
10419 
10420             // If we need to remove mSingleLineLengthFilter, we need to allocate another array.
10421             // Since filter list is expected to be small and want to avoid unnecessary array
10422             // allocation, check if there is mSingleLengthFilter first.
10423             int targetIndex = -1;
10424             for (int i = 0; i < prevFilters.length; ++i) {
10425                 if (prevFilters[i] == mSingleLineLengthFilter) {
10426                     targetIndex = i;
10427                     break;
10428                 }
10429             }
10430             if (targetIndex == -1) return;  // not found. Do nothing.
10431 
10432             if (prevFilters.length == 1) {
10433                 setFilters(NO_FILTERS);
10434                 return;
10435             }
10436 
10437             // Create new array which doesn't include mSingleLengthFilter.
10438             final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1];
10439             System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex);
10440             System.arraycopy(
10441                     prevFilters,
10442                     targetIndex + 1,
10443                     newFilters,
10444                     targetIndex,
10445                     prevFilters.length - targetIndex - 1);
10446             setFilters(newFilters);
10447             mSingleLineLengthFilter = null;
10448         }
10449     }
10450 
10451     /**
10452      * Causes words in the text that are longer than the view's width
10453      * to be ellipsized instead of broken in the middle.  You may also
10454      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
10455      * to constrain the text to a single line.  Use <code>null</code>
10456      * to turn off ellipsizing.
10457      *
10458      * If {@link #setMaxLines} has been used to set two or more lines,
10459      * only {@link android.text.TextUtils.TruncateAt#END} and
10460      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
10461      * (other ellipsizing types will not do anything).
10462      *
10463      * @attr ref android.R.styleable#TextView_ellipsize
10464      */
setEllipsize(TextUtils.TruncateAt where)10465     public void setEllipsize(TextUtils.TruncateAt where) {
10466         // TruncateAt is an enum. != comparison is ok between these singleton objects.
10467         if (mEllipsize != where) {
10468             mEllipsize = where;
10469 
10470             if (mLayout != null) {
10471                 nullLayouts();
10472                 requestLayout();
10473                 invalidate();
10474             }
10475         }
10476     }
10477 
10478     /**
10479      * Sets how many times to repeat the marquee animation. Only applied if the
10480      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
10481      *
10482      * @see #getMarqueeRepeatLimit()
10483      *
10484      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10485      */
setMarqueeRepeatLimit(int marqueeLimit)10486     public void setMarqueeRepeatLimit(int marqueeLimit) {
10487         mMarqueeRepeatLimit = marqueeLimit;
10488     }
10489 
10490     /**
10491      * Gets the number of times the marquee animation is repeated. Only meaningful if the
10492      * TextView has marquee enabled.
10493      *
10494      * @return the number of times the marquee animation is repeated. -1 if the animation
10495      * repeats indefinitely
10496      *
10497      * @see #setMarqueeRepeatLimit(int)
10498      *
10499      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10500      */
10501     @InspectableProperty
getMarqueeRepeatLimit()10502     public int getMarqueeRepeatLimit() {
10503         return mMarqueeRepeatLimit;
10504     }
10505 
10506     /**
10507      * Returns where, if anywhere, words that are longer than the view
10508      * is wide should be ellipsized.
10509      */
10510     @InspectableProperty
10511     @ViewDebug.ExportedProperty
getEllipsize()10512     public TextUtils.TruncateAt getEllipsize() {
10513         return mEllipsize;
10514     }
10515 
10516     /**
10517      * Set the TextView so that when it takes focus, all the text is
10518      * selected.
10519      *
10520      * @attr ref android.R.styleable#TextView_selectAllOnFocus
10521      */
10522     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)10523     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
10524         createEditorIfNeeded();
10525         mEditor.mSelectAllOnFocus = selectAllOnFocus;
10526 
10527         if (selectAllOnFocus && !(mText instanceof Spannable)) {
10528             setText(mText, BufferType.SPANNABLE);
10529         }
10530     }
10531 
10532     /**
10533      * Set whether the cursor is visible. The default is true. Note that this property only
10534      * makes sense for editable TextView. If IME is consuming the input, the cursor will always be
10535      * invisible, visibility will be updated as the last state when IME does not consume
10536      * the input anymore.
10537      *
10538      * @see #isCursorVisible()
10539      *
10540      * @attr ref android.R.styleable#TextView_cursorVisible
10541      */
10542     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)10543     public void setCursorVisible(boolean visible) {
10544         mCursorVisibleFromAttr = visible;
10545         updateCursorVisibleInternal();
10546     }
10547 
10548     /**
10549      * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput}
10550      * is {@code true}. Otherwise, make the cursor visible.
10551      *
10552      * @param imeConsumesInput {@code true} if IME is consuming the input
10553      *
10554      * @hide
10555      */
setImeConsumesInput(boolean imeConsumesInput)10556     public void setImeConsumesInput(boolean imeConsumesInput) {
10557         mImeIsConsumingInput = imeConsumesInput;
10558         updateCursorVisibleInternal();
10559     }
10560 
updateCursorVisibleInternal()10561     private void updateCursorVisibleInternal()  {
10562         boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput;
10563         if (visible && mEditor == null) return; // visible is the default value with no edit data
10564         createEditorIfNeeded();
10565         if (mEditor.mCursorVisible != visible) {
10566             mEditor.mCursorVisible = visible;
10567             invalidate();
10568 
10569             mEditor.makeBlink();
10570 
10571             // InsertionPointCursorController depends on mCursorVisible
10572             mEditor.prepareCursorControllers();
10573         }
10574     }
10575 
10576     /**
10577      * @return whether or not the cursor is visible (assuming this TextView is editable). This
10578      * method may return {@code false} when the IME is consuming the input even if the
10579      * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)}
10580      * is called.
10581      *
10582      * @see #setCursorVisible(boolean)
10583      *
10584      * @attr ref android.R.styleable#TextView_cursorVisible
10585      */
10586     @InspectableProperty
isCursorVisible()10587     public boolean isCursorVisible() {
10588         // true is the default value
10589         return mEditor == null ? true : mEditor.mCursorVisible;
10590     }
10591 
10592     /**
10593      * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}.
10594      * {@code true} is the default value.
10595      *
10596      * @see #setCursorVisible(boolean)
10597      * @hide
10598      */
isCursorVisibleFromAttr()10599     public boolean isCursorVisibleFromAttr() {
10600         return mCursorVisibleFromAttr;
10601     }
10602 
canMarquee()10603     private boolean canMarquee() {
10604         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10605         return width > 0 && (mLayout.getLineWidth(0) > width
10606                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
10607                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
10608     }
10609 
10610     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()10611     private void startMarquee() {
10612         // Do not ellipsize EditText
10613         if (getKeyListener() != null) return;
10614 
10615         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
10616             return;
10617         }
10618 
10619         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
10620                 && getLineCount() == 1 && canMarquee()) {
10621 
10622             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10623                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
10624                 final Layout tmp = mLayout;
10625                 mLayout = mSavedMarqueeModeLayout;
10626                 mSavedMarqueeModeLayout = tmp;
10627                 setHorizontalFadingEdgeEnabled(true);
10628                 requestLayout();
10629                 invalidate();
10630             }
10631 
10632             if (mMarquee == null) mMarquee = new Marquee(this);
10633             mMarquee.start(mMarqueeRepeatLimit);
10634         }
10635     }
10636 
stopMarquee()10637     private void stopMarquee() {
10638         if (mMarquee != null && !mMarquee.isStopped()) {
10639             mMarquee.stop();
10640         }
10641 
10642         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
10643             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10644             final Layout tmp = mSavedMarqueeModeLayout;
10645             mSavedMarqueeModeLayout = mLayout;
10646             mLayout = tmp;
10647             setHorizontalFadingEdgeEnabled(false);
10648             requestLayout();
10649             invalidate();
10650         }
10651     }
10652 
10653     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)10654     private void startStopMarquee(boolean start) {
10655         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10656             if (start) {
10657                 startMarquee();
10658             } else {
10659                 stopMarquee();
10660             }
10661         }
10662     }
10663 
10664     /**
10665      * This method is called when the text is changed, in case any subclasses
10666      * would like to know.
10667      *
10668      * Within <code>text</code>, the <code>lengthAfter</code> characters
10669      * beginning at <code>start</code> have just replaced old text that had
10670      * length <code>lengthBefore</code>. It is an error to attempt to make
10671      * changes to <code>text</code> from this callback.
10672      *
10673      * @param text The text the TextView is displaying
10674      * @param start The offset of the start of the range of the text that was
10675      * modified
10676      * @param lengthBefore The length of the former text that has been replaced
10677      * @param lengthAfter The length of the replacement modified text
10678      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10679     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
10680         // intentionally empty, template pattern method can be overridden by subclasses
10681     }
10682 
10683     /**
10684      * This method is called when the selection has changed, in case any
10685      * subclasses would like to know.
10686      * </p>
10687      * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs
10688      * the accessibility subsystem about the selection change.
10689      * </p>
10690      *
10691      * @param selStart The new selection start location.
10692      * @param selEnd The new selection end location.
10693      */
10694     @CallSuper
onSelectionChanged(int selStart, int selEnd)10695     protected void onSelectionChanged(int selStart, int selEnd) {
10696         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
10697     }
10698 
10699     /**
10700      * Adds a TextWatcher to the list of those whose methods are called
10701      * whenever this TextView's text changes.
10702      * <p>
10703      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
10704      * not called after {@link #setText} calls.  Now, doing {@link #setText}
10705      * if there are any text changed listeners forces the buffer type to
10706      * Editable if it would not otherwise be and does call this method.
10707      */
addTextChangedListener(TextWatcher watcher)10708     public void addTextChangedListener(TextWatcher watcher) {
10709         if (mListeners == null) {
10710             mListeners = new ArrayList<TextWatcher>();
10711         }
10712 
10713         mListeners.add(watcher);
10714     }
10715 
10716     /**
10717      * Removes the specified TextWatcher from the list of those whose
10718      * methods are called
10719      * whenever this TextView's text changes.
10720      */
removeTextChangedListener(TextWatcher watcher)10721     public void removeTextChangedListener(TextWatcher watcher) {
10722         if (mListeners != null) {
10723             int i = mListeners.indexOf(watcher);
10724 
10725             if (i >= 0) {
10726                 mListeners.remove(i);
10727             }
10728         }
10729     }
10730 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)10731     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
10732         if (mListeners != null) {
10733             final ArrayList<TextWatcher> list = mListeners;
10734             final int count = list.size();
10735             for (int i = 0; i < count; i++) {
10736                 list.get(i).beforeTextChanged(text, start, before, after);
10737             }
10738         }
10739 
10740         // The spans that are inside or intersect the modified region no longer make sense
10741         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
10742         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
10743     }
10744 
10745     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10746     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
10747         if (!(mText instanceof Editable)) return;
10748         Editable text = (Editable) mText;
10749 
10750         T[] spans = text.getSpans(start, end, type);
10751         ArrayList<T> spansToRemove = new ArrayList<>();
10752         for (T span : spans) {
10753             final int spanStart = text.getSpanStart(span);
10754             final int spanEnd = text.getSpanEnd(span);
10755             if (spanEnd == start || spanStart == end) continue;
10756             spansToRemove.add(span);
10757         }
10758         for (T span : spansToRemove) {
10759             text.removeSpan(span);
10760         }
10761     }
10762 
removeAdjacentSuggestionSpans(final int pos)10763     void removeAdjacentSuggestionSpans(final int pos) {
10764         if (!(mText instanceof Editable)) return;
10765         final Editable text = (Editable) mText;
10766 
10767         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
10768         final int length = spans.length;
10769         for (int i = 0; i < length; i++) {
10770             final int spanStart = text.getSpanStart(spans[i]);
10771             final int spanEnd = text.getSpanEnd(spans[i]);
10772             if (spanEnd == pos || spanStart == pos) {
10773                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
10774                     text.removeSpan(spans[i]);
10775                 }
10776             }
10777         }
10778     }
10779 
10780     /**
10781      * Not private so it can be called from an inner class without going
10782      * through a thunk.
10783      */
sendOnTextChanged(CharSequence text, int start, int before, int after)10784     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
10785         if (mListeners != null) {
10786             final ArrayList<TextWatcher> list = mListeners;
10787             final int count = list.size();
10788             for (int i = 0; i < count; i++) {
10789                 list.get(i).onTextChanged(text, start, before, after);
10790             }
10791         }
10792 
10793         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
10794     }
10795 
10796     /**
10797      * Not private so it can be called from an inner class without going
10798      * through a thunk.
10799      */
sendAfterTextChanged(Editable text)10800     void sendAfterTextChanged(Editable text) {
10801         if (mListeners != null) {
10802             final ArrayList<TextWatcher> list = mListeners;
10803             final int count = list.size();
10804             for (int i = 0; i < count; i++) {
10805                 list.get(i).afterTextChanged(text);
10806             }
10807         }
10808 
10809         notifyListeningManagersAfterTextChanged();
10810 
10811         hideErrorIfUnchanged();
10812     }
10813 
10814     /**
10815      * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
10816      * interested on text changes.
10817      */
notifyListeningManagersAfterTextChanged()10818     private void notifyListeningManagersAfterTextChanged() {
10819 
10820         // Autofill
10821         if (isAutofillable()) {
10822             // It is important to not check whether the view is important for autofill
10823             // since the user can trigger autofill manually on not important views.
10824             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10825             if (afm != null) {
10826                 if (android.view.autofill.Helper.sVerbose) {
10827                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
10828                 }
10829                 afm.notifyValueChanged(TextView.this);
10830             }
10831         }
10832 
10833         notifyContentCaptureTextChanged();
10834     }
10835 
10836     /**
10837      * Notifies the ContentCapture service that the text of the view has changed (only if
10838      * ContentCapture has been notified of this view's existence already).
10839      *
10840      * @hide
10841      */
notifyContentCaptureTextChanged()10842     public void notifyContentCaptureTextChanged() {
10843         // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
10844         // of using isLaidout(), so it's not called in cases where it's laid out but a
10845         // notifyAppeared was not sent.
10846         if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) {
10847             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
10848             if (cm != null && cm.isContentCaptureEnabled()) {
10849                 final ContentCaptureSession session = getContentCaptureSession();
10850                 if (session != null) {
10851                     // TODO(b/111276913): pass flags when edited by user / add CTS test
10852                     session.notifyViewTextChanged(getAutofillId(), getText());
10853                 }
10854             }
10855         }
10856     }
10857 
isAutofillable()10858     private boolean isAutofillable() {
10859         // It is important to not check whether the view is important for autofill
10860         // since the user can trigger autofill manually on not important views.
10861         return getAutofillType() != AUTOFILL_TYPE_NONE;
10862     }
10863 
updateAfterEdit()10864     void updateAfterEdit() {
10865         invalidate();
10866         int curs = getSelectionStart();
10867 
10868         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
10869             registerForPreDraw();
10870         }
10871 
10872         checkForResize();
10873 
10874         if (curs >= 0) {
10875             mHighlightPathBogus = true;
10876             if (mEditor != null) mEditor.makeBlink();
10877             bringPointIntoView(curs);
10878         }
10879     }
10880 
10881     /**
10882      * Not private so it can be called from an inner class without going
10883      * through a thunk.
10884      */
handleTextChanged(CharSequence buffer, int start, int before, int after)10885     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
10886         sLastCutCopyOrTextChangedTime = 0;
10887 
10888         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10889         if (ims == null || ims.mBatchEditNesting == 0) {
10890             updateAfterEdit();
10891         }
10892         if (ims != null) {
10893             ims.mContentChanged = true;
10894             if (ims.mChangedStart < 0) {
10895                 ims.mChangedStart = start;
10896                 ims.mChangedEnd = start + before;
10897             } else {
10898                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
10899                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
10900             }
10901             ims.mChangedDelta += after - before;
10902         }
10903         resetErrorChangedFlag();
10904         sendOnTextChanged(buffer, start, before, after);
10905         onTextChanged(buffer, start, before, after);
10906     }
10907 
10908     /**
10909      * Not private so it can be called from an inner class without going
10910      * through a thunk.
10911      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10912     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
10913         // XXX Make the start and end move together if this ends up
10914         // spending too much time invalidating.
10915 
10916         boolean selChanged = false;
10917         int newSelStart = -1, newSelEnd = -1;
10918 
10919         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10920 
10921         if (what == Selection.SELECTION_END) {
10922             selChanged = true;
10923             newSelEnd = newStart;
10924 
10925             if (oldStart >= 0 || newStart >= 0) {
10926                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
10927                 checkForResize();
10928                 registerForPreDraw();
10929                 if (mEditor != null) mEditor.makeBlink();
10930             }
10931         }
10932 
10933         if (what == Selection.SELECTION_START) {
10934             selChanged = true;
10935             newSelStart = newStart;
10936 
10937             if (oldStart >= 0 || newStart >= 0) {
10938                 int end = Selection.getSelectionEnd(buf);
10939                 invalidateCursor(end, oldStart, newStart);
10940             }
10941         }
10942 
10943         if (selChanged) {
10944             mHighlightPathBogus = true;
10945             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
10946 
10947             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
10948                 if (newSelStart < 0) {
10949                     newSelStart = Selection.getSelectionStart(buf);
10950                 }
10951                 if (newSelEnd < 0) {
10952                     newSelEnd = Selection.getSelectionEnd(buf);
10953                 }
10954 
10955                 if (mEditor != null) {
10956                     mEditor.refreshTextActionMode();
10957                     if (!hasSelection()
10958                             && mEditor.getTextActionMode() == null && hasTransientState()) {
10959                         // User generated selection has been removed.
10960                         setHasTransientState(false);
10961                     }
10962                 }
10963                 onSelectionChanged(newSelStart, newSelEnd);
10964             }
10965         }
10966 
10967         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
10968                 || what instanceof CharacterStyle) {
10969             if (ims == null || ims.mBatchEditNesting == 0) {
10970                 invalidate();
10971                 mHighlightPathBogus = true;
10972                 checkForResize();
10973             } else {
10974                 ims.mContentChanged = true;
10975             }
10976             if (mEditor != null) {
10977                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
10978                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
10979                 mEditor.invalidateHandlesAndActionMode();
10980             }
10981         }
10982 
10983         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
10984             mHighlightPathBogus = true;
10985             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
10986                 ims.mSelectionModeChanged = true;
10987             }
10988 
10989             if (Selection.getSelectionStart(buf) >= 0) {
10990                 if (ims == null || ims.mBatchEditNesting == 0) {
10991                     invalidateCursor();
10992                 } else {
10993                     ims.mCursorChanged = true;
10994                 }
10995             }
10996         }
10997 
10998         if (what instanceof ParcelableSpan) {
10999             // If this is a span that can be sent to a remote process,
11000             // the current extract editor would be interested in it.
11001             if (ims != null && ims.mExtractedTextRequest != null) {
11002                 if (ims.mBatchEditNesting != 0) {
11003                     if (oldStart >= 0) {
11004                         if (ims.mChangedStart > oldStart) {
11005                             ims.mChangedStart = oldStart;
11006                         }
11007                         if (ims.mChangedStart > oldEnd) {
11008                             ims.mChangedStart = oldEnd;
11009                         }
11010                     }
11011                     if (newStart >= 0) {
11012                         if (ims.mChangedStart > newStart) {
11013                             ims.mChangedStart = newStart;
11014                         }
11015                         if (ims.mChangedStart > newEnd) {
11016                             ims.mChangedStart = newEnd;
11017                         }
11018                     }
11019                 } else {
11020                     if (DEBUG_EXTRACT) {
11021                         Log.v(LOG_TAG, "Span change outside of batch: "
11022                                 + oldStart + "-" + oldEnd + ","
11023                                 + newStart + "-" + newEnd + " " + what);
11024                     }
11025                     ims.mContentChanged = true;
11026                 }
11027             }
11028         }
11029 
11030         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
11031                 && what instanceof SpellCheckSpan) {
11032             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
11033         }
11034     }
11035 
11036     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)11037     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
11038         if (isTemporarilyDetached()) {
11039             // If we are temporarily in the detach state, then do nothing.
11040             super.onFocusChanged(focused, direction, previouslyFocusedRect);
11041             return;
11042         }
11043 
11044         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
11045 
11046         if (focused) {
11047             if (mSpannable != null) {
11048                 MetaKeyKeyListener.resetMetaState(mSpannable);
11049             }
11050         }
11051 
11052         startStopMarquee(focused);
11053 
11054         if (mTransformation != null) {
11055             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
11056         }
11057 
11058         super.onFocusChanged(focused, direction, previouslyFocusedRect);
11059     }
11060 
11061     @Override
onWindowFocusChanged(boolean hasWindowFocus)11062     public void onWindowFocusChanged(boolean hasWindowFocus) {
11063         super.onWindowFocusChanged(hasWindowFocus);
11064 
11065         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
11066 
11067         startStopMarquee(hasWindowFocus);
11068     }
11069 
11070     @Override
onVisibilityChanged(View changedView, int visibility)11071     protected void onVisibilityChanged(View changedView, int visibility) {
11072         super.onVisibilityChanged(changedView, visibility);
11073         if (mEditor != null && visibility != VISIBLE) {
11074             mEditor.hideCursorAndSpanControllers();
11075             stopTextActionMode();
11076         }
11077     }
11078 
11079     /**
11080      * Use {@link BaseInputConnection#removeComposingSpans
11081      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
11082      * state from this text view.
11083      */
clearComposingText()11084     public void clearComposingText() {
11085         if (mText instanceof Spannable) {
11086             BaseInputConnection.removeComposingSpans(mSpannable);
11087         }
11088     }
11089 
11090     @Override
setSelected(boolean selected)11091     public void setSelected(boolean selected) {
11092         boolean wasSelected = isSelected();
11093 
11094         super.setSelected(selected);
11095 
11096         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
11097             if (selected) {
11098                 startMarquee();
11099             } else {
11100                 stopMarquee();
11101             }
11102         }
11103     }
11104 
11105     /**
11106      * Called from onTouchEvent() to prevent the touches by secondary fingers.
11107      * Dragging on handles can revise cursor/selection, so can dragging on the text view.
11108      * This method is a lock to avoid processing multiple fingers on both text view and handles.
11109      * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work.
11110      *
11111      * @param event The motion event that is being handled and carries the pointer info.
11112      * @param fromHandleView true if the event is delivered to selection handle or insertion
11113      * handle; false if this event is delivered to TextView.
11114      * @return Returns true to indicate that onTouchEvent() can continue processing the motion
11115      * event, otherwise false.
11116      *  - Always returns true for the first finger.
11117      *  - For secondary fingers, if the first or current finger is from TextView, returns false.
11118      *    This is to make touch mutually exclusive between the TextView and the handles, but
11119      *    not among the handles.
11120      */
isFromPrimePointer(MotionEvent event, boolean fromHandleView)11121     boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) {
11122         boolean res = true;
11123         if (mPrimePointerId == NO_POINTER_ID)  {
11124             mPrimePointerId = event.getPointerId(0);
11125             mIsPrimePointerFromHandleView = fromHandleView;
11126         } else if (mPrimePointerId != event.getPointerId(0)) {
11127             res = mIsPrimePointerFromHandleView && fromHandleView;
11128         }
11129         if (event.getActionMasked() == MotionEvent.ACTION_UP
11130             || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
11131             mPrimePointerId = -1;
11132         }
11133         return res;
11134     }
11135 
11136     @Override
onTouchEvent(MotionEvent event)11137     public boolean onTouchEvent(MotionEvent event) {
11138         if (DEBUG_CURSOR) {
11139             logCursor("onTouchEvent", "%d: %s (%f,%f)",
11140                     event.getSequenceNumber(),
11141                     MotionEvent.actionToString(event.getActionMasked()),
11142                     event.getX(), event.getY());
11143         }
11144         final int action = event.getActionMasked();
11145         if (mEditor != null) {
11146             if (!isFromPrimePointer(event, false)) {
11147                 return true;
11148             }
11149 
11150             mEditor.onTouchEvent(event);
11151 
11152             if (mEditor.mInsertionPointCursorController != null
11153                     && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
11154                 return true;
11155             }
11156             if (mEditor.mSelectionModifierCursorController != null
11157                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
11158                 return true;
11159             }
11160         }
11161 
11162         final boolean superResult = super.onTouchEvent(event);
11163         if (DEBUG_CURSOR) {
11164             logCursor("onTouchEvent", "superResult=%s", superResult);
11165         }
11166 
11167         /*
11168          * Don't handle the release after a long press, because it will move the selection away from
11169          * whatever the menu action was trying to affect. If the long press should have triggered an
11170          * insertion action mode, we can now actually show it.
11171          */
11172         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
11173             mEditor.mDiscardNextActionUp = false;
11174             if (DEBUG_CURSOR) {
11175                 logCursor("onTouchEvent", "release after long press detected");
11176             }
11177             if (mEditor.mIsInsertionActionModeStartPending) {
11178                 mEditor.startInsertionActionMode();
11179                 mEditor.mIsInsertionActionModeStartPending = false;
11180             }
11181             return superResult;
11182         }
11183 
11184         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
11185                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
11186 
11187         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
11188                 && mText instanceof Spannable && mLayout != null) {
11189             boolean handled = false;
11190 
11191             if (mMovement != null) {
11192                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
11193             }
11194 
11195             final boolean textIsSelectable = isTextSelectable();
11196             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
11197                 // The LinkMovementMethod which should handle taps on links has not been installed
11198                 // on non editable text that support text selection.
11199                 // We reproduce its behavior here to open links for these.
11200                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
11201                     getSelectionEnd(), ClickableSpan.class);
11202 
11203                 if (links.length > 0) {
11204                     links[0].onClick(this);
11205                     handled = true;
11206                 }
11207             }
11208 
11209             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
11210                 // Show the IME, except when selecting in read-only text.
11211                 final InputMethodManager imm = getInputMethodManager();
11212                 viewClicked(imm);
11213                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
11214                     imm.showSoftInput(this, 0);
11215                 }
11216 
11217                 // The above condition ensures that the mEditor is not null
11218                 mEditor.onTouchUpEvent(event);
11219 
11220                 handled = true;
11221             }
11222 
11223             if (handled) {
11224                 return true;
11225             }
11226         }
11227 
11228         return superResult;
11229     }
11230 
11231     @Override
onGenericMotionEvent(MotionEvent event)11232     public boolean onGenericMotionEvent(MotionEvent event) {
11233         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
11234             try {
11235                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
11236                     return true;
11237                 }
11238             } catch (AbstractMethodError ex) {
11239                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
11240                 // Ignore its absence in case third party applications implemented the
11241                 // interface directly.
11242             }
11243         }
11244         return super.onGenericMotionEvent(event);
11245     }
11246 
11247     @Override
onCreateContextMenu(ContextMenu menu)11248     protected void onCreateContextMenu(ContextMenu menu) {
11249         if (mEditor != null) {
11250             mEditor.onCreateContextMenu(menu);
11251         }
11252     }
11253 
11254     @Override
showContextMenu()11255     public boolean showContextMenu() {
11256         if (mEditor != null) {
11257             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
11258         }
11259         return super.showContextMenu();
11260     }
11261 
11262     @Override
showContextMenu(float x, float y)11263     public boolean showContextMenu(float x, float y) {
11264         if (mEditor != null) {
11265             mEditor.setContextMenuAnchor(x, y);
11266         }
11267         return super.showContextMenu(x, y);
11268     }
11269 
11270     /**
11271      * @return True iff this TextView contains a text that can be edited, or if this is
11272      * a selectable TextView.
11273      */
11274     @UnsupportedAppUsage
isTextEditable()11275     boolean isTextEditable() {
11276         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
11277     }
11278 
11279     /**
11280      * Returns true, only while processing a touch gesture, if the initial
11281      * touch down event caused focus to move to the text view and as a result
11282      * its selection changed.  Only valid while processing the touch gesture
11283      * of interest, in an editable text view.
11284      */
didTouchFocusSelect()11285     public boolean didTouchFocusSelect() {
11286         return mEditor != null && mEditor.mTouchFocusSelected;
11287     }
11288 
11289     @Override
cancelLongPress()11290     public void cancelLongPress() {
11291         super.cancelLongPress();
11292         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
11293     }
11294 
11295     @Override
onTrackballEvent(MotionEvent event)11296     public boolean onTrackballEvent(MotionEvent event) {
11297         if (mMovement != null && mSpannable != null && mLayout != null) {
11298             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
11299                 return true;
11300             }
11301         }
11302 
11303         return super.onTrackballEvent(event);
11304     }
11305 
11306     /**
11307      * Sets the Scroller used for producing a scrolling animation
11308      *
11309      * @param s A Scroller instance
11310      */
setScroller(Scroller s)11311     public void setScroller(Scroller s) {
11312         mScroller = s;
11313     }
11314 
11315     @Override
getLeftFadingEdgeStrength()11316     protected float getLeftFadingEdgeStrength() {
11317         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11318             final Marquee marquee = mMarquee;
11319             if (marquee.shouldDrawLeftFade()) {
11320                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
11321             } else {
11322                 return 0.0f;
11323             }
11324         } else if (getLineCount() == 1) {
11325             final float lineLeft = getLayout().getLineLeft(0);
11326             if (lineLeft > mScrollX) return 0.0f;
11327             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
11328         }
11329         return super.getLeftFadingEdgeStrength();
11330     }
11331 
11332     @Override
getRightFadingEdgeStrength()11333     protected float getRightFadingEdgeStrength() {
11334         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11335             final Marquee marquee = mMarquee;
11336             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
11337         } else if (getLineCount() == 1) {
11338             final float rightEdge = mScrollX +
11339                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
11340             final float lineRight = getLayout().getLineRight(0);
11341             if (lineRight < rightEdge) return 0.0f;
11342             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
11343         }
11344         return super.getRightFadingEdgeStrength();
11345     }
11346 
11347     /**
11348      * Calculates the fading edge strength as the ratio of the distance between two
11349      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
11350      * value for the distance calculation.
11351      *
11352      * @param position1 A horizontal position.
11353      * @param position2 A horizontal position.
11354      * @return Fading edge strength between [0.0f, 1.0f].
11355      */
11356     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)11357     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
11358         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
11359         if (horizontalFadingEdgeLength == 0) return 0.0f;
11360         final float diff = Math.abs(position1 - position2);
11361         if (diff > horizontalFadingEdgeLength) return 1.0f;
11362         return diff / horizontalFadingEdgeLength;
11363     }
11364 
isMarqueeFadeEnabled()11365     private boolean isMarqueeFadeEnabled() {
11366         return mEllipsize == TextUtils.TruncateAt.MARQUEE
11367                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
11368     }
11369 
11370     @Override
computeHorizontalScrollRange()11371     protected int computeHorizontalScrollRange() {
11372         if (mLayout != null) {
11373             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
11374                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
11375         }
11376 
11377         return super.computeHorizontalScrollRange();
11378     }
11379 
11380     @Override
computeVerticalScrollRange()11381     protected int computeVerticalScrollRange() {
11382         if (mLayout != null) {
11383             return mLayout.getHeight();
11384         }
11385         return super.computeVerticalScrollRange();
11386     }
11387 
11388     @Override
computeVerticalScrollExtent()11389     protected int computeVerticalScrollExtent() {
11390         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
11391     }
11392 
11393     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11394     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
11395         super.findViewsWithText(outViews, searched, flags);
11396         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
11397                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
11398             String searchedLowerCase = searched.toString().toLowerCase();
11399             String textLowerCase = mText.toString().toLowerCase();
11400             if (textLowerCase.contains(searchedLowerCase)) {
11401                 outViews.add(this);
11402             }
11403         }
11404     }
11405 
11406     /**
11407      * Type of the text buffer that defines the characteristics of the text such as static,
11408      * styleable, or editable.
11409      */
11410     public enum BufferType {
11411         NORMAL, SPANNABLE, EDITABLE
11412     }
11413 
11414     /**
11415      * Returns the TextView_textColor attribute from the TypedArray, if set, or
11416      * the TextAppearance_textColor from the TextView_textAppearance attribute,
11417      * if TextView_textColor was not set directly.
11418      *
11419      * @removed
11420      */
getTextColors(Context context, TypedArray attrs)11421     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
11422         if (attrs == null) {
11423             // Preserve behavior prior to removal of this API.
11424             throw new NullPointerException();
11425         }
11426 
11427         // It's not safe to use this method from apps. The parameter 'attrs'
11428         // must have been obtained using the TextView filter array which is not
11429         // available to the SDK. As such, we grab a default TypedArray with the
11430         // right filter instead here.
11431         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
11432         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
11433         if (colors == null) {
11434             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
11435             if (ap != 0) {
11436                 final TypedArray appearance = context.obtainStyledAttributes(
11437                         ap, R.styleable.TextAppearance);
11438                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
11439                 appearance.recycle();
11440             }
11441         }
11442         a.recycle();
11443 
11444         return colors;
11445     }
11446 
11447     /**
11448      * Returns the default color from the TextView_textColor attribute from the
11449      * AttributeSet, if set, or the default color from the
11450      * TextAppearance_textColor from the TextView_textAppearance attribute, if
11451      * TextView_textColor was not set directly.
11452      *
11453      * @removed
11454      */
getTextColor(Context context, TypedArray attrs, int def)11455     public static int getTextColor(Context context, TypedArray attrs, int def) {
11456         final ColorStateList colors = getTextColors(context, attrs);
11457         if (colors == null) {
11458             return def;
11459         } else {
11460             return colors.getDefaultColor();
11461         }
11462     }
11463 
11464     @Override
onKeyShortcut(int keyCode, KeyEvent event)11465     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
11466         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
11467             // Handle Ctrl-only shortcuts.
11468             switch (keyCode) {
11469                 case KeyEvent.KEYCODE_A:
11470                     if (canSelectText()) {
11471                         return onTextContextMenuItem(ID_SELECT_ALL);
11472                     }
11473                     break;
11474                 case KeyEvent.KEYCODE_Z:
11475                     if (canUndo()) {
11476                         return onTextContextMenuItem(ID_UNDO);
11477                     }
11478                     break;
11479                 case KeyEvent.KEYCODE_X:
11480                     if (canCut()) {
11481                         return onTextContextMenuItem(ID_CUT);
11482                     }
11483                     break;
11484                 case KeyEvent.KEYCODE_C:
11485                     if (canCopy()) {
11486                         return onTextContextMenuItem(ID_COPY);
11487                     }
11488                     break;
11489                 case KeyEvent.KEYCODE_V:
11490                     if (canPaste()) {
11491                         return onTextContextMenuItem(ID_PASTE);
11492                     }
11493                     break;
11494             }
11495         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
11496             // Handle Ctrl-Shift shortcuts.
11497             switch (keyCode) {
11498                 case KeyEvent.KEYCODE_Z:
11499                     if (canRedo()) {
11500                         return onTextContextMenuItem(ID_REDO);
11501                     }
11502                     break;
11503                 case KeyEvent.KEYCODE_V:
11504                     if (canPaste()) {
11505                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
11506                     }
11507             }
11508         }
11509         return super.onKeyShortcut(keyCode, event);
11510     }
11511 
11512     /**
11513      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
11514      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
11515      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
11516      * sufficient.
11517      */
canSelectText()11518     boolean canSelectText() {
11519         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
11520     }
11521 
11522     /**
11523      * Test based on the <i>intrinsic</i> charateristics of the TextView.
11524      * The text must be spannable and the movement method must allow for arbitary selection.
11525      *
11526      * See also {@link #canSelectText()}.
11527      */
textCanBeSelected()11528     boolean textCanBeSelected() {
11529         // prepareCursorController() relies on this method.
11530         // If you change this condition, make sure prepareCursorController is called anywhere
11531         // the value of this condition might be changed.
11532         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
11533         return isTextEditable()
11534                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
11535     }
11536 
11537     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)11538     private Locale getTextServicesLocale(boolean allowNullLocale) {
11539         // Start fetching the text services locale asynchronously.
11540         updateTextServicesLocaleAsync();
11541         // If !allowNullLocale and there is no cached text services locale, just return the default
11542         // locale.
11543         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
11544                 : mCurrentSpellCheckerLocaleCache;
11545     }
11546 
11547     /**
11548      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
11549      * this {@link TextView}.
11550      *
11551      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
11552      * other apps may need to set this so that the system can user right user's resources and
11553      * services such as input methods and spell checkers.</p>
11554      *
11555      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
11556      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
11557      * @hide
11558      */
11559     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)11560     public final void setTextOperationUser(@Nullable UserHandle user) {
11561         if (Objects.equals(mTextOperationUser, user)) {
11562             return;
11563         }
11564         if (user != null && !Process.myUserHandle().equals(user)) {
11565             // Just for preventing people from accidentally using this hidden API without
11566             // the required permission.  The same permission is also checked in the system server.
11567             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
11568                     != PackageManager.PERMISSION_GRANTED) {
11569                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
11570                         + " userId=" + user.getIdentifier()
11571                         + " callingUserId" + UserHandle.myUserId());
11572             }
11573         }
11574         mTextOperationUser = user;
11575         // Invalidate some resources
11576         mCurrentSpellCheckerLocaleCache = null;
11577         if (mEditor != null) {
11578             mEditor.onTextOperationUserChanged();
11579         }
11580     }
11581 
11582     @Nullable
getTextServicesManagerForUser()11583     final TextServicesManager getTextServicesManagerForUser() {
11584         return getServiceManagerForUser("android", TextServicesManager.class);
11585     }
11586 
11587     @Nullable
getClipboardManagerForUser()11588     final ClipboardManager getClipboardManagerForUser() {
11589         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
11590     }
11591 
11592     @Nullable
getTextClassificationManagerForUser()11593     final TextClassificationManager getTextClassificationManagerForUser() {
11594         return getServiceManagerForUser(
11595                 getContext().getPackageName(), TextClassificationManager.class);
11596     }
11597 
11598     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)11599     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
11600         if (mTextOperationUser == null) {
11601             return getContext().getSystemService(managerClazz);
11602         }
11603         try {
11604             Context context = getContext().createPackageContextAsUser(
11605                     packageName, 0 /* flags */, mTextOperationUser);
11606             return context.getSystemService(managerClazz);
11607         } catch (PackageManager.NameNotFoundException e) {
11608             return null;
11609         }
11610     }
11611 
11612     /**
11613      * Starts {@link Activity} as a text-operation user if it is specified with
11614      * {@link #setTextOperationUser(UserHandle)}.
11615      *
11616      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
11617      *
11618      * @param intent The description of the activity to start.
11619      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11620     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
11621         if (mTextOperationUser != null) {
11622             getContext().startActivityAsUser(intent, mTextOperationUser);
11623         } else {
11624             getContext().startActivity(intent);
11625         }
11626     }
11627 
11628     /**
11629      * This is a temporary method. Future versions may support multi-locale text.
11630      * Caveat: This method may not return the latest text services locale, but this should be
11631      * acceptable and it's more important to make this method asynchronous.
11632      *
11633      * @return The locale that should be used for a word iterator
11634      * in this TextView, based on the current spell checker settings,
11635      * the current IME's locale, or the system default locale.
11636      * Please note that a word iterator in this TextView is different from another word iterator
11637      * used by SpellChecker.java of TextView. This method should be used for the former.
11638      * @hide
11639      */
11640     // TODO: Support multi-locale
11641     // TODO: Update the text services locale immediately after the keyboard locale is switched
11642     // by catching intent of keyboard switch event
getTextServicesLocale()11643     public Locale getTextServicesLocale() {
11644         return getTextServicesLocale(false /* allowNullLocale */);
11645     }
11646 
11647     /**
11648      * @return {@code true} if this TextView is specialized for showing and interacting with the
11649      * extracted text in a full-screen input method.
11650      * @hide
11651      */
isInExtractedMode()11652     public boolean isInExtractedMode() {
11653         return false;
11654     }
11655 
11656     /**
11657      * @return {@code true} if this widget supports auto-sizing text and has been configured to
11658      * auto-size.
11659      */
isAutoSizeEnabled()11660     private boolean isAutoSizeEnabled() {
11661         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
11662     }
11663 
11664     /**
11665      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
11666      * @hide
11667      */
supportsAutoSizeText()11668     protected boolean supportsAutoSizeText() {
11669         return true;
11670     }
11671 
11672     /**
11673      * This is a temporary method. Future versions may support multi-locale text.
11674      * Caveat: This method may not return the latest spell checker locale, but this should be
11675      * acceptable and it's more important to make this method asynchronous.
11676      *
11677      * @return The locale that should be used for a spell checker in this TextView,
11678      * based on the current spell checker settings, the current IME's locale, or the system default
11679      * locale.
11680      * @hide
11681      */
getSpellCheckerLocale()11682     public Locale getSpellCheckerLocale() {
11683         return getTextServicesLocale(true /* allowNullLocale */);
11684     }
11685 
updateTextServicesLocaleAsync()11686     private void updateTextServicesLocaleAsync() {
11687         // AsyncTask.execute() uses a serial executor which means we don't have
11688         // to lock around updateTextServicesLocaleLocked() to prevent it from
11689         // being executed n times in parallel.
11690         AsyncTask.execute(new Runnable() {
11691             @Override
11692             public void run() {
11693                 updateTextServicesLocaleLocked();
11694             }
11695         });
11696     }
11697 
11698     @UnsupportedAppUsage
updateTextServicesLocaleLocked()11699     private void updateTextServicesLocaleLocked() {
11700         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
11701         if (textServicesManager == null) {
11702             return;
11703         }
11704         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
11705         final Locale locale;
11706         if (subtype != null) {
11707             locale = subtype.getLocaleObject();
11708         } else {
11709             locale = null;
11710         }
11711         mCurrentSpellCheckerLocaleCache = locale;
11712     }
11713 
onLocaleChanged()11714     void onLocaleChanged() {
11715         mEditor.onLocaleChanged();
11716     }
11717 
11718     /**
11719      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
11720      * Made available to achieve a consistent behavior.
11721      * @hide
11722      */
getWordIterator()11723     public WordIterator getWordIterator() {
11724         if (mEditor != null) {
11725             return mEditor.getWordIterator();
11726         } else {
11727             return null;
11728         }
11729     }
11730 
11731     /** @hide */
11732     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)11733     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
11734         super.onPopulateAccessibilityEventInternal(event);
11735 
11736         final CharSequence text = getTextForAccessibility();
11737         if (!TextUtils.isEmpty(text)) {
11738             event.getText().add(text);
11739         }
11740     }
11741 
11742     @Override
getAccessibilityClassName()11743     public CharSequence getAccessibilityClassName() {
11744         return TextView.class.getName();
11745     }
11746 
11747     /** @hide */
11748     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11749     protected void onProvideStructure(@NonNull ViewStructure structure,
11750             @ViewStructureType int viewFor, int flags) {
11751         super.onProvideStructure(structure, viewFor, flags);
11752 
11753         final boolean isPassword = hasPasswordTransformationMethod()
11754                 || isPasswordInputType(getInputType());
11755         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11756                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11757             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11758                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
11759             }
11760             if (mTextId != Resources.ID_NULL) {
11761                 try {
11762                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
11763                 } catch (Resources.NotFoundException e) {
11764                     if (android.view.autofill.Helper.sVerbose) {
11765                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
11766                                 + mTextId + ": " + e.getMessage());
11767                     }
11768                 }
11769             }
11770             String[] mimeTypes = getReceiveContentMimeTypes();
11771             if (mimeTypes == null && mEditor != null) {
11772                 // If the app hasn't set a listener for receiving content on this view (ie,
11773                 // getReceiveContentMimeTypes() returns null), check if it implements the
11774                 // keyboard image API and, if possible, use those MIME types as fallback.
11775                 // This fallback is only in place for autofill, not other mechanisms for
11776                 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER
11777                 // in TextViewOnReceiveContentListener for more info.
11778                 mimeTypes = mEditor.getDefaultOnReceiveContentListener()
11779                         .getFallbackMimeTypesForAutofill(this);
11780             }
11781             structure.setReceiveContentMimeTypes(mimeTypes);
11782         }
11783 
11784         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11785                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11786             if (mLayout == null) {
11787                 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11788                     Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
11789                 }
11790                 assumeLayout();
11791             }
11792             Layout layout = mLayout;
11793             final int lineCount = layout.getLineCount();
11794             if (lineCount <= 1) {
11795                 // Simple case: this is a single line.
11796                 final CharSequence text = getText();
11797                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11798                     structure.setText(text);
11799                 } else {
11800                     structure.setText(text, getSelectionStart(), getSelectionEnd());
11801                 }
11802             } else {
11803                 // Complex case: multi-line, could be scrolled or within a scroll container
11804                 // so some lines are not visible.
11805                 final int[] tmpCords = new int[2];
11806                 getLocationInWindow(tmpCords);
11807                 final int topWindowLocation = tmpCords[1];
11808                 View root = this;
11809                 ViewParent viewParent = getParent();
11810                 while (viewParent instanceof View) {
11811                     root = (View) viewParent;
11812                     viewParent = root.getParent();
11813                 }
11814                 final int windowHeight = root.getHeight();
11815                 final int topLine;
11816                 final int bottomLine;
11817                 if (topWindowLocation >= 0) {
11818                     // The top of the view is fully within its window; start text at line 0.
11819                     topLine = getLineAtCoordinateUnclamped(0);
11820                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
11821                 } else {
11822                     // The top of hte window has scrolled off the top of the window; figure out
11823                     // the starting line for this.
11824                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
11825                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
11826                 }
11827                 // We want to return some contextual lines above/below the lines that are
11828                 // actually visible.
11829                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
11830                 if (expandedTopLine < 0) {
11831                     expandedTopLine = 0;
11832                 }
11833                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
11834                 if (expandedBottomLine >= lineCount) {
11835                     expandedBottomLine = lineCount - 1;
11836                 }
11837 
11838                 // Convert lines into character offsets.
11839                 int expandedTopChar = layout.getLineStart(expandedTopLine);
11840                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
11841 
11842                 // Take into account selection -- if there is a selection, we need to expand
11843                 // the text we are returning to include that selection.
11844                 final int selStart = getSelectionStart();
11845                 final int selEnd = getSelectionEnd();
11846                 if (selStart < selEnd) {
11847                     if (selStart < expandedTopChar) {
11848                         expandedTopChar = selStart;
11849                     }
11850                     if (selEnd > expandedBottomChar) {
11851                         expandedBottomChar = selEnd;
11852                     }
11853                 }
11854 
11855                 // Get the text and trim it to the range we are reporting.
11856                 CharSequence text = getText();
11857 
11858                 if (text != null) {
11859                     if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
11860                         // Cap the offsets to avoid an OOB exception. That can happen if the
11861                         // displayed/layout text, on which these offsets are calculated, is longer
11862                         // than the original text (such as when the view is translated by the
11863                         // platform intelligence).
11864                         // TODO(b/196433694): Figure out how to better handle the offset
11865                         // calculations for this case (so we don't unnecessarily cutoff the original
11866                         // text, for example).
11867                         expandedTopChar = Math.min(expandedTopChar, text.length());
11868                         expandedBottomChar = Math.min(expandedBottomChar, text.length());
11869                         text = text.subSequence(expandedTopChar, expandedBottomChar);
11870                     }
11871 
11872                     if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11873                         structure.setText(text);
11874                     } else {
11875                         structure.setText(text,
11876                                 selStart - expandedTopChar,
11877                                 selEnd - expandedTopChar);
11878 
11879                         final int[] lineOffsets = new int[bottomLine - topLine + 1];
11880                         final int[] lineBaselines = new int[bottomLine - topLine + 1];
11881                         final int baselineOffset = getBaselineOffset();
11882                         for (int i = topLine; i <= bottomLine; i++) {
11883                             lineOffsets[i - topLine] = layout.getLineStart(i);
11884                             lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
11885                         }
11886                         structure.setTextLines(lineOffsets, lineBaselines);
11887                     }
11888                 }
11889             }
11890 
11891             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
11892                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11893                 // Extract style information that applies to the TextView as a whole.
11894                 int style = 0;
11895                 int typefaceStyle = getTypefaceStyle();
11896                 if ((typefaceStyle & Typeface.BOLD) != 0) {
11897                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11898                 }
11899                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
11900                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
11901                 }
11902 
11903                 // Global styles can also be set via TextView.setPaintFlags().
11904                 int paintFlags = mTextPaint.getFlags();
11905                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
11906                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11907                 }
11908                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
11909                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
11910                 }
11911                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
11912                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
11913                 }
11914 
11915                 // TextView does not have its own text background color. A background is either part
11916                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
11917                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
11918                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
11919             }
11920             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11921                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11922                 structure.setMinTextEms(getMinEms());
11923                 structure.setMaxTextEms(getMaxEms());
11924                 int maxLength = -1;
11925                 for (InputFilter filter: getFilters()) {
11926                     if (filter instanceof InputFilter.LengthFilter) {
11927                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
11928                         break;
11929                     }
11930                 }
11931                 structure.setMaxTextLength(maxLength);
11932             }
11933         }
11934         if (mHintId != Resources.ID_NULL) {
11935             try {
11936                 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId));
11937             } catch (Resources.NotFoundException e) {
11938                 if (android.view.autofill.Helper.sVerbose) {
11939                     Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id "
11940                             + mHintId + ": " + e.getMessage());
11941                 }
11942             }
11943         }
11944         structure.setHint(getHint());
11945         structure.setInputType(getInputType());
11946     }
11947 
canRequestAutofill()11948     boolean canRequestAutofill() {
11949         if (!isAutofillable()) {
11950             return false;
11951         }
11952         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11953         if (afm != null) {
11954             return afm.isEnabled();
11955         }
11956         return false;
11957     }
11958 
requestAutofill()11959     private void requestAutofill() {
11960         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11961         if (afm != null) {
11962             afm.requestAutofill(this);
11963         }
11964     }
11965 
11966     @Override
autofill(AutofillValue value)11967     public void autofill(AutofillValue value) {
11968         if (!isTextEditable()) {
11969             Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
11970             return;
11971         }
11972         if (!value.isText()) {
11973             Log.w(LOG_TAG, "value of type " + value.describeContents()
11974                     + " cannot be autofilled into " + this);
11975             return;
11976         }
11977         final ClipData clip = ClipData.newPlainText("", value.getTextValue());
11978         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build();
11979         performReceiveContent(payload);
11980     }
11981 
11982     @Override
getAutofillType()11983     public @AutofillType int getAutofillType() {
11984         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
11985     }
11986 
11987     /**
11988      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
11989      * {@code char}s if longer.
11990      *
11991      * @return current text, {@code null} if the text is not editable
11992      *
11993      * @see View#getAutofillValue()
11994      */
11995     @Override
11996     @Nullable
getAutofillValue()11997     public AutofillValue getAutofillValue() {
11998         if (isTextEditable()) {
11999             final CharSequence text = TextUtils.trimToParcelableSize(getText());
12000             return AutofillValue.forText(text);
12001         }
12002         return null;
12003     }
12004 
12005     /** @hide */
12006     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)12007     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
12008         super.onInitializeAccessibilityEventInternal(event);
12009 
12010         final boolean isPassword = hasPasswordTransformationMethod();
12011         event.setPassword(isPassword);
12012 
12013         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
12014             event.setFromIndex(Selection.getSelectionStart(mText));
12015             event.setToIndex(Selection.getSelectionEnd(mText));
12016             event.setItemCount(mText.length());
12017         }
12018     }
12019 
12020     /** @hide */
12021     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)12022     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
12023         super.onInitializeAccessibilityNodeInfoInternal(info);
12024 
12025         final boolean isPassword = hasPasswordTransformationMethod();
12026         info.setPassword(isPassword);
12027         info.setText(getTextForAccessibility());
12028         info.setHintText(mHint);
12029         info.setShowingHintText(isShowingHint());
12030 
12031         if (mBufferType == BufferType.EDITABLE) {
12032             info.setEditable(true);
12033             if (isEnabled()) {
12034                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
12035             }
12036         }
12037 
12038         if (mEditor != null) {
12039             info.setInputType(mEditor.mInputType);
12040 
12041             if (mEditor.mError != null) {
12042                 info.setContentInvalid(true);
12043                 info.setError(mEditor.mError);
12044             }
12045             // TextView will expose this action if it is editable and has focus.
12046             if (isTextEditable() && isFocused()) {
12047                 CharSequence imeActionLabel = mContext.getResources().getString(
12048                         com.android.internal.R.string.keyboardview_keycode_enter);
12049                 if (getImeActionLabel() != null) {
12050                     imeActionLabel = getImeActionLabel();
12051                 }
12052                 AccessibilityNodeInfo.AccessibilityAction action =
12053                         new AccessibilityNodeInfo.AccessibilityAction(
12054                                 R.id.accessibilityActionImeEnter, imeActionLabel);
12055                 info.addAction(action);
12056             }
12057         }
12058 
12059         if (!TextUtils.isEmpty(mText)) {
12060             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
12061             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
12062             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
12063                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
12064                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
12065                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
12066                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
12067             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
12068             info.setAvailableExtraData(Arrays.asList(
12069                     EXTRA_DATA_RENDERING_INFO_KEY,
12070                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
12071             ));
12072         } else {
12073             info.setAvailableExtraData(Arrays.asList(
12074                     EXTRA_DATA_RENDERING_INFO_KEY
12075             ));
12076         }
12077 
12078         if (isFocused()) {
12079             if (canCopy()) {
12080                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
12081             }
12082             if (canPaste()) {
12083                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
12084             }
12085             if (canCut()) {
12086                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
12087             }
12088             if (canShare()) {
12089                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
12090                         ACCESSIBILITY_ACTION_SHARE,
12091                         getResources().getString(com.android.internal.R.string.share)));
12092             }
12093             if (canProcessText()) {  // also implies mEditor is not null.
12094                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
12095             }
12096         }
12097 
12098         // Check for known input filter types.
12099         final int numFilters = mFilters.length;
12100         for (int i = 0; i < numFilters; i++) {
12101             final InputFilter filter = mFilters[i];
12102             if (filter instanceof InputFilter.LengthFilter) {
12103                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
12104             }
12105         }
12106 
12107         if (!isSingleLine()) {
12108             info.setMultiLine(true);
12109         }
12110 
12111         // A view should not be exposed as clickable/long-clickable to a service because of a
12112         // LinkMovementMethod.
12113         if ((info.isClickable() || info.isLongClickable())
12114                 && mMovement instanceof LinkMovementMethod) {
12115             if (!hasOnClickListeners()) {
12116                 info.setClickable(false);
12117                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
12118             }
12119             if (!hasOnLongClickListeners()) {
12120                 info.setLongClickable(false);
12121                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
12122             }
12123         }
12124     }
12125 
12126     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)12127     public void addExtraDataToAccessibilityNodeInfo(
12128             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
12129         if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
12130             int positionInfoStartIndex = arguments.getInt(
12131                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
12132             int positionInfoLength = arguments.getInt(
12133                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
12134             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
12135                     || (positionInfoStartIndex >= mText.length())) {
12136                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
12137                 return;
12138             }
12139             RectF[] boundingRects = new RectF[positionInfoLength];
12140             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
12141             populateCharacterBounds(builder, positionInfoStartIndex,
12142                     positionInfoStartIndex + positionInfoLength,
12143                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
12144             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
12145             for (int i = 0; i < positionInfoLength; i++) {
12146                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
12147                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
12148                     RectF bounds = cursorAnchorInfo
12149                             .getCharacterBounds(positionInfoStartIndex + i);
12150                     if (bounds != null) {
12151                         mapRectFromViewToScreenCoords(bounds, true);
12152                         boundingRects[i] = bounds;
12153                     }
12154                 }
12155             }
12156             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
12157             return;
12158         }
12159         if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) {
12160             final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo =
12161                     AccessibilityNodeInfo.ExtraRenderingInfo.obtain();
12162             extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height);
12163             extraRenderingInfo.setTextSizeInPx(getTextSize());
12164             extraRenderingInfo.setTextSizeUnit(getTextSizeUnit());
12165             info.setExtraRenderingInfo(extraRenderingInfo);
12166         }
12167     }
12168 
12169     /**
12170      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
12171      *
12172      * @param builder The builder to populate
12173      * @param startIndex The starting character index to populate
12174      * @param endIndex The ending character index to populate
12175      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
12176      * content
12177      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
12178      * @hide
12179      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)12180     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
12181             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
12182             float viewportToContentVerticalOffset) {
12183         final int minLine = mLayout.getLineForOffset(startIndex);
12184         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
12185         for (int line = minLine; line <= maxLine; ++line) {
12186             final int lineStart = mLayout.getLineStart(line);
12187             final int lineEnd = mLayout.getLineEnd(line);
12188             final int offsetStart = Math.max(lineStart, startIndex);
12189             final int offsetEnd = Math.min(lineEnd, endIndex);
12190             final boolean ltrLine =
12191                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
12192             final float[] widths = new float[offsetEnd - offsetStart];
12193             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
12194             final float top = mLayout.getLineTop(line);
12195             final float bottom = mLayout.getLineBottom(line);
12196             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
12197                 final float charWidth = widths[offset - offsetStart];
12198                 final boolean isRtl = mLayout.isRtlCharAt(offset);
12199                 final float primary = mLayout.getPrimaryHorizontal(offset);
12200                 final float secondary = mLayout.getSecondaryHorizontal(offset);
12201                 // TODO: This doesn't work perfectly for text with custom styles and
12202                 // TAB chars.
12203                 final float left;
12204                 final float right;
12205                 if (ltrLine) {
12206                     if (isRtl) {
12207                         left = secondary - charWidth;
12208                         right = secondary;
12209                     } else {
12210                         left = primary;
12211                         right = primary + charWidth;
12212                     }
12213                 } else {
12214                     if (!isRtl) {
12215                         left = secondary;
12216                         right = secondary + charWidth;
12217                     } else {
12218                         left = primary - charWidth;
12219                         right = primary;
12220                     }
12221                 }
12222                 // TODO: Check top-right and bottom-left as well.
12223                 final float localLeft = left + viewportToContentHorizontalOffset;
12224                 final float localRight = right + viewportToContentHorizontalOffset;
12225                 final float localTop = top + viewportToContentVerticalOffset;
12226                 final float localBottom = bottom + viewportToContentVerticalOffset;
12227                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
12228                 final boolean isBottomRightVisible =
12229                         isPositionVisible(localRight, localBottom);
12230                 int characterBoundsFlags = 0;
12231                 if (isTopLeftVisible || isBottomRightVisible) {
12232                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
12233                 }
12234                 if (!isTopLeftVisible || !isBottomRightVisible) {
12235                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
12236                 }
12237                 if (isRtl) {
12238                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
12239                 }
12240                 // Here offset is the index in Java chars.
12241                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
12242                         localBottom, characterBoundsFlags);
12243             }
12244         }
12245     }
12246 
12247     /**
12248      * @hide
12249      */
isPositionVisible(final float positionX, final float positionY)12250     public boolean isPositionVisible(final float positionX, final float positionY) {
12251         synchronized (TEMP_POSITION) {
12252             final float[] position = TEMP_POSITION;
12253             position[0] = positionX;
12254             position[1] = positionY;
12255             View view = this;
12256 
12257             while (view != null) {
12258                 if (view != this) {
12259                     // Local scroll is already taken into account in positionX/Y
12260                     position[0] -= view.getScrollX();
12261                     position[1] -= view.getScrollY();
12262                 }
12263 
12264                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
12265                         || position[1] > view.getHeight()) {
12266                     return false;
12267                 }
12268 
12269                 if (!view.getMatrix().isIdentity()) {
12270                     view.getMatrix().mapPoints(position);
12271                 }
12272 
12273                 position[0] += view.getLeft();
12274                 position[1] += view.getTop();
12275 
12276                 final ViewParent parent = view.getParent();
12277                 if (parent instanceof View) {
12278                     view = (View) parent;
12279                 } else {
12280                     // We've reached the ViewRoot, stop iterating
12281                     view = null;
12282                 }
12283             }
12284         }
12285 
12286         // We've been able to walk up the view hierarchy and the position was never clipped
12287         return true;
12288     }
12289 
12290     /**
12291      * Performs an accessibility action after it has been offered to the
12292      * delegate.
12293      *
12294      * @hide
12295      */
12296     @Override
performAccessibilityActionInternal(int action, Bundle arguments)12297     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
12298         if (mEditor != null
12299                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
12300             return true;
12301         }
12302         switch (action) {
12303             case AccessibilityNodeInfo.ACTION_CLICK: {
12304                 return performAccessibilityActionClick(arguments);
12305             }
12306             case AccessibilityNodeInfo.ACTION_COPY: {
12307                 if (isFocused() && canCopy()) {
12308                     if (onTextContextMenuItem(ID_COPY)) {
12309                         return true;
12310                     }
12311                 }
12312             } return false;
12313             case AccessibilityNodeInfo.ACTION_PASTE: {
12314                 if (isFocused() && canPaste()) {
12315                     if (onTextContextMenuItem(ID_PASTE)) {
12316                         return true;
12317                     }
12318                 }
12319             } return false;
12320             case AccessibilityNodeInfo.ACTION_CUT: {
12321                 if (isFocused() && canCut()) {
12322                     if (onTextContextMenuItem(ID_CUT)) {
12323                         return true;
12324                     }
12325                 }
12326             } return false;
12327             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
12328                 ensureIterableTextForAccessibilitySelectable();
12329                 CharSequence text = getIterableTextForAccessibility();
12330                 if (text == null) {
12331                     return false;
12332                 }
12333                 final int start = (arguments != null) ? arguments.getInt(
12334                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
12335                 final int end = (arguments != null) ? arguments.getInt(
12336                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
12337                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
12338                     // No arguments clears the selection.
12339                     if (start == end && end == -1) {
12340                         Selection.removeSelection((Spannable) text);
12341                         return true;
12342                     }
12343                     if (start >= 0 && start <= end && end <= text.length()) {
12344                         Selection.setSelection((Spannable) text, start, end);
12345                         // Make sure selection mode is engaged.
12346                         if (mEditor != null) {
12347                             mEditor.startSelectionActionModeAsync(false);
12348                         }
12349                         return true;
12350                     }
12351                 }
12352             } return false;
12353             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
12354             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
12355                 ensureIterableTextForAccessibilitySelectable();
12356                 return super.performAccessibilityActionInternal(action, arguments);
12357             }
12358             case ACCESSIBILITY_ACTION_SHARE: {
12359                 if (isFocused() && canShare()) {
12360                     if (onTextContextMenuItem(ID_SHARE)) {
12361                         return true;
12362                     }
12363                 }
12364             } return false;
12365             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
12366                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
12367                     return false;
12368                 }
12369                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
12370                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
12371                 setText(text);
12372                 if (mText != null) {
12373                     int updatedTextLength = mText.length();
12374                     if (updatedTextLength > 0) {
12375                         Selection.setSelection(mSpannable, updatedTextLength);
12376                     }
12377                 }
12378             } return true;
12379             case R.id.accessibilityActionImeEnter: {
12380                 if (isFocused() && isTextEditable()) {
12381                     onEditorAction(getImeActionId());
12382                 }
12383             } return true;
12384             case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
12385                 if (isLongClickable()) {
12386                     boolean handled;
12387                     if (isEnabled() && (mBufferType == BufferType.EDITABLE)) {
12388                         mEditor.mIsBeingLongClickedByAccessibility = true;
12389                         try {
12390                             handled = performLongClick();
12391                         } finally {
12392                             mEditor.mIsBeingLongClickedByAccessibility = false;
12393                         }
12394                     } else {
12395                         handled = performLongClick();
12396                     }
12397                     return handled;
12398                 }
12399             }
12400             return false;
12401             default: {
12402                 return super.performAccessibilityActionInternal(action, arguments);
12403             }
12404         }
12405     }
12406 
performAccessibilityActionClick(Bundle arguments)12407     private boolean performAccessibilityActionClick(Bundle arguments) {
12408         boolean handled = false;
12409 
12410         if (!isEnabled()) {
12411             return false;
12412         }
12413 
12414         if (isClickable() || isLongClickable()) {
12415             // Simulate View.onTouchEvent for an ACTION_UP event
12416             if (isFocusable() && !isFocused()) {
12417                 requestFocus();
12418             }
12419 
12420             performClick();
12421             handled = true;
12422         }
12423 
12424         // Show the IME, except when selecting in read-only text.
12425         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
12426                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
12427             final InputMethodManager imm = getInputMethodManager();
12428             viewClicked(imm);
12429             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
12430                 handled |= imm.showSoftInput(this, 0);
12431             }
12432         }
12433 
12434         return handled;
12435     }
12436 
hasSpannableText()12437     private boolean hasSpannableText() {
12438         return mText != null && mText instanceof Spannable;
12439     }
12440 
12441     /** @hide */
12442     @Override
sendAccessibilityEventInternal(int eventType)12443     public void sendAccessibilityEventInternal(int eventType) {
12444         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
12445             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
12446         }
12447 
12448         super.sendAccessibilityEventInternal(eventType);
12449     }
12450 
12451     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)12452     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
12453         // Do not send scroll events since first they are not interesting for
12454         // accessibility and second such events a generated too frequently.
12455         // For details see the implementation of bringTextIntoView().
12456         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
12457             return;
12458         }
12459         super.sendAccessibilityEventUnchecked(event);
12460     }
12461 
12462     /**
12463      * Returns the text that should be exposed to accessibility services.
12464      * <p>
12465      * This approximates what is displayed visually. If the user has specified
12466      * that accessibility services should speak passwords, this method will
12467      * bypass any password transformation method and return unobscured text.
12468      *
12469      * @return the text that should be exposed to accessibility services, may
12470      *         be {@code null} if no text is set
12471      */
12472     @Nullable
12473     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTextForAccessibility()12474     private CharSequence getTextForAccessibility() {
12475         // If the text is empty, we must be showing the hint text.
12476         if (TextUtils.isEmpty(mText)) {
12477             return mHint;
12478         }
12479 
12480         // Otherwise, return whatever text is being displayed.
12481         return TextUtils.trimToParcelableSize(mTransformed);
12482     }
12483 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12484     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
12485             int fromIndex, int removedCount, int addedCount) {
12486         AccessibilityEvent event =
12487                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
12488         event.setFromIndex(fromIndex);
12489         event.setRemovedCount(removedCount);
12490         event.setAddedCount(addedCount);
12491         event.setBeforeText(beforeText);
12492         sendAccessibilityEventUnchecked(event);
12493     }
12494 
getInputMethodManager()12495     private InputMethodManager getInputMethodManager() {
12496         return getContext().getSystemService(InputMethodManager.class);
12497     }
12498 
12499     /**
12500      * Returns whether this text view is a current input method target.  The
12501      * default implementation just checks with {@link InputMethodManager}.
12502      * @return True if the TextView is a current input method target; false otherwise.
12503      */
isInputMethodTarget()12504     public boolean isInputMethodTarget() {
12505         InputMethodManager imm = getInputMethodManager();
12506         return imm != null && imm.isActive(this);
12507     }
12508 
12509     static final int ID_SELECT_ALL = android.R.id.selectAll;
12510     static final int ID_UNDO = android.R.id.undo;
12511     static final int ID_REDO = android.R.id.redo;
12512     static final int ID_CUT = android.R.id.cut;
12513     static final int ID_COPY = android.R.id.copy;
12514     static final int ID_PASTE = android.R.id.paste;
12515     static final int ID_SHARE = android.R.id.shareText;
12516     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
12517     static final int ID_REPLACE = android.R.id.replaceText;
12518     static final int ID_ASSIST = android.R.id.textAssist;
12519     static final int ID_AUTOFILL = android.R.id.autofill;
12520 
12521     /**
12522      * Called when a context menu option for the text view is selected.  Currently
12523      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
12524      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
12525      *
12526      * @return true if the context menu item action was performed.
12527      */
onTextContextMenuItem(int id)12528     public boolean onTextContextMenuItem(int id) {
12529         int min = 0;
12530         int max = mText.length();
12531 
12532         if (isFocused()) {
12533             final int selStart = getSelectionStart();
12534             final int selEnd = getSelectionEnd();
12535 
12536             min = Math.max(0, Math.min(selStart, selEnd));
12537             max = Math.max(0, Math.max(selStart, selEnd));
12538         }
12539 
12540         switch (id) {
12541             case ID_SELECT_ALL:
12542                 final boolean hadSelection = hasSelection();
12543                 selectAllText();
12544                 if (mEditor != null && hadSelection) {
12545                     mEditor.invalidateActionModeAsync();
12546                 }
12547                 return true;
12548 
12549             case ID_UNDO:
12550                 if (mEditor != null) {
12551                     mEditor.undo();
12552                 }
12553                 return true;  // Returns true even if nothing was undone.
12554 
12555             case ID_REDO:
12556                 if (mEditor != null) {
12557                     mEditor.redo();
12558                 }
12559                 return true;  // Returns true even if nothing was undone.
12560 
12561             case ID_PASTE:
12562                 paste(true /* withFormatting */);
12563                 return true;
12564 
12565             case ID_PASTE_AS_PLAIN_TEXT:
12566                 paste(false /* withFormatting */);
12567                 return true;
12568 
12569             case ID_CUT:
12570                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
12571                 if (setPrimaryClip(cutData)) {
12572                     deleteText_internal(min, max);
12573                 } else {
12574                     Toast.makeText(getContext(),
12575                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12576                             Toast.LENGTH_SHORT).show();
12577                 }
12578                 return true;
12579 
12580             case ID_COPY:
12581                 // For link action mode in a non-selectable/non-focusable TextView,
12582                 // make sure that we set the appropriate min/max.
12583                 final int selStart = getSelectionStart();
12584                 final int selEnd = getSelectionEnd();
12585                 min = Math.max(0, Math.min(selStart, selEnd));
12586                 max = Math.max(0, Math.max(selStart, selEnd));
12587                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
12588                 if (setPrimaryClip(copyData)) {
12589                     stopTextActionMode();
12590                 } else {
12591                     Toast.makeText(getContext(),
12592                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12593                             Toast.LENGTH_SHORT).show();
12594                 }
12595                 return true;
12596 
12597             case ID_REPLACE:
12598                 if (mEditor != null) {
12599                     mEditor.replace();
12600                 }
12601                 return true;
12602 
12603             case ID_SHARE:
12604                 shareSelectedText();
12605                 return true;
12606 
12607             case ID_AUTOFILL:
12608                 requestAutofill();
12609                 stopTextActionMode();
12610                 return true;
12611         }
12612         return false;
12613     }
12614 
12615     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTransformedText(int start, int end)12616     CharSequence getTransformedText(int start, int end) {
12617         return removeSuggestionSpans(mTransformed.subSequence(start, end));
12618     }
12619 
12620     @Override
performLongClick()12621     public boolean performLongClick() {
12622         if (DEBUG_CURSOR) {
12623             logCursor("performLongClick", null);
12624         }
12625 
12626         boolean handled = false;
12627         boolean performedHapticFeedback = false;
12628 
12629         if (mEditor != null) {
12630             mEditor.mIsBeingLongClicked = true;
12631         }
12632 
12633         if (super.performLongClick()) {
12634             handled = true;
12635             performedHapticFeedback = true;
12636         }
12637 
12638         if (mEditor != null) {
12639             handled |= mEditor.performLongClick(handled);
12640             mEditor.mIsBeingLongClicked = false;
12641         }
12642 
12643         if (handled) {
12644             if (!performedHapticFeedback) {
12645               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
12646             }
12647             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
12648         } else {
12649             MetricsLogger.action(
12650                     mContext,
12651                     MetricsEvent.TEXT_LONGPRESS,
12652                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
12653         }
12654 
12655         return handled;
12656     }
12657 
12658     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12659     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
12660         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
12661         if (mEditor != null) {
12662             mEditor.onScrollChanged();
12663         }
12664     }
12665 
12666     /**
12667      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
12668      * by the IME or by the spell checker as the user types. This is done by adding
12669      * {@link SuggestionSpan}s to the text.
12670      *
12671      * When suggestions are enabled (default), this list of suggestions will be displayed when the
12672      * user asks for them on these parts of the text. This value depends on the inputType of this
12673      * TextView.
12674      *
12675      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
12676      *
12677      * In addition, the type variation must be one of
12678      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
12679      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
12680      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
12681      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
12682      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
12683      *
12684      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
12685      *
12686      * @return true if the suggestions popup window is enabled, based on the inputType.
12687      */
isSuggestionsEnabled()12688     public boolean isSuggestionsEnabled() {
12689         if (mEditor == null) return false;
12690         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
12691             return false;
12692         }
12693         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
12694 
12695         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
12696         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
12697                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
12698                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
12699                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
12700                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
12701     }
12702 
12703     /**
12704      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12705      * selection is initiated in this View.
12706      *
12707      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
12708      * Paste, Replace and Share actions, depending on what this View supports.
12709      *
12710      * <p>A custom implementation can add new entries in the default menu in its
12711      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
12712      * method. The default actions can also be removed from the menu using
12713      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12714      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
12715      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
12716      *
12717      * <p>Returning false from
12718      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
12719      * will prevent the action mode from being started.
12720      *
12721      * <p>Action click events should be handled by the custom implementation of
12722      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
12723      * android.view.MenuItem)}.
12724      *
12725      * <p>Note that text selection mode is not started when a TextView receives focus and the
12726      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
12727      * that case, to allow for quick replacement.
12728      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12729     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
12730         createEditorIfNeeded();
12731         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
12732     }
12733 
12734     /**
12735      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
12736      *
12737      * @return The current custom selection callback.
12738      */
getCustomSelectionActionModeCallback()12739     public ActionMode.Callback getCustomSelectionActionModeCallback() {
12740         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
12741     }
12742 
12743     /**
12744      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12745      * insertion is initiated in this View.
12746      * The standard implementation populates the menu with a subset of Select All,
12747      * Paste and Replace actions, depending on what this View supports.
12748      *
12749      * <p>A custom implementation can add new entries in the default menu in its
12750      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
12751      * android.view.Menu)} method. The default actions can also be removed from the menu using
12752      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12753      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
12754      *
12755      * <p>Returning false from
12756      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
12757      * android.view.Menu)} will prevent the action mode from being started.</p>
12758      *
12759      * <p>Action click events should be handled by the custom implementation of
12760      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
12761      * android.view.MenuItem)}.</p>
12762      *
12763      * <p>Note that text insertion mode is not started when a TextView receives focus and the
12764      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
12765      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12766     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
12767         createEditorIfNeeded();
12768         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
12769     }
12770 
12771     /**
12772      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
12773      *
12774      * @return The current custom insertion callback.
12775      */
getCustomInsertionActionModeCallback()12776     public ActionMode.Callback getCustomInsertionActionModeCallback() {
12777         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
12778     }
12779 
12780     /**
12781      * Sets the {@link TextClassifier} for this TextView.
12782      */
setTextClassifier(@ullable TextClassifier textClassifier)12783     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
12784         mTextClassifier = textClassifier;
12785     }
12786 
12787     /**
12788      * Returns the {@link TextClassifier} used by this TextView.
12789      * If no TextClassifier has been set, this TextView uses the default set by the
12790      * {@link TextClassificationManager}.
12791      */
12792     @NonNull
getTextClassifier()12793     public TextClassifier getTextClassifier() {
12794         if (mTextClassifier == null) {
12795             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12796             if (tcm != null) {
12797                 return tcm.getTextClassifier();
12798             }
12799             return TextClassifier.NO_OP;
12800         }
12801         return mTextClassifier;
12802     }
12803 
12804     /**
12805      * Returns a session-aware text classifier.
12806      * This method creates one if none already exists or the current one is destroyed.
12807      */
12808     @NonNull
getTextClassificationSession()12809     TextClassifier getTextClassificationSession() {
12810         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
12811             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12812             if (tcm != null) {
12813                 final String widgetType;
12814                 if (isTextEditable()) {
12815                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
12816                 } else if (isTextSelectable()) {
12817                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
12818                 } else {
12819                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
12820                 }
12821                 mTextClassificationContext = new TextClassificationContext.Builder(
12822                         mContext.getPackageName(), widgetType)
12823                         .build();
12824                 if (mTextClassifier != null) {
12825                     mTextClassificationSession = tcm.createTextClassificationSession(
12826                             mTextClassificationContext, mTextClassifier);
12827                 } else {
12828                     mTextClassificationSession = tcm.createTextClassificationSession(
12829                             mTextClassificationContext);
12830                 }
12831             } else {
12832                 mTextClassificationSession = TextClassifier.NO_OP;
12833             }
12834         }
12835         return mTextClassificationSession;
12836     }
12837 
12838     /**
12839      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
12840      * @see #getTextClassificationSession()
12841      */
12842     @Nullable
getTextClassificationContext()12843     TextClassificationContext getTextClassificationContext() {
12844         return mTextClassificationContext;
12845     }
12846 
12847     /**
12848      * Returns true if this TextView uses a no-op TextClassifier.
12849      */
usesNoOpTextClassifier()12850     boolean usesNoOpTextClassifier() {
12851         return getTextClassifier() == TextClassifier.NO_OP;
12852     }
12853 
12854     /**
12855      * Starts an ActionMode for the specified TextLinkSpan.
12856      *
12857      * @return Whether or not we're attempting to start the action mode.
12858      * @hide
12859      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12860     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12861         Preconditions.checkNotNull(clickedSpan);
12862 
12863         if (!(mText instanceof Spanned)) {
12864             return false;
12865         }
12866 
12867         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
12868         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
12869 
12870         if (start < 0 || end > mText.length() || start >= end) {
12871             return false;
12872         }
12873 
12874         createEditorIfNeeded();
12875         mEditor.startLinkActionModeAsync(start, end);
12876         return true;
12877     }
12878 
12879     /**
12880      * Handles a click on the specified TextLinkSpan.
12881      *
12882      * @return Whether or not the click is being handled.
12883      * @hide
12884      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12885     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12886         Preconditions.checkNotNull(clickedSpan);
12887         if (mText instanceof Spanned) {
12888             final Spanned spanned = (Spanned) mText;
12889             final int start = spanned.getSpanStart(clickedSpan);
12890             final int end = spanned.getSpanEnd(clickedSpan);
12891             if (start >= 0 && end <= mText.length() && start < end) {
12892                 final TextClassification.Request request = new TextClassification.Request.Builder(
12893                         mText, start, end)
12894                         .setDefaultLocales(getTextLocales())
12895                         .build();
12896                 final Supplier<TextClassification> supplier = () ->
12897                         getTextClassificationSession().classifyText(request);
12898                 final Consumer<TextClassification> consumer = classification -> {
12899                     if (classification != null) {
12900                         if (!classification.getActions().isEmpty()) {
12901                             try {
12902                                 classification.getActions().get(0).getActionIntent().send();
12903                             } catch (PendingIntent.CanceledException e) {
12904                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
12905                             }
12906                         } else {
12907                             Log.d(LOG_TAG, "No link action to perform");
12908                         }
12909                     } else {
12910                         // classification == null
12911                         Log.d(LOG_TAG, "Timeout while classifying text");
12912                     }
12913                 };
12914                 CompletableFuture.supplyAsync(supplier)
12915                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
12916                         .thenAccept(consumer);
12917                 return true;
12918             }
12919         }
12920         return false;
12921     }
12922 
12923     /**
12924      * @hide
12925      */
12926     @UnsupportedAppUsage
stopTextActionMode()12927     protected void stopTextActionMode() {
12928         if (mEditor != null) {
12929             mEditor.stopTextActionMode();
12930         }
12931     }
12932 
12933     /** @hide */
hideFloatingToolbar(int durationMs)12934     public void hideFloatingToolbar(int durationMs) {
12935         if (mEditor != null) {
12936             mEditor.hideFloatingToolbar(durationMs);
12937         }
12938     }
12939 
canUndo()12940     boolean canUndo() {
12941         return mEditor != null && mEditor.canUndo();
12942     }
12943 
canRedo()12944     boolean canRedo() {
12945         return mEditor != null && mEditor.canRedo();
12946     }
12947 
canCut()12948     boolean canCut() {
12949         if (hasPasswordTransformationMethod()) {
12950             return false;
12951         }
12952 
12953         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
12954                 && mEditor.mKeyListener != null) {
12955             return true;
12956         }
12957 
12958         return false;
12959     }
12960 
canCopy()12961     boolean canCopy() {
12962         if (hasPasswordTransformationMethod()) {
12963             return false;
12964         }
12965 
12966         if (mText.length() > 0 && hasSelection() && mEditor != null) {
12967             return true;
12968         }
12969 
12970         return false;
12971     }
12972 
canShare()12973     boolean canShare() {
12974         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
12975             return false;
12976         }
12977         return canCopy();
12978     }
12979 
isDeviceProvisioned()12980     boolean isDeviceProvisioned() {
12981         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
12982             mDeviceProvisionedState = Settings.Global.getInt(
12983                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
12984                     ? DEVICE_PROVISIONED_YES
12985                     : DEVICE_PROVISIONED_NO;
12986         }
12987         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
12988     }
12989 
12990     @UnsupportedAppUsage
canPaste()12991     boolean canPaste() {
12992         return (mText instanceof Editable
12993                 && mEditor != null && mEditor.mKeyListener != null
12994                 && getSelectionStart() >= 0
12995                 && getSelectionEnd() >= 0
12996                 && getClipboardManagerForUser().hasPrimaryClip());
12997     }
12998 
canPasteAsPlainText()12999     boolean canPasteAsPlainText() {
13000         if (!canPaste()) {
13001             return false;
13002         }
13003 
13004         final ClipDescription description =
13005                 getClipboardManagerForUser().getPrimaryClipDescription();
13006         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
13007         return (isPlainType && description.isStyledText())
13008                 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
13009     }
13010 
canProcessText()13011     boolean canProcessText() {
13012         if (getId() == View.NO_ID) {
13013             return false;
13014         }
13015         return canShare();
13016     }
13017 
canSelectAllText()13018     boolean canSelectAllText() {
13019         return canSelectText() && !hasPasswordTransformationMethod()
13020                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
13021     }
13022 
selectAllText()13023     boolean selectAllText() {
13024         if (mEditor != null) {
13025             // Hide the toolbar before changing the selection to avoid flickering.
13026             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
13027         }
13028         final int length = mText.length();
13029         Selection.setSelection(mSpannable, 0, length);
13030         return length > 0;
13031     }
13032 
paste(boolean withFormatting)13033     private void paste(boolean withFormatting) {
13034         ClipboardManager clipboard = getClipboardManagerForUser();
13035         ClipData clip = clipboard.getPrimaryClip();
13036         if (clip == null) {
13037             return;
13038         }
13039         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD)
13040                 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT)
13041                 .build();
13042         performReceiveContent(payload);
13043         sLastCutCopyOrTextChangedTime = 0;
13044     }
13045 
shareSelectedText()13046     private void shareSelectedText() {
13047         String selectedText = getSelectedText();
13048         if (selectedText != null && !selectedText.isEmpty()) {
13049             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
13050             sharingIntent.setType("text/plain");
13051             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
13052             selectedText = TextUtils.trimToParcelableSize(selectedText);
13053             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
13054             getContext().startActivity(Intent.createChooser(sharingIntent, null));
13055             Selection.setSelection(mSpannable, getSelectionEnd());
13056         }
13057     }
13058 
13059     @CheckResult
setPrimaryClip(ClipData clip)13060     private boolean setPrimaryClip(ClipData clip) {
13061         ClipboardManager clipboard = getClipboardManagerForUser();
13062         try {
13063             clipboard.setPrimaryClip(clip);
13064         } catch (Throwable t) {
13065             return false;
13066         }
13067         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
13068         return true;
13069     }
13070 
13071     /**
13072      * Get the character offset closest to the specified absolute position. A typical use case is to
13073      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
13074      *
13075      * @param x The horizontal absolute position of a point on screen
13076      * @param y The vertical absolute position of a point on screen
13077      * @return the character offset for the character whose position is closest to the specified
13078      *  position. Returns -1 if there is no layout.
13079      */
getOffsetForPosition(float x, float y)13080     public int getOffsetForPosition(float x, float y) {
13081         if (getLayout() == null) return -1;
13082         final int line = getLineAtCoordinate(y);
13083         final int offset = getOffsetAtCoordinate(line, x);
13084         return offset;
13085     }
13086 
convertToLocalHorizontalCoordinate(float x)13087     float convertToLocalHorizontalCoordinate(float x) {
13088         x -= getTotalPaddingLeft();
13089         // Clamp the position to inside of the view.
13090         x = Math.max(0.0f, x);
13091         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
13092         x += getScrollX();
13093         return x;
13094     }
13095 
13096     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getLineAtCoordinate(float y)13097     int getLineAtCoordinate(float y) {
13098         y -= getTotalPaddingTop();
13099         // Clamp the position to inside of the view.
13100         y = Math.max(0.0f, y);
13101         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
13102         y += getScrollY();
13103         return getLayout().getLineForVertical((int) y);
13104     }
13105 
getLineAtCoordinateUnclamped(float y)13106     int getLineAtCoordinateUnclamped(float y) {
13107         y -= getTotalPaddingTop();
13108         y += getScrollY();
13109         return getLayout().getLineForVertical((int) y);
13110     }
13111 
getOffsetAtCoordinate(int line, float x)13112     int getOffsetAtCoordinate(int line, float x) {
13113         x = convertToLocalHorizontalCoordinate(x);
13114         return getLayout().getOffsetForHorizontal(line, x);
13115     }
13116 
13117     /**
13118      * Handles drag events sent by the system following a call to
13119      * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
13120      * startDragAndDrop()}.
13121      *
13122      * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent}
13123      * implementation.
13124      *
13125      * <p>If this text view is editable, accepts all drag actions (returns true for an
13126      * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all
13127      * subsequent drag events). While the drag is in progress, updates the cursor position
13128      * to follow the touch location. Once a drop event is received, handles content insertion
13129      * via {@link #performReceiveContent}.
13130      *
13131      * @param event The {@link android.view.DragEvent} sent by the system.
13132      * The {@link android.view.DragEvent#getAction()} method returns an action type constant
13133      * defined in DragEvent, indicating the type of drag event represented by this object.
13134      * @return Returns true if this text view is editable and delegates to super otherwise.
13135      * See {@link View#onDragEvent}.
13136      */
13137     @Override
onDragEvent(DragEvent event)13138     public boolean onDragEvent(DragEvent event) {
13139         if (mEditor == null || !mEditor.hasInsertionController()) {
13140             // If this TextView is not editable, defer to the default View implementation. This
13141             // will check for the presence of an OnReceiveContentListener and accept/reject
13142             // drag events depending on whether the listener is/isn't set.
13143             return super.onDragEvent(event);
13144         }
13145         switch (event.getAction()) {
13146             case DragEvent.ACTION_DRAG_STARTED:
13147                 return true;
13148 
13149             case DragEvent.ACTION_DRAG_ENTERED:
13150                 TextView.this.requestFocus();
13151                 return true;
13152 
13153             case DragEvent.ACTION_DRAG_LOCATION:
13154                 if (mText instanceof Spannable) {
13155                     final int offset = getOffsetForPosition(event.getX(), event.getY());
13156                     Selection.setSelection(mSpannable, offset);
13157                 }
13158                 return true;
13159 
13160             case DragEvent.ACTION_DROP:
13161                 if (mEditor != null) mEditor.onDrop(event);
13162                 return true;
13163 
13164             case DragEvent.ACTION_DRAG_ENDED:
13165             case DragEvent.ACTION_DRAG_EXITED:
13166             default:
13167                 return true;
13168         }
13169     }
13170 
isInBatchEditMode()13171     boolean isInBatchEditMode() {
13172         if (mEditor == null) return false;
13173         final Editor.InputMethodState ims = mEditor.mInputMethodState;
13174         if (ims != null) {
13175             return ims.mBatchEditNesting > 0;
13176         }
13177         return mEditor.mInBatchEditControllers;
13178     }
13179 
13180     @Override
onRtlPropertiesChanged(int layoutDirection)13181     public void onRtlPropertiesChanged(int layoutDirection) {
13182         super.onRtlPropertiesChanged(layoutDirection);
13183 
13184         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
13185         if (mTextDir != newTextDir) {
13186             mTextDir = newTextDir;
13187             if (mLayout != null) {
13188                 checkForRelayout();
13189             }
13190         }
13191     }
13192 
13193     /**
13194      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
13195      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
13196      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
13197      * return value may not be the same as the one TextView uses if the View's layout direction is
13198      * not resolved or detached from parent root view.
13199      */
getTextDirectionHeuristic()13200     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
13201         if (hasPasswordTransformationMethod()) {
13202             // passwords fields should be LTR
13203             return TextDirectionHeuristics.LTR;
13204         }
13205 
13206         if (mEditor != null
13207                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
13208                     == EditorInfo.TYPE_CLASS_PHONE) {
13209             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
13210             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
13211             // RTL digits.
13212             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
13213             final String zero = symbols.getDigitStrings()[0];
13214             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
13215             // direction.
13216             final int firstCodepoint = zero.codePointAt(0);
13217             final byte digitDirection = Character.getDirectionality(firstCodepoint);
13218             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
13219                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
13220                 return TextDirectionHeuristics.RTL;
13221             } else {
13222                 return TextDirectionHeuristics.LTR;
13223             }
13224         }
13225 
13226         // Always need to resolve layout direction first
13227         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
13228 
13229         // Now, we can select the heuristic
13230         switch (getTextDirection()) {
13231             default:
13232             case TEXT_DIRECTION_FIRST_STRONG:
13233                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
13234                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
13235             case TEXT_DIRECTION_ANY_RTL:
13236                 return TextDirectionHeuristics.ANYRTL_LTR;
13237             case TEXT_DIRECTION_LTR:
13238                 return TextDirectionHeuristics.LTR;
13239             case TEXT_DIRECTION_RTL:
13240                 return TextDirectionHeuristics.RTL;
13241             case TEXT_DIRECTION_LOCALE:
13242                 return TextDirectionHeuristics.LOCALE;
13243             case TEXT_DIRECTION_FIRST_STRONG_LTR:
13244                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
13245             case TEXT_DIRECTION_FIRST_STRONG_RTL:
13246                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
13247         }
13248     }
13249 
13250     /**
13251      * @hide
13252      */
13253     @Override
onResolveDrawables(int layoutDirection)13254     public void onResolveDrawables(int layoutDirection) {
13255         // No need to resolve twice
13256         if (mLastLayoutDirection == layoutDirection) {
13257             return;
13258         }
13259         mLastLayoutDirection = layoutDirection;
13260 
13261         // Resolve drawables
13262         if (mDrawables != null) {
13263             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
13264                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
13265                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
13266                 applyCompoundDrawableTint();
13267             }
13268         }
13269     }
13270 
13271     /**
13272      * Prepares a drawable for display by propagating layout direction and
13273      * drawable state.
13274      *
13275      * @param dr the drawable to prepare
13276      */
prepareDrawableForDisplay(@ullable Drawable dr)13277     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
13278         if (dr == null) {
13279             return;
13280         }
13281 
13282         dr.setLayoutDirection(getLayoutDirection());
13283 
13284         if (dr.isStateful()) {
13285             dr.setState(getDrawableState());
13286             dr.jumpToCurrentState();
13287         }
13288     }
13289 
13290     /**
13291      * @hide
13292      */
resetResolvedDrawables()13293     protected void resetResolvedDrawables() {
13294         super.resetResolvedDrawables();
13295         mLastLayoutDirection = -1;
13296     }
13297 
13298     /**
13299      * @hide
13300      */
viewClicked(InputMethodManager imm)13301     protected void viewClicked(InputMethodManager imm) {
13302         if (imm != null) {
13303             imm.viewClicked(this);
13304         }
13305     }
13306 
13307     /**
13308      * Deletes the range of text [start, end[.
13309      * @hide
13310      */
13311     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
deleteText_internal(int start, int end)13312     protected void deleteText_internal(int start, int end) {
13313         ((Editable) mText).delete(start, end);
13314     }
13315 
13316     /**
13317      * Replaces the range of text [start, end[ by replacement text
13318      * @hide
13319      */
replaceText_internal(int start, int end, CharSequence text)13320     protected void replaceText_internal(int start, int end, CharSequence text) {
13321         ((Editable) mText).replace(start, end, text);
13322     }
13323 
13324     /**
13325      * Sets a span on the specified range of text
13326      * @hide
13327      */
setSpan_internal(Object span, int start, int end, int flags)13328     protected void setSpan_internal(Object span, int start, int end, int flags) {
13329         ((Editable) mText).setSpan(span, start, end, flags);
13330     }
13331 
13332     /**
13333      * Moves the cursor to the specified offset position in text
13334      * @hide
13335      */
setCursorPosition_internal(int start, int end)13336     protected void setCursorPosition_internal(int start, int end) {
13337         Selection.setSelection(((Editable) mText), start, end);
13338     }
13339 
13340     /**
13341      * An Editor should be created as soon as any of the editable-specific fields (grouped
13342      * inside the Editor object) is assigned to a non-default value.
13343      * This method will create the Editor if needed.
13344      *
13345      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
13346      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
13347      * Editor for backward compatibility, as soon as one of these fields is assigned.
13348      *
13349      * Also note that for performance reasons, the mEditor is created when needed, but not
13350      * reset when no more edit-specific fields are needed.
13351      */
13352     @UnsupportedAppUsage
createEditorIfNeeded()13353     private void createEditorIfNeeded() {
13354         if (mEditor == null) {
13355             mEditor = new Editor(this);
13356         }
13357     }
13358 
13359     /**
13360      * @hide
13361      */
13362     @Override
13363     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getIterableTextForAccessibility()13364     public CharSequence getIterableTextForAccessibility() {
13365         return mText;
13366     }
13367 
ensureIterableTextForAccessibilitySelectable()13368     private void ensureIterableTextForAccessibilitySelectable() {
13369         if (!(mText instanceof Spannable)) {
13370             setText(mText, BufferType.SPANNABLE);
13371         }
13372     }
13373 
13374     /**
13375      * @hide
13376      */
13377     @Override
getIteratorForGranularity(int granularity)13378     public TextSegmentIterator getIteratorForGranularity(int granularity) {
13379         switch (granularity) {
13380             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
13381                 Spannable text = (Spannable) getIterableTextForAccessibility();
13382                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
13383                     AccessibilityIterators.LineTextSegmentIterator iterator =
13384                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
13385                     iterator.initialize(text, getLayout());
13386                     return iterator;
13387                 }
13388             } break;
13389             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
13390                 Spannable text = (Spannable) getIterableTextForAccessibility();
13391                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
13392                     AccessibilityIterators.PageTextSegmentIterator iterator =
13393                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
13394                     iterator.initialize(this);
13395                     return iterator;
13396                 }
13397             } break;
13398         }
13399         return super.getIteratorForGranularity(granularity);
13400     }
13401 
13402     /**
13403      * @hide
13404      */
13405     @Override
getAccessibilitySelectionStart()13406     public int getAccessibilitySelectionStart() {
13407         return getSelectionStart();
13408     }
13409 
13410     /**
13411      * @hide
13412      */
isAccessibilitySelectionExtendable()13413     public boolean isAccessibilitySelectionExtendable() {
13414         return true;
13415     }
13416 
13417     /**
13418      * @hide
13419      */
13420     @Override
getAccessibilitySelectionEnd()13421     public int getAccessibilitySelectionEnd() {
13422         return getSelectionEnd();
13423     }
13424 
13425     /**
13426      * @hide
13427      */
13428     @Override
setAccessibilitySelection(int start, int end)13429     public void setAccessibilitySelection(int start, int end) {
13430         if (getAccessibilitySelectionStart() == start
13431                 && getAccessibilitySelectionEnd() == end) {
13432             return;
13433         }
13434         CharSequence text = getIterableTextForAccessibility();
13435         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
13436             Selection.setSelection((Spannable) text, start, end);
13437         } else {
13438             Selection.removeSelection((Spannable) text);
13439         }
13440         // Hide all selection controllers used for adjusting selection
13441         // since we are doing so explicitlty by other means and these
13442         // controllers interact with how selection behaves.
13443         if (mEditor != null) {
13444             mEditor.hideCursorAndSpanControllers();
13445             mEditor.stopTextActionMode();
13446         }
13447     }
13448 
13449     /** @hide */
13450     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)13451     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
13452         super.encodeProperties(stream);
13453 
13454         TruncateAt ellipsize = getEllipsize();
13455         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
13456         stream.addProperty("text:textSize", getTextSize());
13457         stream.addProperty("text:scaledTextSize", getScaledTextSize());
13458         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
13459         stream.addProperty("text:selectionStart", getSelectionStart());
13460         stream.addProperty("text:selectionEnd", getSelectionEnd());
13461         stream.addProperty("text:curTextColor", mCurTextColor);
13462         stream.addUserProperty("text:text", mText == null ? null : mText.toString());
13463         stream.addProperty("text:gravity", mGravity);
13464     }
13465 
13466     /**
13467      * User interface state that is stored by TextView for implementing
13468      * {@link View#onSaveInstanceState}.
13469      */
13470     public static class SavedState extends BaseSavedState {
13471         int selStart = -1;
13472         int selEnd = -1;
13473         @UnsupportedAppUsage
13474         CharSequence text;
13475         boolean frozenWithFocus;
13476         CharSequence error;
13477         ParcelableParcel editorState;  // Optional state from Editor.
13478 
SavedState(Parcelable superState)13479         SavedState(Parcelable superState) {
13480             super(superState);
13481         }
13482 
13483         @Override
writeToParcel(Parcel out, int flags)13484         public void writeToParcel(Parcel out, int flags) {
13485             super.writeToParcel(out, flags);
13486             out.writeInt(selStart);
13487             out.writeInt(selEnd);
13488             out.writeInt(frozenWithFocus ? 1 : 0);
13489             TextUtils.writeToParcel(text, out, flags);
13490 
13491             if (error == null) {
13492                 out.writeInt(0);
13493             } else {
13494                 out.writeInt(1);
13495                 TextUtils.writeToParcel(error, out, flags);
13496             }
13497 
13498             if (editorState == null) {
13499                 out.writeInt(0);
13500             } else {
13501                 out.writeInt(1);
13502                 editorState.writeToParcel(out, flags);
13503             }
13504         }
13505 
13506         @Override
toString()13507         public String toString() {
13508             String str = "TextView.SavedState{"
13509                     + Integer.toHexString(System.identityHashCode(this))
13510                     + " start=" + selStart + " end=" + selEnd;
13511             if (text != null) {
13512                 str += " text=" + text;
13513             }
13514             return str + "}";
13515         }
13516 
13517         @SuppressWarnings("hiding")
13518         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
13519                 new Parcelable.Creator<SavedState>() {
13520                     public SavedState createFromParcel(Parcel in) {
13521                         return new SavedState(in);
13522                     }
13523 
13524                     public SavedState[] newArray(int size) {
13525                         return new SavedState[size];
13526                     }
13527                 };
13528 
SavedState(Parcel in)13529         private SavedState(Parcel in) {
13530             super(in);
13531             selStart = in.readInt();
13532             selEnd = in.readInt();
13533             frozenWithFocus = (in.readInt() != 0);
13534             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13535 
13536             if (in.readInt() != 0) {
13537                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13538             }
13539 
13540             if (in.readInt() != 0) {
13541                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
13542             }
13543         }
13544     }
13545 
13546     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
13547         private char[] mChars;
13548         private int mStart, mLength;
13549 
CharWrapper(char[] chars, int start, int len)13550         public CharWrapper(char[] chars, int start, int len) {
13551             mChars = chars;
13552             mStart = start;
13553             mLength = len;
13554         }
13555 
set(char[] chars, int start, int len)13556         /* package */ void set(char[] chars, int start, int len) {
13557             mChars = chars;
13558             mStart = start;
13559             mLength = len;
13560         }
13561 
length()13562         public int length() {
13563             return mLength;
13564         }
13565 
charAt(int off)13566         public char charAt(int off) {
13567             return mChars[off + mStart];
13568         }
13569 
13570         @Override
toString()13571         public String toString() {
13572             return new String(mChars, mStart, mLength);
13573         }
13574 
subSequence(int start, int end)13575         public CharSequence subSequence(int start, int end) {
13576             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13577                 throw new IndexOutOfBoundsException(start + ", " + end);
13578             }
13579 
13580             return new String(mChars, start + mStart, end - start);
13581         }
13582 
getChars(int start, int end, char[] buf, int off)13583         public void getChars(int start, int end, char[] buf, int off) {
13584             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13585                 throw new IndexOutOfBoundsException(start + ", " + end);
13586             }
13587 
13588             System.arraycopy(mChars, start + mStart, buf, off, end - start);
13589         }
13590 
13591         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13592         public void drawText(BaseCanvas c, int start, int end,
13593                              float x, float y, Paint p) {
13594             c.drawText(mChars, start + mStart, end - start, x, y, p);
13595         }
13596 
13597         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13598         public void drawTextRun(BaseCanvas c, int start, int end,
13599                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
13600             int count = end - start;
13601             int contextCount = contextEnd - contextStart;
13602             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
13603                     contextCount, x, y, isRtl, p);
13604         }
13605 
measureText(int start, int end, Paint p)13606         public float measureText(int start, int end, Paint p) {
13607             return p.measureText(mChars, start + mStart, end - start);
13608         }
13609 
getTextWidths(int start, int end, float[] widths, Paint p)13610         public int getTextWidths(int start, int end, float[] widths, Paint p) {
13611             return p.getTextWidths(mChars, start + mStart, end - start, widths);
13612         }
13613 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13614         public float getTextRunAdvances(int start, int end, int contextStart,
13615                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
13616                 Paint p) {
13617             int count = end - start;
13618             int contextCount = contextEnd - contextStart;
13619             return p.getTextRunAdvances(mChars, start + mStart, count,
13620                     contextStart + mStart, contextCount, isRtl, advances,
13621                     advancesIndex);
13622         }
13623 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13624         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
13625                 int offset, int cursorOpt, Paint p) {
13626             int contextCount = contextEnd - contextStart;
13627             return p.getTextRunCursor(mChars, contextStart + mStart,
13628                     contextCount, isRtl, offset + mStart, cursorOpt);
13629         }
13630     }
13631 
13632     private static final class Marquee {
13633         // TODO: Add an option to configure this
13634         private static final float MARQUEE_DELTA_MAX = 0.07f;
13635         private static final int MARQUEE_DELAY = 1200;
13636         private static final int MARQUEE_DP_PER_SECOND = 30;
13637 
13638         private static final byte MARQUEE_STOPPED = 0x0;
13639         private static final byte MARQUEE_STARTING = 0x1;
13640         private static final byte MARQUEE_RUNNING = 0x2;
13641 
13642         private final WeakReference<TextView> mView;
13643         private final Choreographer mChoreographer;
13644 
13645         private byte mStatus = MARQUEE_STOPPED;
13646         private final float mPixelsPerMs;
13647         private float mMaxScroll;
13648         private float mMaxFadeScroll;
13649         private float mGhostStart;
13650         private float mGhostOffset;
13651         private float mFadeStop;
13652         private int mRepeatLimit;
13653 
13654         private float mScroll;
13655         private long mLastAnimationMs;
13656 
Marquee(TextView v)13657         Marquee(TextView v) {
13658             final float density = v.getContext().getResources().getDisplayMetrics().density;
13659             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
13660             mView = new WeakReference<TextView>(v);
13661             mChoreographer = Choreographer.getInstance();
13662         }
13663 
13664         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
13665             @Override
13666             public void doFrame(long frameTimeNanos) {
13667                 tick();
13668             }
13669         };
13670 
13671         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
13672             @Override
13673             public void doFrame(long frameTimeNanos) {
13674                 mStatus = MARQUEE_RUNNING;
13675                 mLastAnimationMs = mChoreographer.getFrameTime();
13676                 tick();
13677             }
13678         };
13679 
13680         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
13681             @Override
13682             public void doFrame(long frameTimeNanos) {
13683                 if (mStatus == MARQUEE_RUNNING) {
13684                     if (mRepeatLimit >= 0) {
13685                         mRepeatLimit--;
13686                     }
13687                     start(mRepeatLimit);
13688                 }
13689             }
13690         };
13691 
tick()13692         void tick() {
13693             if (mStatus != MARQUEE_RUNNING) {
13694                 return;
13695             }
13696 
13697             mChoreographer.removeFrameCallback(mTickCallback);
13698 
13699             final TextView textView = mView.get();
13700             if (textView != null && (textView.isFocused() || textView.isSelected())) {
13701                 long currentMs = mChoreographer.getFrameTime();
13702                 long deltaMs = currentMs - mLastAnimationMs;
13703                 mLastAnimationMs = currentMs;
13704                 float deltaPx = deltaMs * mPixelsPerMs;
13705                 mScroll += deltaPx;
13706                 if (mScroll > mMaxScroll) {
13707                     mScroll = mMaxScroll;
13708                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
13709                 } else {
13710                     mChoreographer.postFrameCallback(mTickCallback);
13711                 }
13712                 textView.invalidate();
13713             }
13714         }
13715 
stop()13716         void stop() {
13717             mStatus = MARQUEE_STOPPED;
13718             mChoreographer.removeFrameCallback(mStartCallback);
13719             mChoreographer.removeFrameCallback(mRestartCallback);
13720             mChoreographer.removeFrameCallback(mTickCallback);
13721             resetScroll();
13722         }
13723 
resetScroll()13724         private void resetScroll() {
13725             mScroll = 0.0f;
13726             final TextView textView = mView.get();
13727             if (textView != null) textView.invalidate();
13728         }
13729 
start(int repeatLimit)13730         void start(int repeatLimit) {
13731             if (repeatLimit == 0) {
13732                 stop();
13733                 return;
13734             }
13735             mRepeatLimit = repeatLimit;
13736             final TextView textView = mView.get();
13737             if (textView != null && textView.mLayout != null) {
13738                 mStatus = MARQUEE_STARTING;
13739                 mScroll = 0.0f;
13740                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
13741                         - textView.getCompoundPaddingRight();
13742                 final float lineWidth = textView.mLayout.getLineWidth(0);
13743                 final float gap = textWidth / 3.0f;
13744                 mGhostStart = lineWidth - textWidth + gap;
13745                 mMaxScroll = mGhostStart + textWidth;
13746                 mGhostOffset = lineWidth + gap;
13747                 mFadeStop = lineWidth + textWidth / 6.0f;
13748                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
13749 
13750                 textView.invalidate();
13751                 mChoreographer.postFrameCallback(mStartCallback);
13752             }
13753         }
13754 
getGhostOffset()13755         float getGhostOffset() {
13756             return mGhostOffset;
13757         }
13758 
getScroll()13759         float getScroll() {
13760             return mScroll;
13761         }
13762 
getMaxFadeScroll()13763         float getMaxFadeScroll() {
13764             return mMaxFadeScroll;
13765         }
13766 
shouldDrawLeftFade()13767         boolean shouldDrawLeftFade() {
13768             return mScroll <= mFadeStop;
13769         }
13770 
shouldDrawGhost()13771         boolean shouldDrawGhost() {
13772             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
13773         }
13774 
isRunning()13775         boolean isRunning() {
13776             return mStatus == MARQUEE_RUNNING;
13777         }
13778 
isStopped()13779         boolean isStopped() {
13780             return mStatus == MARQUEE_STOPPED;
13781         }
13782     }
13783 
13784     private class ChangeWatcher implements TextWatcher, SpanWatcher {
13785 
13786         private CharSequence mBeforeText;
13787 
beforeTextChanged(CharSequence buffer, int start, int before, int after)13788         public void beforeTextChanged(CharSequence buffer, int start,
13789                                       int before, int after) {
13790             if (DEBUG_EXTRACT) {
13791                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
13792                         + " before=" + before + " after=" + after + ": " + buffer);
13793             }
13794 
13795             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
13796                 mBeforeText = mTransformed.toString();
13797             }
13798 
13799             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
13800         }
13801 
onTextChanged(CharSequence buffer, int start, int before, int after)13802         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
13803             if (DEBUG_EXTRACT) {
13804                 Log.v(LOG_TAG, "onTextChanged start=" + start
13805                         + " before=" + before + " after=" + after + ": " + buffer);
13806             }
13807             TextView.this.handleTextChanged(buffer, start, before, after);
13808 
13809             if (AccessibilityManager.getInstance(mContext).isEnabled()
13810                     && (isFocused() || isSelected() && isShown())) {
13811                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
13812                 mBeforeText = null;
13813             }
13814         }
13815 
afterTextChanged(Editable buffer)13816         public void afterTextChanged(Editable buffer) {
13817             if (DEBUG_EXTRACT) {
13818                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
13819             }
13820             TextView.this.sendAfterTextChanged(buffer);
13821 
13822             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
13823                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
13824             }
13825         }
13826 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13827         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
13828             if (DEBUG_EXTRACT) {
13829                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
13830                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
13831             }
13832             TextView.this.spanChange(buf, what, s, st, e, en);
13833         }
13834 
onSpanAdded(Spannable buf, Object what, int s, int e)13835         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
13836             if (DEBUG_EXTRACT) {
13837                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
13838             }
13839             TextView.this.spanChange(buf, what, -1, s, -1, e);
13840         }
13841 
onSpanRemoved(Spannable buf, Object what, int s, int e)13842         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
13843             if (DEBUG_EXTRACT) {
13844                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
13845             }
13846             TextView.this.spanChange(buf, what, s, -1, e, -1);
13847         }
13848     }
13849 
13850     /** @hide */
13851     @Override
onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)13852     public void onInputConnectionOpenedInternal(@NonNull InputConnection ic,
13853             @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
13854         if (mEditor != null) {
13855             mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic,
13856                     editorInfo);
13857         }
13858     }
13859 
13860     /** @hide */
13861     @Override
onInputConnectionClosedInternal()13862     public void onInputConnectionClosedInternal() {
13863         if (mEditor != null) {
13864             mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo();
13865         }
13866     }
13867 
13868     /**
13869      * Default {@link TextView} implementation for receiving content. Apps wishing to provide
13870      * custom behavior should configure a listener via {@link #setOnReceiveContentListener}.
13871      *
13872      * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in
13873      * content without acting on it).
13874      *
13875      * <p>For editable TextViews the default behavior is to insert text into the view, coercing
13876      * non-text content to text as needed. The MIME types "text/plain" and "text/html" have
13877      * well-defined behavior for this, while other MIME types have reasonable fallback behavior
13878      * (see {@link ClipData.Item#coerceToStyledText}).
13879      *
13880      * @param payload The content to insert and related metadata.
13881      *
13882      * @return The portion of the passed-in content that was not handled (may be all, some, or none
13883      * of the passed-in content).
13884      */
13885     @Nullable
13886     @Override
onReceiveContent(@onNull ContentInfo payload)13887     public ContentInfo onReceiveContent(@NonNull ContentInfo payload) {
13888         if (mEditor != null) {
13889             return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload);
13890         }
13891         return payload;
13892     }
13893 
logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)13894     private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
13895         if (msgFormat == null) {
13896             Log.d(LOG_TAG, location);
13897         } else {
13898             Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs));
13899         }
13900     }
13901 
13902     /**
13903      * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
13904      * the view.
13905      *
13906      * <p>NOTE: When overriding the method, it should not translate the password. If the subclass
13907      * uses {@link TransformationMethod} to display the translated result, it's also not recommend
13908      * to translate text is selectable or editable.
13909      *
13910      * @param supportedFormats the supported translation format. The value could be {@link
13911      *                         android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
13912      * @return the {@link ViewTranslationRequest} which contains the information to be translated.
13913      */
13914     @Override
onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)13915     public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats,
13916             @NonNull Consumer<ViewTranslationRequest> requestsCollector) {
13917         if (supportedFormats == null || supportedFormats.length == 0) {
13918             if (UiTranslationController.DEBUG) {
13919                 Log.w(LOG_TAG, "Do not provide the support translation formats.");
13920             }
13921             return;
13922         }
13923         ViewTranslationRequest.Builder requestBuilder =
13924                 new ViewTranslationRequest.Builder(getAutofillId());
13925         // Support Text translation
13926         if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) {
13927             if (mText == null || mText.length() == 0) {
13928                 if (UiTranslationController.DEBUG) {
13929                     Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
13930                 }
13931                 return;
13932             }
13933             boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
13934             // TODO(b/177214256): support selectable text translation.
13935             //  We use the TransformationMethod to implement showing the translated text. The
13936             //  TextView does not support the text length change for TransformationMethod. If the
13937             //  text is selectable or editable, it will crash while selecting the text. To support
13938             //  it, it needs broader changes to text APIs, we only allow to translate non selectable
13939             //  and editable text in S.
13940             if (isTextEditable() || isPassword || isTextSelectable()) {
13941                 if (UiTranslationController.DEBUG) {
13942                     Log.w(LOG_TAG, "Cannot create translation request. editable = "
13943                             + isTextEditable() + ", isPassword = " + isPassword + ", selectable = "
13944                             + isTextSelectable());
13945                 }
13946                 return;
13947             }
13948             // TODO(b/176488462): apply the view's important for translation
13949             requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
13950                     TranslationRequestValue.forText(mText));
13951             if (!TextUtils.isEmpty(getContentDescription())) {
13952                 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION,
13953                         TranslationRequestValue.forText(getContentDescription()));
13954             }
13955         }
13956         requestsCollector.accept(requestBuilder.build());
13957     }
13958 }
13959