• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY;
21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
23 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
24 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
25 
26 import android.R;
27 import android.annotation.CallSuper;
28 import android.annotation.CheckResult;
29 import android.annotation.ColorInt;
30 import android.annotation.DrawableRes;
31 import android.annotation.FloatRange;
32 import android.annotation.IntDef;
33 import android.annotation.IntRange;
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.annotation.Px;
37 import android.annotation.RequiresPermission;
38 import android.annotation.Size;
39 import android.annotation.StringRes;
40 import android.annotation.StyleRes;
41 import android.annotation.XmlRes;
42 import android.app.Activity;
43 import android.app.PendingIntent;
44 import android.app.assist.AssistStructure;
45 import android.compat.annotation.UnsupportedAppUsage;
46 import android.content.ClipData;
47 import android.content.ClipDescription;
48 import android.content.ClipboardManager;
49 import android.content.Context;
50 import android.content.Intent;
51 import android.content.UndoManager;
52 import android.content.pm.PackageManager;
53 import android.content.res.ColorStateList;
54 import android.content.res.CompatibilityInfo;
55 import android.content.res.Configuration;
56 import android.content.res.Resources;
57 import android.content.res.TypedArray;
58 import android.content.res.XmlResourceParser;
59 import android.graphics.BaseCanvas;
60 import android.graphics.BlendMode;
61 import android.graphics.Canvas;
62 import android.graphics.Insets;
63 import android.graphics.Paint;
64 import android.graphics.Paint.FontMetricsInt;
65 import android.graphics.Path;
66 import android.graphics.PorterDuff;
67 import android.graphics.Rect;
68 import android.graphics.RectF;
69 import android.graphics.Typeface;
70 import android.graphics.drawable.Drawable;
71 import android.graphics.fonts.FontStyle;
72 import android.graphics.fonts.FontVariationAxis;
73 import android.icu.text.DecimalFormatSymbols;
74 import android.os.AsyncTask;
75 import android.os.Build;
76 import android.os.Build.VERSION_CODES;
77 import android.os.Bundle;
78 import android.os.LocaleList;
79 import android.os.Parcel;
80 import android.os.Parcelable;
81 import android.os.ParcelableParcel;
82 import android.os.Process;
83 import android.os.SystemClock;
84 import android.os.UserHandle;
85 import android.provider.Settings;
86 import android.text.BoringLayout;
87 import android.text.DynamicLayout;
88 import android.text.Editable;
89 import android.text.GetChars;
90 import android.text.GraphicsOperations;
91 import android.text.InputFilter;
92 import android.text.InputType;
93 import android.text.Layout;
94 import android.text.ParcelableSpan;
95 import android.text.PrecomputedText;
96 import android.text.Selection;
97 import android.text.SpanWatcher;
98 import android.text.Spannable;
99 import android.text.SpannableStringBuilder;
100 import android.text.Spanned;
101 import android.text.SpannedString;
102 import android.text.StaticLayout;
103 import android.text.TextDirectionHeuristic;
104 import android.text.TextDirectionHeuristics;
105 import android.text.TextPaint;
106 import android.text.TextUtils;
107 import android.text.TextUtils.TruncateAt;
108 import android.text.TextWatcher;
109 import android.text.method.AllCapsTransformationMethod;
110 import android.text.method.ArrowKeyMovementMethod;
111 import android.text.method.DateKeyListener;
112 import android.text.method.DateTimeKeyListener;
113 import android.text.method.DialerKeyListener;
114 import android.text.method.DigitsKeyListener;
115 import android.text.method.KeyListener;
116 import android.text.method.LinkMovementMethod;
117 import android.text.method.MetaKeyKeyListener;
118 import android.text.method.MovementMethod;
119 import android.text.method.PasswordTransformationMethod;
120 import android.text.method.SingleLineTransformationMethod;
121 import android.text.method.TextKeyListener;
122 import android.text.method.TimeKeyListener;
123 import android.text.method.TransformationMethod;
124 import android.text.method.TransformationMethod2;
125 import android.text.method.WordIterator;
126 import android.text.style.CharacterStyle;
127 import android.text.style.ClickableSpan;
128 import android.text.style.ParagraphStyle;
129 import android.text.style.SpellCheckSpan;
130 import android.text.style.SuggestionSpan;
131 import android.text.style.URLSpan;
132 import android.text.style.UpdateAppearance;
133 import android.text.util.Linkify;
134 import android.util.AttributeSet;
135 import android.util.DisplayMetrics;
136 import android.util.IntArray;
137 import android.util.Log;
138 import android.util.SparseIntArray;
139 import android.util.TypedValue;
140 import android.view.AccessibilityIterators.TextSegmentIterator;
141 import android.view.ActionMode;
142 import android.view.Choreographer;
143 import android.view.ContextMenu;
144 import android.view.DragEvent;
145 import android.view.Gravity;
146 import android.view.HapticFeedbackConstants;
147 import android.view.InputDevice;
148 import android.view.KeyCharacterMap;
149 import android.view.KeyEvent;
150 import android.view.MotionEvent;
151 import android.view.PointerIcon;
152 import android.view.View;
153 import android.view.ViewConfiguration;
154 import android.view.ViewDebug;
155 import android.view.ViewGroup.LayoutParams;
156 import android.view.ViewHierarchyEncoder;
157 import android.view.ViewParent;
158 import android.view.ViewRootImpl;
159 import android.view.ViewStructure;
160 import android.view.ViewTreeObserver;
161 import android.view.accessibility.AccessibilityEvent;
162 import android.view.accessibility.AccessibilityManager;
163 import android.view.accessibility.AccessibilityNodeInfo;
164 import android.view.animation.AnimationUtils;
165 import android.view.autofill.AutofillManager;
166 import android.view.autofill.AutofillValue;
167 import android.view.contentcapture.ContentCaptureManager;
168 import android.view.contentcapture.ContentCaptureSession;
169 import android.view.inputmethod.BaseInputConnection;
170 import android.view.inputmethod.CompletionInfo;
171 import android.view.inputmethod.CorrectionInfo;
172 import android.view.inputmethod.CursorAnchorInfo;
173 import android.view.inputmethod.EditorInfo;
174 import android.view.inputmethod.ExtractedText;
175 import android.view.inputmethod.ExtractedTextRequest;
176 import android.view.inputmethod.InputConnection;
177 import android.view.inputmethod.InputMethodManager;
178 import android.view.inspector.InspectableProperty;
179 import android.view.inspector.InspectableProperty.EnumEntry;
180 import android.view.inspector.InspectableProperty.FlagEntry;
181 import android.view.textclassifier.TextClassification;
182 import android.view.textclassifier.TextClassificationContext;
183 import android.view.textclassifier.TextClassificationManager;
184 import android.view.textclassifier.TextClassifier;
185 import android.view.textclassifier.TextLinks;
186 import android.view.textservice.SpellCheckerSubtype;
187 import android.view.textservice.TextServicesManager;
188 import android.widget.RemoteViews.RemoteView;
189 
190 import com.android.internal.annotations.VisibleForTesting;
191 import com.android.internal.logging.MetricsLogger;
192 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
193 import com.android.internal.util.FastMath;
194 import com.android.internal.util.Preconditions;
195 import com.android.internal.widget.EditableInputConnection;
196 
197 import libcore.util.EmptyArray;
198 
199 import org.xmlpull.v1.XmlPullParserException;
200 
201 import java.io.IOException;
202 import java.lang.annotation.Retention;
203 import java.lang.annotation.RetentionPolicy;
204 import java.lang.ref.WeakReference;
205 import java.util.ArrayList;
206 import java.util.Arrays;
207 import java.util.Locale;
208 import java.util.Objects;
209 import java.util.concurrent.CompletableFuture;
210 import java.util.concurrent.TimeUnit;
211 import java.util.function.Consumer;
212 import java.util.function.Supplier;
213 
214 /**
215  * A user interface element that displays text to the user.
216  * To provide user-editable text, see {@link EditText}.
217  * <p>
218  * The following code sample shows a typical use, with an XML layout
219  * and code to modify the contents of the text view:
220  * </p>
221 
222  * <pre>
223  * &lt;LinearLayout
224        xmlns:android="http://schemas.android.com/apk/res/android"
225        android:layout_width="match_parent"
226        android:layout_height="match_parent"&gt;
227  *    &lt;TextView
228  *        android:id="@+id/text_view_id"
229  *        android:layout_height="wrap_content"
230  *        android:layout_width="wrap_content"
231  *        android:text="@string/hello" /&gt;
232  * &lt;/LinearLayout&gt;
233  * </pre>
234  * <p>
235  * This code sample demonstrates how to modify the contents of the text view
236  * defined in the previous XML layout:
237  * </p>
238  * <pre>
239  * public class MainActivity extends Activity {
240  *
241  *    protected void onCreate(Bundle savedInstanceState) {
242  *         super.onCreate(savedInstanceState);
243  *         setContentView(R.layout.activity_main);
244  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
245  *         helloTextView.setText(R.string.user_greeting);
246  *     }
247  * }
248  * </pre>
249  * <p>
250  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
251  * </p>
252  * <p>
253  * <b>XML attributes</b>
254  * <p>
255  * See {@link android.R.styleable#TextView TextView Attributes},
256  * {@link android.R.styleable#View View Attributes}
257  *
258  * @attr ref android.R.styleable#TextView_text
259  * @attr ref android.R.styleable#TextView_bufferType
260  * @attr ref android.R.styleable#TextView_hint
261  * @attr ref android.R.styleable#TextView_textColor
262  * @attr ref android.R.styleable#TextView_textColorHighlight
263  * @attr ref android.R.styleable#TextView_textColorHint
264  * @attr ref android.R.styleable#TextView_textAppearance
265  * @attr ref android.R.styleable#TextView_textColorLink
266  * @attr ref android.R.styleable#TextView_textFontWeight
267  * @attr ref android.R.styleable#TextView_textSize
268  * @attr ref android.R.styleable#TextView_textScaleX
269  * @attr ref android.R.styleable#TextView_fontFamily
270  * @attr ref android.R.styleable#TextView_typeface
271  * @attr ref android.R.styleable#TextView_textStyle
272  * @attr ref android.R.styleable#TextView_cursorVisible
273  * @attr ref android.R.styleable#TextView_maxLines
274  * @attr ref android.R.styleable#TextView_maxHeight
275  * @attr ref android.R.styleable#TextView_lines
276  * @attr ref android.R.styleable#TextView_height
277  * @attr ref android.R.styleable#TextView_minLines
278  * @attr ref android.R.styleable#TextView_minHeight
279  * @attr ref android.R.styleable#TextView_maxEms
280  * @attr ref android.R.styleable#TextView_maxWidth
281  * @attr ref android.R.styleable#TextView_ems
282  * @attr ref android.R.styleable#TextView_width
283  * @attr ref android.R.styleable#TextView_minEms
284  * @attr ref android.R.styleable#TextView_minWidth
285  * @attr ref android.R.styleable#TextView_gravity
286  * @attr ref android.R.styleable#TextView_scrollHorizontally
287  * @attr ref android.R.styleable#TextView_password
288  * @attr ref android.R.styleable#TextView_singleLine
289  * @attr ref android.R.styleable#TextView_selectAllOnFocus
290  * @attr ref android.R.styleable#TextView_includeFontPadding
291  * @attr ref android.R.styleable#TextView_maxLength
292  * @attr ref android.R.styleable#TextView_shadowColor
293  * @attr ref android.R.styleable#TextView_shadowDx
294  * @attr ref android.R.styleable#TextView_shadowDy
295  * @attr ref android.R.styleable#TextView_shadowRadius
296  * @attr ref android.R.styleable#TextView_autoLink
297  * @attr ref android.R.styleable#TextView_linksClickable
298  * @attr ref android.R.styleable#TextView_numeric
299  * @attr ref android.R.styleable#TextView_digits
300  * @attr ref android.R.styleable#TextView_phoneNumber
301  * @attr ref android.R.styleable#TextView_inputMethod
302  * @attr ref android.R.styleable#TextView_capitalize
303  * @attr ref android.R.styleable#TextView_autoText
304  * @attr ref android.R.styleable#TextView_editable
305  * @attr ref android.R.styleable#TextView_freezesText
306  * @attr ref android.R.styleable#TextView_ellipsize
307  * @attr ref android.R.styleable#TextView_drawableTop
308  * @attr ref android.R.styleable#TextView_drawableBottom
309  * @attr ref android.R.styleable#TextView_drawableRight
310  * @attr ref android.R.styleable#TextView_drawableLeft
311  * @attr ref android.R.styleable#TextView_drawableStart
312  * @attr ref android.R.styleable#TextView_drawableEnd
313  * @attr ref android.R.styleable#TextView_drawablePadding
314  * @attr ref android.R.styleable#TextView_drawableTint
315  * @attr ref android.R.styleable#TextView_drawableTintMode
316  * @attr ref android.R.styleable#TextView_lineSpacingExtra
317  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
318  * @attr ref android.R.styleable#TextView_justificationMode
319  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
320  * @attr ref android.R.styleable#TextView_inputType
321  * @attr ref android.R.styleable#TextView_imeOptions
322  * @attr ref android.R.styleable#TextView_privateImeOptions
323  * @attr ref android.R.styleable#TextView_imeActionLabel
324  * @attr ref android.R.styleable#TextView_imeActionId
325  * @attr ref android.R.styleable#TextView_editorExtras
326  * @attr ref android.R.styleable#TextView_elegantTextHeight
327  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
328  * @attr ref android.R.styleable#TextView_letterSpacing
329  * @attr ref android.R.styleable#TextView_fontFeatureSettings
330  * @attr ref android.R.styleable#TextView_fontVariationSettings
331  * @attr ref android.R.styleable#TextView_breakStrategy
332  * @attr ref android.R.styleable#TextView_hyphenationFrequency
333  * @attr ref android.R.styleable#TextView_autoSizeTextType
334  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
335  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
336  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
337  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
338  * @attr ref android.R.styleable#TextView_textCursorDrawable
339  * @attr ref android.R.styleable#TextView_textSelectHandle
340  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
341  * @attr ref android.R.styleable#TextView_textSelectHandleRight
342  * @attr ref android.R.styleable#TextView_allowUndo
343  * @attr ref android.R.styleable#TextView_enabled
344  */
345 @RemoteView
346 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
347     static final String LOG_TAG = "TextView";
348     static final boolean DEBUG_EXTRACT = false;
349     static final boolean DEBUG_CURSOR = false;
350 
351     private static final float[] TEMP_POSITION = new float[2];
352 
353     // Enum for the "typeface" XML parameter.
354     // TODO: How can we get this from the XML instead of hardcoding it here?
355     /** @hide */
356     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
357     @Retention(RetentionPolicy.SOURCE)
358     public @interface XMLTypefaceAttr{}
359     private static final int DEFAULT_TYPEFACE = -1;
360     private static final int SANS = 1;
361     private static final int SERIF = 2;
362     private static final int MONOSPACE = 3;
363 
364     // Enum for the "ellipsize" XML parameter.
365     private static final int ELLIPSIZE_NOT_SET = -1;
366     private static final int ELLIPSIZE_NONE = 0;
367     private static final int ELLIPSIZE_START = 1;
368     private static final int ELLIPSIZE_MIDDLE = 2;
369     private static final int ELLIPSIZE_END = 3;
370     private static final int ELLIPSIZE_MARQUEE = 4;
371 
372     // Bitfield for the "numeric" XML parameter.
373     // TODO: How can we get this from the XML instead of hardcoding it here?
374     private static final int SIGNED = 2;
375     private static final int DECIMAL = 4;
376 
377     /**
378      * Draw marquee text with fading edges as usual
379      */
380     private static final int MARQUEE_FADE_NORMAL = 0;
381 
382     /**
383      * Draw marquee text as ellipsize end while inactive instead of with the fade.
384      * (Useful for devices where the fade can be expensive if overdone)
385      */
386     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
387 
388     /**
389      * Draw marquee text with fading edges because it is currently active/animating.
390      */
391     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
392 
393     @UnsupportedAppUsage
394     private static final int LINES = 1;
395     private static final int EMS = LINES;
396     private static final int PIXELS = 2;
397 
398     // Maximum text length for single line input.
399     private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
400     private InputFilter.LengthFilter mSingleLineLengthFilter = null;
401 
402     private static final RectF TEMP_RECTF = new RectF();
403 
404     /** @hide */
405     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
406     private static final int ANIMATED_SCROLL_GAP = 250;
407 
408     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
409     private static final Spanned EMPTY_SPANNED = new SpannedString("");
410 
411     private static final int CHANGE_WATCHER_PRIORITY = 100;
412 
413     // New state used to change background based on whether this TextView is multiline.
414     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
415 
416     // Accessibility action to share selected text.
417     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
418 
419     /**
420      * @hide
421      */
422     // Accessibility action start id for "process text" actions.
423     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
424 
425     /**
426      * @hide
427      */
428     static final int PROCESS_TEXT_REQUEST_CODE = 100;
429 
430     /**
431      *  Return code of {@link #doKeyDown}.
432      */
433     private static final int KEY_EVENT_NOT_HANDLED = 0;
434     private static final int KEY_EVENT_HANDLED = -1;
435     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
436     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
437 
438     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
439 
440     // System wide time for last cut, copy or text changed action.
441     static long sLastCutCopyOrTextChangedTime;
442 
443     private ColorStateList mTextColor;
444     private ColorStateList mHintTextColor;
445     private ColorStateList mLinkTextColor;
446     @ViewDebug.ExportedProperty(category = "text")
447 
448     /**
449      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
450      */
451     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
452     private int mCurTextColor;
453 
454     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
455     private int mCurHintTextColor;
456     private boolean mFreezesText;
457 
458     @UnsupportedAppUsage
459     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
460     @UnsupportedAppUsage
461     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
462 
463     @UnsupportedAppUsage
464     private float mShadowRadius;
465     @UnsupportedAppUsage
466     private float mShadowDx;
467     @UnsupportedAppUsage
468     private float mShadowDy;
469     private int mShadowColor;
470 
471     private boolean mPreDrawRegistered;
472     private boolean mPreDrawListenerDetached;
473 
474     private TextClassifier mTextClassifier;
475     private TextClassifier mTextClassificationSession;
476     private TextClassificationContext mTextClassificationContext;
477 
478     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
479     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
480     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
481     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
482     // into by the user holding the movement key down) then we shouldn't prevent the focus from
483     // changing.
484     private boolean mPreventDefaultMovement;
485 
486     private TextUtils.TruncateAt mEllipsize;
487 
488     static class Drawables {
489         static final int LEFT = 0;
490         static final int TOP = 1;
491         static final int RIGHT = 2;
492         static final int BOTTOM = 3;
493 
494         static final int DRAWABLE_NONE = -1;
495         static final int DRAWABLE_RIGHT = 0;
496         static final int DRAWABLE_LEFT = 1;
497 
498         final Rect mCompoundRect = new Rect();
499 
500         final Drawable[] mShowing = new Drawable[4];
501 
502         ColorStateList mTintList;
503         BlendMode mBlendMode;
504         boolean mHasTint;
505         boolean mHasTintMode;
506 
507         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
508         Drawable mDrawableLeftInitial, mDrawableRightInitial;
509 
510         boolean mIsRtlCompatibilityMode;
511         boolean mOverride;
512 
513         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
514                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
515 
516         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
517                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
518 
519         int mDrawablePadding;
520 
521         int mDrawableSaved = DRAWABLE_NONE;
522 
Drawables(Context context)523         public Drawables(Context context) {
524             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
525             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
526                     || !context.getApplicationInfo().hasRtlSupport();
527             mOverride = false;
528         }
529 
530         /**
531          * @return {@code true} if this object contains metadata that needs to
532          *         be retained, {@code false} otherwise
533          */
534         public boolean hasMetadata() {
535             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
536         }
537 
538         /**
539          * Updates the list of displayed drawables to account for the current
540          * layout direction.
541          *
542          * @param layoutDirection the current layout direction
543          * @return {@code true} if the displayed drawables changed
544          */
545         public boolean resolveWithLayoutDirection(int layoutDirection) {
546             final Drawable previousLeft = mShowing[Drawables.LEFT];
547             final Drawable previousRight = mShowing[Drawables.RIGHT];
548 
549             // First reset "left" and "right" drawables to their initial values
550             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
551             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
552 
553             if (mIsRtlCompatibilityMode) {
554                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
555                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
556                     mShowing[Drawables.LEFT] = mDrawableStart;
557                     mDrawableSizeLeft = mDrawableSizeStart;
558                     mDrawableHeightLeft = mDrawableHeightStart;
559                 }
560                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
561                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
562                     mShowing[Drawables.RIGHT] = mDrawableEnd;
563                     mDrawableSizeRight = mDrawableSizeEnd;
564                     mDrawableHeightRight = mDrawableHeightEnd;
565                 }
566             } else {
567                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
568                 // drawable if and only if they have been defined
569                 switch(layoutDirection) {
570                     case LAYOUT_DIRECTION_RTL:
571                         if (mOverride) {
572                             mShowing[Drawables.RIGHT] = mDrawableStart;
573                             mDrawableSizeRight = mDrawableSizeStart;
574                             mDrawableHeightRight = mDrawableHeightStart;
575 
576                             mShowing[Drawables.LEFT] = mDrawableEnd;
577                             mDrawableSizeLeft = mDrawableSizeEnd;
578                             mDrawableHeightLeft = mDrawableHeightEnd;
579                         }
580                         break;
581 
582                     case LAYOUT_DIRECTION_LTR:
583                     default:
584                         if (mOverride) {
585                             mShowing[Drawables.LEFT] = mDrawableStart;
586                             mDrawableSizeLeft = mDrawableSizeStart;
587                             mDrawableHeightLeft = mDrawableHeightStart;
588 
589                             mShowing[Drawables.RIGHT] = mDrawableEnd;
590                             mDrawableSizeRight = mDrawableSizeEnd;
591                             mDrawableHeightRight = mDrawableHeightEnd;
592                         }
593                         break;
594                 }
595             }
596 
597             applyErrorDrawableIfNeeded(layoutDirection);
598 
599             return mShowing[Drawables.LEFT] != previousLeft
600                     || mShowing[Drawables.RIGHT] != previousRight;
601         }
602 
603         public void setErrorDrawable(Drawable dr, TextView tv) {
604             if (mDrawableError != dr && mDrawableError != null) {
605                 mDrawableError.setCallback(null);
606             }
607             mDrawableError = dr;
608 
609             if (mDrawableError != null) {
610                 final Rect compoundRect = mCompoundRect;
611                 final int[] state = tv.getDrawableState();
612 
613                 mDrawableError.setState(state);
614                 mDrawableError.copyBounds(compoundRect);
615                 mDrawableError.setCallback(tv);
616                 mDrawableSizeError = compoundRect.width();
617                 mDrawableHeightError = compoundRect.height();
618             } else {
619                 mDrawableSizeError = mDrawableHeightError = 0;
620             }
621         }
622 
623         private void applyErrorDrawableIfNeeded(int layoutDirection) {
624             // first restore the initial state if needed
625             switch (mDrawableSaved) {
626                 case DRAWABLE_LEFT:
627                     mShowing[Drawables.LEFT] = mDrawableTemp;
628                     mDrawableSizeLeft = mDrawableSizeTemp;
629                     mDrawableHeightLeft = mDrawableHeightTemp;
630                     break;
631                 case DRAWABLE_RIGHT:
632                     mShowing[Drawables.RIGHT] = mDrawableTemp;
633                     mDrawableSizeRight = mDrawableSizeTemp;
634                     mDrawableHeightRight = mDrawableHeightTemp;
635                     break;
636                 case DRAWABLE_NONE:
637                 default:
638             }
639             // then, if needed, assign the Error drawable to the correct location
640             if (mDrawableError != null) {
641                 switch(layoutDirection) {
642                     case LAYOUT_DIRECTION_RTL:
643                         mDrawableSaved = DRAWABLE_LEFT;
644 
645                         mDrawableTemp = mShowing[Drawables.LEFT];
646                         mDrawableSizeTemp = mDrawableSizeLeft;
647                         mDrawableHeightTemp = mDrawableHeightLeft;
648 
649                         mShowing[Drawables.LEFT] = mDrawableError;
650                         mDrawableSizeLeft = mDrawableSizeError;
651                         mDrawableHeightLeft = mDrawableHeightError;
652                         break;
653                     case LAYOUT_DIRECTION_LTR:
654                     default:
655                         mDrawableSaved = DRAWABLE_RIGHT;
656 
657                         mDrawableTemp = mShowing[Drawables.RIGHT];
658                         mDrawableSizeTemp = mDrawableSizeRight;
659                         mDrawableHeightTemp = mDrawableHeightRight;
660 
661                         mShowing[Drawables.RIGHT] = mDrawableError;
662                         mDrawableSizeRight = mDrawableSizeError;
663                         mDrawableHeightRight = mDrawableHeightError;
664                         break;
665                 }
666             }
667         }
668     }
669 
670     @UnsupportedAppUsage
671     Drawables mDrawables;
672 
673     @UnsupportedAppUsage
674     private CharWrapper mCharWrapper;
675 
676     @UnsupportedAppUsage(trackingBug = 124050217)
677     private Marquee mMarquee;
678     @UnsupportedAppUsage
679     private boolean mRestartMarquee;
680 
681     private int mMarqueeRepeatLimit = 3;
682 
683     private int mLastLayoutDirection = -1;
684 
685     /**
686      * On some devices the fading edges add a performance penalty if used
687      * extensively in the same layout. This mode indicates how the marquee
688      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
689      */
690     @UnsupportedAppUsage
691     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
692 
693     /**
694      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
695      * the layout that should be used when the mode switches.
696      */
697     @UnsupportedAppUsage
698     private Layout mSavedMarqueeModeLayout;
699 
700     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
701     @ViewDebug.ExportedProperty(category = "text")
702     @UnsupportedAppUsage
703     private @Nullable CharSequence mText;
704     private @Nullable Spannable mSpannable;
705     private @Nullable PrecomputedText mPrecomputed;
706 
707     @UnsupportedAppUsage
708     private CharSequence mTransformed;
709     @UnsupportedAppUsage
710     private BufferType mBufferType = BufferType.NORMAL;
711 
712     private CharSequence mHint;
713     @UnsupportedAppUsage
714     private Layout mHintLayout;
715 
716     private MovementMethod mMovement;
717 
718     private TransformationMethod mTransformation;
719     @UnsupportedAppUsage
720     private boolean mAllowTransformationLengthChange;
721     @UnsupportedAppUsage
722     private ChangeWatcher mChangeWatcher;
723 
724     @UnsupportedAppUsage(trackingBug = 123769451)
725     private ArrayList<TextWatcher> mListeners;
726 
727     // display attributes
728     @UnsupportedAppUsage
729     private final TextPaint mTextPaint;
730     @UnsupportedAppUsage
731     private boolean mUserSetTextScaleX;
732     @UnsupportedAppUsage
733     private Layout mLayout;
734     private boolean mLocalesChanged = false;
735     private int mTextSizeUnit = -1;
736 
737     // True if setKeyListener() has been explicitly called
738     private boolean mListenerChanged = false;
739     // True if internationalized input should be used for numbers and date and time.
740     private final boolean mUseInternationalizedInput;
741     // True if fallback fonts that end up getting used should be allowed to affect line spacing.
742     /* package */ boolean mUseFallbackLineSpacing;
743 
744     @ViewDebug.ExportedProperty(category = "text")
745     @UnsupportedAppUsage
746     private int mGravity = Gravity.TOP | Gravity.START;
747     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
748     private boolean mHorizontallyScrolling;
749 
750     private int mAutoLinkMask;
751     private boolean mLinksClickable = true;
752 
753     @UnsupportedAppUsage
754     private float mSpacingMult = 1.0f;
755     @UnsupportedAppUsage
756     private float mSpacingAdd = 0.0f;
757 
758     private int mBreakStrategy;
759     private int mHyphenationFrequency;
760     private int mJustificationMode;
761 
762     @UnsupportedAppUsage
763     private int mMaximum = Integer.MAX_VALUE;
764     @UnsupportedAppUsage
765     private int mMaxMode = LINES;
766     @UnsupportedAppUsage
767     private int mMinimum = 0;
768     @UnsupportedAppUsage
769     private int mMinMode = LINES;
770 
771     @UnsupportedAppUsage
772     private int mOldMaximum = mMaximum;
773     @UnsupportedAppUsage
774     private int mOldMaxMode = mMaxMode;
775 
776     @UnsupportedAppUsage
777     private int mMaxWidth = Integer.MAX_VALUE;
778     @UnsupportedAppUsage
779     private int mMaxWidthMode = PIXELS;
780     @UnsupportedAppUsage
781     private int mMinWidth = 0;
782     @UnsupportedAppUsage
783     private int mMinWidthMode = PIXELS;
784 
785     @UnsupportedAppUsage
786     private boolean mSingleLine;
787     @UnsupportedAppUsage
788     private int mDesiredHeightAtMeasure = -1;
789     @UnsupportedAppUsage
790     private boolean mIncludePad = true;
791     private int mDeferScroll = -1;
792 
793     // tmp primitives, so we don't alloc them on each draw
794     private Rect mTempRect;
795     private long mLastScroll;
796     private Scroller mScroller;
797     private TextPaint mTempTextPaint;
798 
799     @UnsupportedAppUsage
800     private BoringLayout.Metrics mBoring;
801     @UnsupportedAppUsage
802     private BoringLayout.Metrics mHintBoring;
803     @UnsupportedAppUsage
804     private BoringLayout mSavedLayout;
805     @UnsupportedAppUsage
806     private BoringLayout mSavedHintLayout;
807 
808     @UnsupportedAppUsage
809     private TextDirectionHeuristic mTextDir;
810 
811     private InputFilter[] mFilters = NO_FILTERS;
812 
813     /**
814      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
815      * the same as {@link Process#myUserHandle()}.
816      *
817      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
818      * other apps may need to set this so that the system can use right user's resources and
819      * services such as input methods and spell checkers.</p>
820      *
821      * @see #setTextOperationUser(UserHandle)
822      */
823     @Nullable
824     private UserHandle mTextOperationUser;
825 
826     private volatile Locale mCurrentSpellCheckerLocaleCache;
827 
828     // It is possible to have a selection even when mEditor is null (programmatically set, like when
829     // a link is pressed). These highlight-related fields do not go in mEditor.
830     @UnsupportedAppUsage
831     int mHighlightColor = 0x6633B5E5;
832     private Path mHighlightPath;
833     @UnsupportedAppUsage
834     private final Paint mHighlightPaint;
835     @UnsupportedAppUsage
836     private boolean mHighlightPathBogus = true;
837 
838     // Although these fields are specific to editable text, they are not added to Editor because
839     // they are defined by the TextView's style and are theme-dependent.
840     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
841     int mCursorDrawableRes;
842     private Drawable mCursorDrawable;
843     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
844     // by removing it, but we would break apps targeting <= P that use it by reflection.
845     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
846     int mTextSelectHandleLeftRes;
847     private Drawable mTextSelectHandleLeft;
848     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
849     // by removing it, but we would break apps targeting <= P that use it by reflection.
850     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
851     int mTextSelectHandleRightRes;
852     private Drawable mTextSelectHandleRight;
853     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
854     // by removing it, but we would break apps targeting <= P that use it by reflection.
855     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
856     int mTextSelectHandleRes;
857     private Drawable mTextSelectHandle;
858     int mTextEditSuggestionItemLayout;
859     int mTextEditSuggestionContainerLayout;
860     int mTextEditSuggestionHighlightStyle;
861 
862     private static final int NO_POINTER_ID = -1;
863     /**
864      * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among
865      * TextView and the handle views which are rendered on popup windows.
866      */
867     private int mPrimePointerId = NO_POINTER_ID;
868 
869     /**
870      * Whether the prime pointer is from the event delivered to selection handle or insertion
871      * handle.
872      */
873     private boolean mIsPrimePointerFromHandleView;
874 
875     /**
876      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
877      * See {@link #createEditorIfNeeded()}.
878      */
879     @UnsupportedAppUsage
880     private Editor mEditor;
881 
882     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
883     private static final int DEVICE_PROVISIONED_NO = 1;
884     private static final int DEVICE_PROVISIONED_YES = 2;
885 
886     /**
887      * Some special options such as sharing selected text should only be shown if the device
888      * is provisioned. Only check the provisioned state once for a given view instance.
889      */
890     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
891 
892     /**
893      * The TextView does not auto-size text (default).
894      */
895     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
896 
897     /**
898      * The TextView scales text size both horizontally and vertically to fit within the
899      * container.
900      */
901     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
902 
903     /** @hide */
904     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
905             AUTO_SIZE_TEXT_TYPE_NONE,
906             AUTO_SIZE_TEXT_TYPE_UNIFORM
907     })
908     @Retention(RetentionPolicy.SOURCE)
909     public @interface AutoSizeTextType {}
910     // Default minimum size for auto-sizing text in scaled pixels.
911     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
912     // Default maximum size for auto-sizing text in scaled pixels.
913     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
914     // Default value for the step size in pixels.
915     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
916     // Use this to specify that any of the auto-size configuration int values have not been set.
917     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
918     // Auto-size text type.
919     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
920     // Specify if auto-size text is needed.
921     private boolean mNeedsAutoSizeText = false;
922     // Step size for auto-sizing in pixels.
923     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
924     // Minimum text size for auto-sizing in pixels.
925     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
926     // Maximum text size for auto-sizing in pixels.
927     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
928     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
929     // when auto-sizing text.
930     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
931     // Specifies whether auto-size should use the provided auto size steps set or if it should
932     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
933     // mAutoSizeStepGranularityInPx.
934     private boolean mHasPresetAutoSizeValues = false;
935 
936     // Autofill-related attributes
937     //
938     // Indicates whether the text was set statically or dynamically, so it can be used to
939     // sanitize autofill requests.
940     private boolean mTextSetFromXmlOrResourceId = false;
941     // Resource id used to set the text.
942     private @StringRes int mTextId = Resources.ID_NULL;
943     // Resource id used to set the hint.
944     private @StringRes int mHintId = Resources.ID_NULL;
945     //
946     // End of autofill-related attributes
947 
948     /**
949      * Kick-start the font cache for the zygote process (to pay the cost of
950      * initializing freetype for our default font only once).
951      * @hide
952      */
953     public static void preloadFontCache() {
954         Paint p = new Paint();
955         p.setAntiAlias(true);
956         // Ensure that the Typeface is loaded here.
957         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
958         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
959         // since Paint.measureText can not be called without Typeface static initializer.
960         p.setTypeface(Typeface.DEFAULT);
961         // We don't care about the result, just the side-effect of measuring.
962         p.measureText("H");
963     }
964 
965     /**
966      * Interface definition for a callback to be invoked when an action is
967      * performed on the editor.
968      */
969     public interface OnEditorActionListener {
970         /**
971          * Called when an action is being performed.
972          *
973          * @param v The view that was clicked.
974          * @param actionId Identifier of the action.  This will be either the
975          * identifier you supplied, or {@link EditorInfo#IME_NULL
976          * EditorInfo.IME_NULL} if being called due to the enter key
977          * being pressed.
978          * @param event If triggered by an enter key, this is the event;
979          * otherwise, this is null.
980          * @return Return true if you have consumed the action, else false.
981          */
982         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
983     }
984 
985     public TextView(Context context) {
986         this(context, null);
987     }
988 
989     public TextView(Context context, @Nullable AttributeSet attrs) {
990         this(context, attrs, com.android.internal.R.attr.textViewStyle);
991     }
992 
993     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
994         this(context, attrs, defStyleAttr, 0);
995     }
996 
997     @SuppressWarnings("deprecation")
998     public TextView(
999             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1000         super(context, attrs, defStyleAttr, defStyleRes);
1001 
1002         // TextView is important by default, unless app developer overrode attribute.
1003         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
1004             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
1005         }
1006         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
1007             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
1008         }
1009 
1010         setTextInternal("");
1011 
1012         final Resources res = getResources();
1013         final CompatibilityInfo compat = res.getCompatibilityInfo();
1014 
1015         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
1016         mTextPaint.density = res.getDisplayMetrics().density;
1017         mTextPaint.setCompatibilityScaling(compat.applicationScale);
1018 
1019         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1020         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
1021 
1022         mMovement = getDefaultMovementMethod();
1023 
1024         mTransformation = null;
1025 
1026         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
1027         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
1028         attributes.mTextSize = 15;
1029         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1030         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1031         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1032 
1033         final Resources.Theme theme = context.getTheme();
1034 
1035         /*
1036          * Look the appearance up without checking first if it exists because
1037          * almost every TextView has one and it greatly simplifies the logic
1038          * to be able to parse the appearance first and then let specific tags
1039          * for this View override it.
1040          */
1041         TypedArray a = theme.obtainStyledAttributes(attrs,
1042                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1043         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1044                 attrs, a, defStyleAttr, defStyleRes);
1045         TypedArray appearance = null;
1046         int ap = a.getResourceId(
1047                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1048         a.recycle();
1049         if (ap != -1) {
1050             appearance = theme.obtainStyledAttributes(
1051                     ap, com.android.internal.R.styleable.TextAppearance);
1052             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1053                     null, appearance, 0, ap);
1054         }
1055         if (appearance != null) {
1056             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1057             attributes.mFontFamilyExplicit = false;
1058             appearance.recycle();
1059         }
1060 
1061         boolean editable = getDefaultEditable();
1062         CharSequence inputMethod = null;
1063         int numeric = 0;
1064         CharSequence digits = null;
1065         boolean phone = false;
1066         boolean autotext = false;
1067         int autocap = -1;
1068         int buffertype = 0;
1069         boolean selectallonfocus = false;
1070         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1071                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1072         ColorStateList drawableTint = null;
1073         BlendMode drawableTintMode = null;
1074         int drawablePadding = 0;
1075         int ellipsize = ELLIPSIZE_NOT_SET;
1076         boolean singleLine = false;
1077         int maxlength = -1;
1078         CharSequence text = "";
1079         CharSequence hint = null;
1080         boolean password = false;
1081         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1082         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1083         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1084         int inputType = EditorInfo.TYPE_NULL;
1085         a = theme.obtainStyledAttributes(
1086                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1087         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1088                 defStyleAttr, defStyleRes);
1089         int firstBaselineToTopHeight = -1;
1090         int lastBaselineToBottomHeight = -1;
1091         int lineHeight = -1;
1092 
1093         readTextAppearance(context, a, attributes, true /* styleArray */);
1094 
1095         int n = a.getIndexCount();
1096 
1097         // Must set id in a temporary variable because it will be reset by setText()
1098         boolean textIsSetFromXml = false;
1099         for (int i = 0; i < n; i++) {
1100             int attr = a.getIndex(i);
1101 
1102             switch (attr) {
1103                 case com.android.internal.R.styleable.TextView_editable:
1104                     editable = a.getBoolean(attr, editable);
1105                     break;
1106 
1107                 case com.android.internal.R.styleable.TextView_inputMethod:
1108                     inputMethod = a.getText(attr);
1109                     break;
1110 
1111                 case com.android.internal.R.styleable.TextView_numeric:
1112                     numeric = a.getInt(attr, numeric);
1113                     break;
1114 
1115                 case com.android.internal.R.styleable.TextView_digits:
1116                     digits = a.getText(attr);
1117                     break;
1118 
1119                 case com.android.internal.R.styleable.TextView_phoneNumber:
1120                     phone = a.getBoolean(attr, phone);
1121                     break;
1122 
1123                 case com.android.internal.R.styleable.TextView_autoText:
1124                     autotext = a.getBoolean(attr, autotext);
1125                     break;
1126 
1127                 case com.android.internal.R.styleable.TextView_capitalize:
1128                     autocap = a.getInt(attr, autocap);
1129                     break;
1130 
1131                 case com.android.internal.R.styleable.TextView_bufferType:
1132                     buffertype = a.getInt(attr, buffertype);
1133                     break;
1134 
1135                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1136                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1137                     break;
1138 
1139                 case com.android.internal.R.styleable.TextView_autoLink:
1140                     mAutoLinkMask = a.getInt(attr, 0);
1141                     break;
1142 
1143                 case com.android.internal.R.styleable.TextView_linksClickable:
1144                     mLinksClickable = a.getBoolean(attr, true);
1145                     break;
1146 
1147                 case com.android.internal.R.styleable.TextView_drawableLeft:
1148                     drawableLeft = a.getDrawable(attr);
1149                     break;
1150 
1151                 case com.android.internal.R.styleable.TextView_drawableTop:
1152                     drawableTop = a.getDrawable(attr);
1153                     break;
1154 
1155                 case com.android.internal.R.styleable.TextView_drawableRight:
1156                     drawableRight = a.getDrawable(attr);
1157                     break;
1158 
1159                 case com.android.internal.R.styleable.TextView_drawableBottom:
1160                     drawableBottom = a.getDrawable(attr);
1161                     break;
1162 
1163                 case com.android.internal.R.styleable.TextView_drawableStart:
1164                     drawableStart = a.getDrawable(attr);
1165                     break;
1166 
1167                 case com.android.internal.R.styleable.TextView_drawableEnd:
1168                     drawableEnd = a.getDrawable(attr);
1169                     break;
1170 
1171                 case com.android.internal.R.styleable.TextView_drawableTint:
1172                     drawableTint = a.getColorStateList(attr);
1173                     break;
1174 
1175                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1176                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1177                             drawableTintMode);
1178                     break;
1179 
1180                 case com.android.internal.R.styleable.TextView_drawablePadding:
1181                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1182                     break;
1183 
1184                 case com.android.internal.R.styleable.TextView_maxLines:
1185                     setMaxLines(a.getInt(attr, -1));
1186                     break;
1187 
1188                 case com.android.internal.R.styleable.TextView_maxHeight:
1189                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1190                     break;
1191 
1192                 case com.android.internal.R.styleable.TextView_lines:
1193                     setLines(a.getInt(attr, -1));
1194                     break;
1195 
1196                 case com.android.internal.R.styleable.TextView_height:
1197                     setHeight(a.getDimensionPixelSize(attr, -1));
1198                     break;
1199 
1200                 case com.android.internal.R.styleable.TextView_minLines:
1201                     setMinLines(a.getInt(attr, -1));
1202                     break;
1203 
1204                 case com.android.internal.R.styleable.TextView_minHeight:
1205                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1206                     break;
1207 
1208                 case com.android.internal.R.styleable.TextView_maxEms:
1209                     setMaxEms(a.getInt(attr, -1));
1210                     break;
1211 
1212                 case com.android.internal.R.styleable.TextView_maxWidth:
1213                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1214                     break;
1215 
1216                 case com.android.internal.R.styleable.TextView_ems:
1217                     setEms(a.getInt(attr, -1));
1218                     break;
1219 
1220                 case com.android.internal.R.styleable.TextView_width:
1221                     setWidth(a.getDimensionPixelSize(attr, -1));
1222                     break;
1223 
1224                 case com.android.internal.R.styleable.TextView_minEms:
1225                     setMinEms(a.getInt(attr, -1));
1226                     break;
1227 
1228                 case com.android.internal.R.styleable.TextView_minWidth:
1229                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1230                     break;
1231 
1232                 case com.android.internal.R.styleable.TextView_gravity:
1233                     setGravity(a.getInt(attr, -1));
1234                     break;
1235 
1236                 case com.android.internal.R.styleable.TextView_hint:
1237                     mHintId = a.getResourceId(attr, Resources.ID_NULL);
1238                     hint = a.getText(attr);
1239                     break;
1240 
1241                 case com.android.internal.R.styleable.TextView_text:
1242                     textIsSetFromXml = true;
1243                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1244                     text = a.getText(attr);
1245                     break;
1246 
1247                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1248                     if (a.getBoolean(attr, false)) {
1249                         setHorizontallyScrolling(true);
1250                     }
1251                     break;
1252 
1253                 case com.android.internal.R.styleable.TextView_singleLine:
1254                     singleLine = a.getBoolean(attr, singleLine);
1255                     break;
1256 
1257                 case com.android.internal.R.styleable.TextView_ellipsize:
1258                     ellipsize = a.getInt(attr, ellipsize);
1259                     break;
1260 
1261                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1262                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1263                     break;
1264 
1265                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1266                     if (!a.getBoolean(attr, true)) {
1267                         setIncludeFontPadding(false);
1268                     }
1269                     break;
1270 
1271                 case com.android.internal.R.styleable.TextView_cursorVisible:
1272                     if (!a.getBoolean(attr, true)) {
1273                         setCursorVisible(false);
1274                     }
1275                     break;
1276 
1277                 case com.android.internal.R.styleable.TextView_maxLength:
1278                     maxlength = a.getInt(attr, -1);
1279                     break;
1280 
1281                 case com.android.internal.R.styleable.TextView_textScaleX:
1282                     setTextScaleX(a.getFloat(attr, 1.0f));
1283                     break;
1284 
1285                 case com.android.internal.R.styleable.TextView_freezesText:
1286                     mFreezesText = a.getBoolean(attr, false);
1287                     break;
1288 
1289                 case com.android.internal.R.styleable.TextView_enabled:
1290                     setEnabled(a.getBoolean(attr, isEnabled()));
1291                     break;
1292 
1293                 case com.android.internal.R.styleable.TextView_password:
1294                     password = a.getBoolean(attr, password);
1295                     break;
1296 
1297                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1298                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1299                     break;
1300 
1301                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1302                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1303                     break;
1304 
1305                 case com.android.internal.R.styleable.TextView_inputType:
1306                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1307                     break;
1308 
1309                 case com.android.internal.R.styleable.TextView_allowUndo:
1310                     createEditorIfNeeded();
1311                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1312                     break;
1313 
1314                 case com.android.internal.R.styleable.TextView_imeOptions:
1315                     createEditorIfNeeded();
1316                     mEditor.createInputContentTypeIfNeeded();
1317                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1318                             mEditor.mInputContentType.imeOptions);
1319                     break;
1320 
1321                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1322                     createEditorIfNeeded();
1323                     mEditor.createInputContentTypeIfNeeded();
1324                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1325                     break;
1326 
1327                 case com.android.internal.R.styleable.TextView_imeActionId:
1328                     createEditorIfNeeded();
1329                     mEditor.createInputContentTypeIfNeeded();
1330                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1331                             mEditor.mInputContentType.imeActionId);
1332                     break;
1333 
1334                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1335                     setPrivateImeOptions(a.getString(attr));
1336                     break;
1337 
1338                 case com.android.internal.R.styleable.TextView_editorExtras:
1339                     try {
1340                         setInputExtras(a.getResourceId(attr, 0));
1341                     } catch (XmlPullParserException e) {
1342                         Log.w(LOG_TAG, "Failure reading input extras", e);
1343                     } catch (IOException e) {
1344                         Log.w(LOG_TAG, "Failure reading input extras", e);
1345                     }
1346                     break;
1347 
1348                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1349                     mCursorDrawableRes = a.getResourceId(attr, 0);
1350                     break;
1351 
1352                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1353                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1354                     break;
1355 
1356                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1357                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1358                     break;
1359 
1360                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1361                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1362                     break;
1363 
1364                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1365                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1366                     break;
1367 
1368                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1369                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1370                     break;
1371 
1372                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1373                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1374                     break;
1375 
1376                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1377                     setTextIsSelectable(a.getBoolean(attr, false));
1378                     break;
1379 
1380                 case com.android.internal.R.styleable.TextView_breakStrategy:
1381                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1382                     break;
1383 
1384                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1385                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1386                     break;
1387 
1388                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1389                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1390                     break;
1391 
1392                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1393                     autoSizeStepGranularityInPx = a.getDimension(attr,
1394                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1395                     break;
1396 
1397                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1398                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1399                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1400                     break;
1401 
1402                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1403                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1404                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1405                     break;
1406 
1407                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1408                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1409                     if (autoSizeStepSizeArrayResId > 0) {
1410                         final TypedArray autoSizePresetTextSizes = a.getResources()
1411                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1412                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1413                         autoSizePresetTextSizes.recycle();
1414                     }
1415                     break;
1416                 case com.android.internal.R.styleable.TextView_justificationMode:
1417                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1418                     break;
1419 
1420                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1421                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1422                     break;
1423 
1424                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1425                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1426                     break;
1427 
1428                 case com.android.internal.R.styleable.TextView_lineHeight:
1429                     lineHeight = a.getDimensionPixelSize(attr, -1);
1430                     break;
1431             }
1432         }
1433 
1434         a.recycle();
1435 
1436         BufferType bufferType = BufferType.EDITABLE;
1437 
1438         final int variation =
1439                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1440         final boolean passwordInputType = variation
1441                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1442         final boolean webPasswordInputType = variation
1443                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1444         final boolean numberPasswordInputType = variation
1445                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1446 
1447         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1448         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1449         mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
1450 
1451         if (inputMethod != null) {
1452             Class<?> c;
1453 
1454             try {
1455                 c = Class.forName(inputMethod.toString());
1456             } catch (ClassNotFoundException ex) {
1457                 throw new RuntimeException(ex);
1458             }
1459 
1460             try {
1461                 createEditorIfNeeded();
1462                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1463             } catch (InstantiationException ex) {
1464                 throw new RuntimeException(ex);
1465             } catch (IllegalAccessException ex) {
1466                 throw new RuntimeException(ex);
1467             }
1468             try {
1469                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1470                         ? inputType
1471                         : mEditor.mKeyListener.getInputType();
1472             } catch (IncompatibleClassChangeError e) {
1473                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1474             }
1475         } else if (digits != null) {
1476             createEditorIfNeeded();
1477             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1478             // If no input type was specified, we will default to generic
1479             // text, since we can't tell the IME about the set of digits
1480             // that was selected.
1481             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1482                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1483         } else if (inputType != EditorInfo.TYPE_NULL) {
1484             setInputType(inputType, true);
1485             // If set, the input type overrides what was set using the deprecated singleLine flag.
1486             singleLine = !isMultilineInputType(inputType);
1487         } else if (phone) {
1488             createEditorIfNeeded();
1489             mEditor.mKeyListener = DialerKeyListener.getInstance();
1490             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1491         } else if (numeric != 0) {
1492             createEditorIfNeeded();
1493             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1494                     null,  // locale
1495                     (numeric & SIGNED) != 0,
1496                     (numeric & DECIMAL) != 0);
1497             inputType = mEditor.mKeyListener.getInputType();
1498             mEditor.mInputType = inputType;
1499         } else if (autotext || autocap != -1) {
1500             TextKeyListener.Capitalize cap;
1501 
1502             inputType = EditorInfo.TYPE_CLASS_TEXT;
1503 
1504             switch (autocap) {
1505                 case 1:
1506                     cap = TextKeyListener.Capitalize.SENTENCES;
1507                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1508                     break;
1509 
1510                 case 2:
1511                     cap = TextKeyListener.Capitalize.WORDS;
1512                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1513                     break;
1514 
1515                 case 3:
1516                     cap = TextKeyListener.Capitalize.CHARACTERS;
1517                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1518                     break;
1519 
1520                 default:
1521                     cap = TextKeyListener.Capitalize.NONE;
1522                     break;
1523             }
1524 
1525             createEditorIfNeeded();
1526             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1527             mEditor.mInputType = inputType;
1528         } else if (editable) {
1529             createEditorIfNeeded();
1530             mEditor.mKeyListener = TextKeyListener.getInstance();
1531             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1532         } else if (isTextSelectable()) {
1533             // Prevent text changes from keyboard.
1534             if (mEditor != null) {
1535                 mEditor.mKeyListener = null;
1536                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1537             }
1538             bufferType = BufferType.SPANNABLE;
1539             // So that selection can be changed using arrow keys and touch is handled.
1540             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1541         } else {
1542             if (mEditor != null) mEditor.mKeyListener = null;
1543 
1544             switch (buffertype) {
1545                 case 0:
1546                     bufferType = BufferType.NORMAL;
1547                     break;
1548                 case 1:
1549                     bufferType = BufferType.SPANNABLE;
1550                     break;
1551                 case 2:
1552                     bufferType = BufferType.EDITABLE;
1553                     break;
1554             }
1555         }
1556 
1557         if (mEditor != null) {
1558             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1559                     numberPasswordInputType);
1560         }
1561 
1562         if (selectallonfocus) {
1563             createEditorIfNeeded();
1564             mEditor.mSelectAllOnFocus = true;
1565 
1566             if (bufferType == BufferType.NORMAL) {
1567                 bufferType = BufferType.SPANNABLE;
1568             }
1569         }
1570 
1571         // Set up the tint (if needed) before setting the drawables so that it
1572         // gets applied correctly.
1573         if (drawableTint != null || drawableTintMode != null) {
1574             if (mDrawables == null) {
1575                 mDrawables = new Drawables(context);
1576             }
1577             if (drawableTint != null) {
1578                 mDrawables.mTintList = drawableTint;
1579                 mDrawables.mHasTint = true;
1580             }
1581             if (drawableTintMode != null) {
1582                 mDrawables.mBlendMode = drawableTintMode;
1583                 mDrawables.mHasTintMode = true;
1584             }
1585         }
1586 
1587         // This call will save the initial left/right drawables
1588         setCompoundDrawablesWithIntrinsicBounds(
1589                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1590         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1591         setCompoundDrawablePadding(drawablePadding);
1592 
1593         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1594         // of lines of height are unchanged for multi-line TextViews.
1595         setInputTypeSingleLine(singleLine);
1596         applySingleLine(singleLine, singleLine, singleLine,
1597                 // Does not apply automated max length filter since length filter will be resolved
1598                 // later in this function.
1599                 false
1600         );
1601 
1602         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1603             ellipsize = ELLIPSIZE_END;
1604         }
1605 
1606         switch (ellipsize) {
1607             case ELLIPSIZE_START:
1608                 setEllipsize(TextUtils.TruncateAt.START);
1609                 break;
1610             case ELLIPSIZE_MIDDLE:
1611                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1612                 break;
1613             case ELLIPSIZE_END:
1614                 setEllipsize(TextUtils.TruncateAt.END);
1615                 break;
1616             case ELLIPSIZE_MARQUEE:
1617                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1618                     setHorizontalFadingEdgeEnabled(true);
1619                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1620                 } else {
1621                     setHorizontalFadingEdgeEnabled(false);
1622                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1623                 }
1624                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1625                 break;
1626         }
1627 
1628         final boolean isPassword = password || passwordInputType || webPasswordInputType
1629                 || numberPasswordInputType;
1630         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1631                 && (mEditor.mInputType
1632                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1633                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1634         if (isMonospaceEnforced) {
1635             attributes.mTypefaceIndex = MONOSPACE;
1636         }
1637 
1638         applyTextAppearance(attributes);
1639 
1640         if (isPassword) {
1641             setTransformationMethod(PasswordTransformationMethod.getInstance());
1642         }
1643 
1644         // For addressing b/145128646
1645         // For the performance reason, we limit characters for single line text field.
1646         if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) {
1647             mSingleLineLengthFilter = new InputFilter.LengthFilter(
1648                 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
1649         }
1650 
1651         if (mSingleLineLengthFilter != null) {
1652             setFilters(new InputFilter[] { mSingleLineLengthFilter });
1653         } else if (maxlength >= 0) {
1654             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1655         } else {
1656             setFilters(NO_FILTERS);
1657         }
1658 
1659         setText(text, bufferType);
1660         if (mText == null) {
1661             mText = "";
1662         }
1663         if (mTransformed == null) {
1664             mTransformed = "";
1665         }
1666 
1667         if (textIsSetFromXml) {
1668             mTextSetFromXmlOrResourceId = true;
1669         }
1670 
1671         if (hint != null) setHint(hint);
1672 
1673         /*
1674          * Views are not normally clickable unless specified to be.
1675          * However, TextViews that have input or movement methods *are*
1676          * clickable by default. By setting clickable here, we implicitly set focusable as well
1677          * if not overridden by the developer.
1678          */
1679         a = context.obtainStyledAttributes(
1680                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1681         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1682         boolean clickable = canInputOrMove || isClickable();
1683         boolean longClickable = canInputOrMove || isLongClickable();
1684         int focusable = getFocusable();
1685 
1686         n = a.getIndexCount();
1687         for (int i = 0; i < n; i++) {
1688             int attr = a.getIndex(i);
1689 
1690             switch (attr) {
1691                 case com.android.internal.R.styleable.View_focusable:
1692                     TypedValue val = new TypedValue();
1693                     if (a.getValue(attr, val)) {
1694                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1695                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1696                                 : val.data;
1697                     }
1698                     break;
1699 
1700                 case com.android.internal.R.styleable.View_clickable:
1701                     clickable = a.getBoolean(attr, clickable);
1702                     break;
1703 
1704                 case com.android.internal.R.styleable.View_longClickable:
1705                     longClickable = a.getBoolean(attr, longClickable);
1706                     break;
1707             }
1708         }
1709         a.recycle();
1710 
1711         // Some apps were relying on the undefined behavior of focusable winning over
1712         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1713         // when starting with EditText and setting only focusable=false). To keep those apps from
1714         // breaking, re-apply the focusable attribute here.
1715         if (focusable != getFocusable()) {
1716             setFocusable(focusable);
1717         }
1718         setClickable(clickable);
1719         setLongClickable(longClickable);
1720 
1721         if (mEditor != null) mEditor.prepareCursorControllers();
1722 
1723         // If not explicitly specified this view is important for accessibility.
1724         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1725             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1726         }
1727 
1728         if (supportsAutoSizeText()) {
1729             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1730                 // If uniform auto-size has been specified but preset values have not been set then
1731                 // replace the auto-size configuration values that have not been specified with the
1732                 // defaults.
1733                 if (!mHasPresetAutoSizeValues) {
1734                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1735 
1736                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1737                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1738                                 TypedValue.COMPLEX_UNIT_SP,
1739                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1740                                 displayMetrics);
1741                     }
1742 
1743                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1744                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1745                                 TypedValue.COMPLEX_UNIT_SP,
1746                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1747                                 displayMetrics);
1748                     }
1749 
1750                     if (autoSizeStepGranularityInPx
1751                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1752                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1753                     }
1754 
1755                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1756                             autoSizeMaxTextSizeInPx,
1757                             autoSizeStepGranularityInPx);
1758                 }
1759 
1760                 setupAutoSizeText();
1761             }
1762         } else {
1763             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1764         }
1765 
1766         if (firstBaselineToTopHeight >= 0) {
1767             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1768         }
1769         if (lastBaselineToBottomHeight >= 0) {
1770             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1771         }
1772         if (lineHeight >= 0) {
1773             setLineHeight(lineHeight);
1774         }
1775     }
1776 
1777     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)1778     private void setTextInternal(@Nullable CharSequence text) {
1779         mText = text;
1780         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
1781         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
1782     }
1783 
1784     /**
1785      * Specify whether this widget should automatically scale the text to try to perfectly fit
1786      * within the layout bounds by using the default auto-size configuration.
1787      *
1788      * @param autoSizeTextType the type of auto-size. Must be one of
1789      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1790      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1791      *
1792      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1793      *
1794      * @attr ref android.R.styleable#TextView_autoSizeTextType
1795      *
1796      * @see #getAutoSizeTextType()
1797      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1798     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1799         if (supportsAutoSizeText()) {
1800             switch (autoSizeTextType) {
1801                 case AUTO_SIZE_TEXT_TYPE_NONE:
1802                     clearAutoSizeConfiguration();
1803                     break;
1804                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1805                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1806                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1807                             TypedValue.COMPLEX_UNIT_SP,
1808                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1809                             displayMetrics);
1810                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1811                             TypedValue.COMPLEX_UNIT_SP,
1812                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1813                             displayMetrics);
1814 
1815                     validateAndSetAutoSizeTextTypeUniformConfiguration(
1816                             autoSizeMinTextSizeInPx,
1817                             autoSizeMaxTextSizeInPx,
1818                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1819                     if (setupAutoSizeText()) {
1820                         autoSizeText();
1821                         invalidate();
1822                     }
1823                     break;
1824                 default:
1825                     throw new IllegalArgumentException(
1826                             "Unknown auto-size text type: " + autoSizeTextType);
1827             }
1828         }
1829     }
1830 
1831     /**
1832      * Specify whether this widget should automatically scale the text to try to perfectly fit
1833      * within the layout bounds. If all the configuration params are valid the type of auto-size is
1834      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1835      *
1836      * @param autoSizeMinTextSize the minimum text size available for auto-size
1837      * @param autoSizeMaxTextSize the maximum text size available for auto-size
1838      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1839      *                                the minimum and maximum text size in order to build the set of
1840      *                                text sizes the system uses to choose from when auto-sizing
1841      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1842      *             possible dimension units
1843      *
1844      * @throws IllegalArgumentException if any of the configuration params are invalid.
1845      *
1846      * @attr ref android.R.styleable#TextView_autoSizeTextType
1847      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1848      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1849      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1850      *
1851      * @see #setAutoSizeTextTypeWithDefaults(int)
1852      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1853      * @see #getAutoSizeMinTextSize()
1854      * @see #getAutoSizeMaxTextSize()
1855      * @see #getAutoSizeStepGranularity()
1856      * @see #getAutoSizeTextAvailableSizes()
1857      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1858     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1859             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1860         if (supportsAutoSizeText()) {
1861             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1862             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1863                     unit, autoSizeMinTextSize, displayMetrics);
1864             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1865                     unit, autoSizeMaxTextSize, displayMetrics);
1866             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1867                     unit, autoSizeStepGranularity, displayMetrics);
1868 
1869             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1870                     autoSizeMaxTextSizeInPx,
1871                     autoSizeStepGranularityInPx);
1872 
1873             if (setupAutoSizeText()) {
1874                 autoSizeText();
1875                 invalidate();
1876             }
1877         }
1878     }
1879 
1880     /**
1881      * Specify whether this widget should automatically scale the text to try to perfectly fit
1882      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1883      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1884      *
1885      * @param presetSizes an {@code int} array of sizes in pixels
1886      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1887      *             the possible dimension units
1888      *
1889      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1890      *
1891      * @attr ref android.R.styleable#TextView_autoSizeTextType
1892      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1893      *
1894      * @see #setAutoSizeTextTypeWithDefaults(int)
1895      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1896      * @see #getAutoSizeMinTextSize()
1897      * @see #getAutoSizeMaxTextSize()
1898      * @see #getAutoSizeTextAvailableSizes()
1899      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1900     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1901         if (supportsAutoSizeText()) {
1902             final int presetSizesLength = presetSizes.length;
1903             if (presetSizesLength > 0) {
1904                 int[] presetSizesInPx = new int[presetSizesLength];
1905 
1906                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1907                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1908                 } else {
1909                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1910                     // Convert all to sizes to pixels.
1911                     for (int i = 0; i < presetSizesLength; i++) {
1912                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
1913                             presetSizes[i], displayMetrics));
1914                     }
1915                 }
1916 
1917                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1918                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
1919                     throw new IllegalArgumentException("None of the preset sizes is valid: "
1920                             + Arrays.toString(presetSizes));
1921                 }
1922             } else {
1923                 mHasPresetAutoSizeValues = false;
1924             }
1925 
1926             if (setupAutoSizeText()) {
1927                 autoSizeText();
1928                 invalidate();
1929             }
1930         }
1931     }
1932 
1933     /**
1934      * Returns the type of auto-size set for this widget.
1935      *
1936      * @return an {@code int} corresponding to one of the auto-size types:
1937      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1938      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1939      *
1940      * @attr ref android.R.styleable#TextView_autoSizeTextType
1941      *
1942      * @see #setAutoSizeTextTypeWithDefaults(int)
1943      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1944      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1945      */
1946     @InspectableProperty(enumMapping = {
1947             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
1948             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
1949     })
1950     @AutoSizeTextType
getAutoSizeTextType()1951     public int getAutoSizeTextType() {
1952         return mAutoSizeTextType;
1953     }
1954 
1955     /**
1956      * @return the current auto-size step granularity in pixels.
1957      *
1958      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1959      *
1960      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1961      */
1962     @InspectableProperty
getAutoSizeStepGranularity()1963     public int getAutoSizeStepGranularity() {
1964         return Math.round(mAutoSizeStepGranularityInPx);
1965     }
1966 
1967     /**
1968      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
1969      *         if auto-size has not been configured this function returns {@code -1}.
1970      *
1971      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1972      *
1973      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1974      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1975      */
1976     @InspectableProperty
getAutoSizeMinTextSize()1977     public int getAutoSizeMinTextSize() {
1978         return Math.round(mAutoSizeMinTextSizeInPx);
1979     }
1980 
1981     /**
1982      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
1983      *         if auto-size has not been configured this function returns {@code -1}.
1984      *
1985      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1986      *
1987      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1988      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1989      */
1990     @InspectableProperty
getAutoSizeMaxTextSize()1991     public int getAutoSizeMaxTextSize() {
1992         return Math.round(mAutoSizeMaxTextSizeInPx);
1993     }
1994 
1995     /**
1996      * @return the current auto-size {@code int} sizes array (in pixels).
1997      *
1998      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1999      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2000      */
getAutoSizeTextAvailableSizes()2001     public int[] getAutoSizeTextAvailableSizes() {
2002         return mAutoSizeTextSizesInPx;
2003     }
2004 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)2005     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
2006         final int textSizesLength = textSizes.length();
2007         final int[] parsedSizes = new int[textSizesLength];
2008 
2009         if (textSizesLength > 0) {
2010             for (int i = 0; i < textSizesLength; i++) {
2011                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
2012             }
2013             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
2014             setupAutoSizeUniformPresetSizesConfiguration();
2015         }
2016     }
2017 
setupAutoSizeUniformPresetSizesConfiguration()2018     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
2019         final int sizesLength = mAutoSizeTextSizesInPx.length;
2020         mHasPresetAutoSizeValues = sizesLength > 0;
2021         if (mHasPresetAutoSizeValues) {
2022             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2023             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
2024             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
2025             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2026         }
2027         return mHasPresetAutoSizeValues;
2028     }
2029 
2030     /**
2031      * If all params are valid then save the auto-size configuration.
2032      *
2033      * @throws IllegalArgumentException if any of the params are invalid
2034      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2035     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
2036             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
2037         // First validate.
2038         if (autoSizeMinTextSizeInPx <= 0) {
2039             throw new IllegalArgumentException("Minimum auto-size text size ("
2040                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
2041         }
2042 
2043         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2044             throw new IllegalArgumentException("Maximum auto-size text size ("
2045                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2046                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2047         }
2048 
2049         if (autoSizeStepGranularityInPx <= 0) {
2050             throw new IllegalArgumentException("The auto-size step granularity ("
2051                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2052         }
2053 
2054         // All good, persist the configuration.
2055         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2056         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2057         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2058         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2059         mHasPresetAutoSizeValues = false;
2060     }
2061 
clearAutoSizeConfiguration()2062     private void clearAutoSizeConfiguration() {
2063         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2064         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2065         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2066         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2067         mAutoSizeTextSizesInPx = EmptyArray.INT;
2068         mNeedsAutoSizeText = false;
2069     }
2070 
2071     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)2072     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2073         final int presetValuesLength = presetValues.length;
2074         if (presetValuesLength == 0) {
2075             return presetValues;
2076         }
2077         Arrays.sort(presetValues);
2078 
2079         final IntArray uniqueValidSizes = new IntArray();
2080         for (int i = 0; i < presetValuesLength; i++) {
2081             final int currentPresetValue = presetValues[i];
2082 
2083             if (currentPresetValue > 0
2084                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2085                 uniqueValidSizes.add(currentPresetValue);
2086             }
2087         }
2088 
2089         return presetValuesLength == uniqueValidSizes.size()
2090             ? presetValues
2091             : uniqueValidSizes.toArray();
2092     }
2093 
setupAutoSizeText()2094     private boolean setupAutoSizeText() {
2095         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2096             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2097             // not have a predefined set of sizes or if the current sizes array is empty.
2098             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2099                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2100                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2101                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2102                 for (int i = 0; i < autoSizeValuesLength; i++) {
2103                     autoSizeTextSizesInPx[i] = Math.round(
2104                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2105                 }
2106                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2107             }
2108 
2109             mNeedsAutoSizeText = true;
2110         } else {
2111             mNeedsAutoSizeText = false;
2112         }
2113 
2114         return mNeedsAutoSizeText;
2115     }
2116 
parseDimensionArray(TypedArray dimens)2117     private int[] parseDimensionArray(TypedArray dimens) {
2118         if (dimens == null) {
2119             return null;
2120         }
2121         int[] result = new int[dimens.length()];
2122         for (int i = 0; i < result.length; i++) {
2123             result[i] = dimens.getDimensionPixelSize(i, 0);
2124         }
2125         return result;
2126     }
2127 
2128     /**
2129      * @hide
2130      */
2131     @Override
onActivityResult(int requestCode, int resultCode, Intent data)2132     public void onActivityResult(int requestCode, int resultCode, Intent data) {
2133         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2134             if (resultCode == Activity.RESULT_OK && data != null) {
2135                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2136                 if (result != null) {
2137                     if (isTextEditable()) {
2138                         replaceSelectionWithText(result);
2139                         if (mEditor != null) {
2140                             mEditor.refreshTextActionMode();
2141                         }
2142                     } else {
2143                         if (result.length() > 0) {
2144                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2145                                 .show();
2146                         }
2147                     }
2148                 }
2149             } else if (mSpannable != null) {
2150                 // Reset the selection.
2151                 Selection.setSelection(mSpannable, getSelectionEnd());
2152             }
2153         }
2154     }
2155 
2156     /**
2157      * Sets the Typeface taking into account the given attributes.
2158      *
2159      * @param typeface a typeface
2160      * @param familyName family name string, e.g. "serif"
2161      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2162      * @param style a typeface style
2163      * @param weight a weight value for the Typeface or -1 if not specified.
2164      */
setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2165     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2166             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2167             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2168         if (typeface == null && familyName != null) {
2169             // Lookup normal Typeface from system font map.
2170             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2171             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2172         } else if (typeface != null) {
2173             resolveStyleAndSetTypeface(typeface, style, weight);
2174         } else {  // both typeface and familyName is null.
2175             switch (typefaceIndex) {
2176                 case SANS:
2177                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2178                     break;
2179                 case SERIF:
2180                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2181                     break;
2182                 case MONOSPACE:
2183                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2184                     break;
2185                 case DEFAULT_TYPEFACE:
2186                 default:
2187                     resolveStyleAndSetTypeface(null, style, weight);
2188                     break;
2189             }
2190         }
2191     }
2192 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2193     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2194             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2195         if (weight >= 0) {
2196             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2197             final boolean italic = (style & Typeface.ITALIC) != 0;
2198             setTypeface(Typeface.create(typeface, weight, italic));
2199         } else {
2200             setTypeface(typeface, style);
2201         }
2202     }
2203 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2204     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2205         boolean hasRelativeDrawables = (start != null) || (end != null);
2206         if (hasRelativeDrawables) {
2207             Drawables dr = mDrawables;
2208             if (dr == null) {
2209                 mDrawables = dr = new Drawables(getContext());
2210             }
2211             mDrawables.mOverride = true;
2212             final Rect compoundRect = dr.mCompoundRect;
2213             int[] state = getDrawableState();
2214             if (start != null) {
2215                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2216                 start.setState(state);
2217                 start.copyBounds(compoundRect);
2218                 start.setCallback(this);
2219 
2220                 dr.mDrawableStart = start;
2221                 dr.mDrawableSizeStart = compoundRect.width();
2222                 dr.mDrawableHeightStart = compoundRect.height();
2223             } else {
2224                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2225             }
2226             if (end != null) {
2227                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2228                 end.setState(state);
2229                 end.copyBounds(compoundRect);
2230                 end.setCallback(this);
2231 
2232                 dr.mDrawableEnd = end;
2233                 dr.mDrawableSizeEnd = compoundRect.width();
2234                 dr.mDrawableHeightEnd = compoundRect.height();
2235             } else {
2236                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2237             }
2238             resetResolvedDrawables();
2239             resolveDrawables();
2240             applyCompoundDrawableTint();
2241         }
2242     }
2243 
2244     @android.view.RemotableViewMethod
2245     @Override
setEnabled(boolean enabled)2246     public void setEnabled(boolean enabled) {
2247         if (enabled == isEnabled()) {
2248             return;
2249         }
2250 
2251         if (!enabled) {
2252             // Hide the soft input if the currently active TextView is disabled
2253             InputMethodManager imm = getInputMethodManager();
2254             if (imm != null && imm.isActive(this)) {
2255                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2256             }
2257         }
2258 
2259         super.setEnabled(enabled);
2260 
2261         if (enabled) {
2262             // Make sure IME is updated with current editor info.
2263             InputMethodManager imm = getInputMethodManager();
2264             if (imm != null) imm.restartInput(this);
2265         }
2266 
2267         // Will change text color
2268         if (mEditor != null) {
2269             mEditor.invalidateTextDisplayList();
2270             mEditor.prepareCursorControllers();
2271 
2272             // start or stop the cursor blinking as appropriate
2273             mEditor.makeBlink();
2274         }
2275     }
2276 
2277     /**
2278      * Sets the typeface and style in which the text should be displayed,
2279      * and turns on the fake bold and italic bits in the Paint if the
2280      * Typeface that you provided does not have all the bits in the
2281      * style that you specified.
2282      *
2283      * @attr ref android.R.styleable#TextView_typeface
2284      * @attr ref android.R.styleable#TextView_textStyle
2285      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2286     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2287         if (style > 0) {
2288             if (tf == null) {
2289                 tf = Typeface.defaultFromStyle(style);
2290             } else {
2291                 tf = Typeface.create(tf, style);
2292             }
2293 
2294             setTypeface(tf);
2295             // now compute what (if any) algorithmic styling is needed
2296             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2297             int need = style & ~typefaceStyle;
2298             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2299             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2300         } else {
2301             mTextPaint.setFakeBoldText(false);
2302             mTextPaint.setTextSkewX(0);
2303             setTypeface(tf);
2304         }
2305     }
2306 
2307     /**
2308      * Subclasses override this to specify that they have a KeyListener
2309      * by default even if not specifically called for in the XML options.
2310      */
getDefaultEditable()2311     protected boolean getDefaultEditable() {
2312         return false;
2313     }
2314 
2315     /**
2316      * Subclasses override this to specify a default movement method.
2317      */
getDefaultMovementMethod()2318     protected MovementMethod getDefaultMovementMethod() {
2319         return null;
2320     }
2321 
2322     /**
2323      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2324      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2325      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2326      * the return value from this method to Spannable or Editable, respectively.
2327      *
2328      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2329      * should make your own copy first.</p>
2330      *
2331      * @return The text displayed by the text view.
2332      * @attr ref android.R.styleable#TextView_text
2333      */
2334     @ViewDebug.CapturedViewProperty
2335     @InspectableProperty
getText()2336     public CharSequence getText() {
2337         return mText;
2338     }
2339 
2340     /**
2341      * Returns the length, in characters, of the text managed by this TextView
2342      * @return The length of the text managed by the TextView in characters.
2343      */
length()2344     public int length() {
2345         return mText.length();
2346     }
2347 
2348     /**
2349      * Return the text that TextView is displaying as an Editable object. If the text is not
2350      * editable, null is returned.
2351      *
2352      * @see #getText
2353      */
getEditableText()2354     public Editable getEditableText() {
2355         return (mText instanceof Editable) ? (Editable) mText : null;
2356     }
2357 
2358     /**
2359      * @hide
2360      */
2361     @VisibleForTesting
getTransformed()2362     public CharSequence getTransformed() {
2363         return mTransformed;
2364     }
2365 
2366     /**
2367      * Gets the vertical distance between lines of text, in pixels.
2368      * Note that markup within the text can cause individual lines
2369      * to be taller or shorter than this height, and the layout may
2370      * contain additional first-or last-line padding.
2371      * @return The height of one standard line in pixels.
2372      */
2373     @InspectableProperty
getLineHeight()2374     public int getLineHeight() {
2375         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2376     }
2377 
2378     /**
2379      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2380      * This value can be null if the text or width has recently changed.
2381      * @return The Layout that is currently being used to display the text.
2382      */
getLayout()2383     public final Layout getLayout() {
2384         return mLayout;
2385     }
2386 
2387     /**
2388      * @return the {@link android.text.Layout} that is currently being used to
2389      * display the hint text. This can be null.
2390      */
2391     @UnsupportedAppUsage
getHintLayout()2392     final Layout getHintLayout() {
2393         return mHintLayout;
2394     }
2395 
2396     /**
2397      * Retrieve the {@link android.content.UndoManager} that is currently associated
2398      * with this TextView.  By default there is no associated UndoManager, so null
2399      * is returned.  One can be associated with the TextView through
2400      * {@link #setUndoManager(android.content.UndoManager, String)}
2401      *
2402      * @hide
2403      */
getUndoManager()2404     public final UndoManager getUndoManager() {
2405         // TODO: Consider supporting a global undo manager.
2406         throw new UnsupportedOperationException("not implemented");
2407     }
2408 
2409 
2410     /**
2411      * @hide
2412      */
2413     @VisibleForTesting
getEditorForTesting()2414     public final Editor getEditorForTesting() {
2415         return mEditor;
2416     }
2417 
2418     /**
2419      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2420      * done, all edit operations on the TextView will result in appropriate
2421      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2422      * stack.
2423      *
2424      * @param undoManager The {@link android.content.UndoManager} to associate with
2425      * this TextView, or null to clear any existing association.
2426      * @param tag String tag identifying this particular TextView owner in the
2427      * UndoManager.  This is used to keep the correct association with the
2428      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2429      *
2430      * @hide
2431      */
setUndoManager(UndoManager undoManager, String tag)2432     public final void setUndoManager(UndoManager undoManager, String tag) {
2433         // TODO: Consider supporting a global undo manager. An implementation will need to:
2434         // * createEditorIfNeeded()
2435         // * Promote to BufferType.EDITABLE if needed.
2436         // * Update the UndoManager and UndoOwner.
2437         // Likewise it will need to be able to restore the default UndoManager.
2438         throw new UnsupportedOperationException("not implemented");
2439     }
2440 
2441     /**
2442      * Gets the current {@link KeyListener} for the TextView.
2443      * This will frequently be null for non-EditText TextViews.
2444      * @return the current key listener for this TextView.
2445      *
2446      * @attr ref android.R.styleable#TextView_numeric
2447      * @attr ref android.R.styleable#TextView_digits
2448      * @attr ref android.R.styleable#TextView_phoneNumber
2449      * @attr ref android.R.styleable#TextView_inputMethod
2450      * @attr ref android.R.styleable#TextView_capitalize
2451      * @attr ref android.R.styleable#TextView_autoText
2452      */
getKeyListener()2453     public final KeyListener getKeyListener() {
2454         return mEditor == null ? null : mEditor.mKeyListener;
2455     }
2456 
2457     /**
2458      * Sets the key listener to be used with this TextView.  This can be null
2459      * to disallow user input.  Note that this method has significant and
2460      * subtle interactions with soft keyboards and other input method:
2461      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2462      * for important details.  Calling this method will replace the current
2463      * content type of the text view with the content type returned by the
2464      * key listener.
2465      * <p>
2466      * Be warned that if you want a TextView with a key listener or movement
2467      * method not to be focusable, or if you want a TextView without a
2468      * key listener or movement method to be focusable, you must call
2469      * {@link #setFocusable} again after calling this to get the focusability
2470      * back the way you want it.
2471      *
2472      * @attr ref android.R.styleable#TextView_numeric
2473      * @attr ref android.R.styleable#TextView_digits
2474      * @attr ref android.R.styleable#TextView_phoneNumber
2475      * @attr ref android.R.styleable#TextView_inputMethod
2476      * @attr ref android.R.styleable#TextView_capitalize
2477      * @attr ref android.R.styleable#TextView_autoText
2478      */
setKeyListener(KeyListener input)2479     public void setKeyListener(KeyListener input) {
2480         mListenerChanged = true;
2481         setKeyListenerOnly(input);
2482         fixFocusableAndClickableSettings();
2483 
2484         if (input != null) {
2485             createEditorIfNeeded();
2486             setInputTypeFromEditor();
2487         } else {
2488             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2489         }
2490 
2491         InputMethodManager imm = getInputMethodManager();
2492         if (imm != null) imm.restartInput(this);
2493     }
2494 
setInputTypeFromEditor()2495     private void setInputTypeFromEditor() {
2496         try {
2497             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2498         } catch (IncompatibleClassChangeError e) {
2499             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2500         }
2501         // Change inputType, without affecting transformation.
2502         // No need to applySingleLine since mSingleLine is unchanged.
2503         setInputTypeSingleLine(mSingleLine);
2504     }
2505 
setKeyListenerOnly(KeyListener input)2506     private void setKeyListenerOnly(KeyListener input) {
2507         if (mEditor == null && input == null) return; // null is the default value
2508 
2509         createEditorIfNeeded();
2510         if (mEditor.mKeyListener != input) {
2511             mEditor.mKeyListener = input;
2512             if (input != null && !(mText instanceof Editable)) {
2513                 setText(mText);
2514             }
2515 
2516             setFilters((Editable) mText, mFilters);
2517         }
2518     }
2519 
2520     /**
2521      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2522      * which provides positioning, scrolling, and text selection functionality.
2523      * This will frequently be null for non-EditText TextViews.
2524      * @return the movement method being used for this TextView.
2525      * @see android.text.method.MovementMethod
2526      */
getMovementMethod()2527     public final MovementMethod getMovementMethod() {
2528         return mMovement;
2529     }
2530 
2531     /**
2532      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2533      * for this TextView. This can be null to disallow using the arrow keys to move the
2534      * cursor or scroll the view.
2535      * <p>
2536      * Be warned that if you want a TextView with a key listener or movement
2537      * method not to be focusable, or if you want a TextView without a
2538      * key listener or movement method to be focusable, you must call
2539      * {@link #setFocusable} again after calling this to get the focusability
2540      * back the way you want it.
2541      */
setMovementMethod(MovementMethod movement)2542     public final void setMovementMethod(MovementMethod movement) {
2543         if (mMovement != movement) {
2544             mMovement = movement;
2545 
2546             if (movement != null && mSpannable == null) {
2547                 setText(mText);
2548             }
2549 
2550             fixFocusableAndClickableSettings();
2551 
2552             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2553             // mMovement
2554             if (mEditor != null) mEditor.prepareCursorControllers();
2555         }
2556     }
2557 
fixFocusableAndClickableSettings()2558     private void fixFocusableAndClickableSettings() {
2559         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2560             setFocusable(FOCUSABLE);
2561             setClickable(true);
2562             setLongClickable(true);
2563         } else {
2564             setFocusable(FOCUSABLE_AUTO);
2565             setClickable(false);
2566             setLongClickable(false);
2567         }
2568     }
2569 
2570     /**
2571      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2572      * This is frequently null, except for single-line and password fields.
2573      * @return the current transformation method for this TextView.
2574      *
2575      * @attr ref android.R.styleable#TextView_password
2576      * @attr ref android.R.styleable#TextView_singleLine
2577      */
getTransformationMethod()2578     public final TransformationMethod getTransformationMethod() {
2579         return mTransformation;
2580     }
2581 
2582     /**
2583      * Sets the transformation that is applied to the text that this
2584      * TextView is displaying.
2585      *
2586      * @attr ref android.R.styleable#TextView_password
2587      * @attr ref android.R.styleable#TextView_singleLine
2588      */
setTransformationMethod(TransformationMethod method)2589     public final void setTransformationMethod(TransformationMethod method) {
2590         if (method == mTransformation) {
2591             // Avoid the setText() below if the transformation is
2592             // the same.
2593             return;
2594         }
2595         if (mTransformation != null) {
2596             if (mSpannable != null) {
2597                 mSpannable.removeSpan(mTransformation);
2598             }
2599         }
2600 
2601         mTransformation = method;
2602 
2603         if (method instanceof TransformationMethod2) {
2604             TransformationMethod2 method2 = (TransformationMethod2) method;
2605             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2606             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2607         } else {
2608             mAllowTransformationLengthChange = false;
2609         }
2610 
2611         setText(mText);
2612 
2613         if (hasPasswordTransformationMethod()) {
2614             notifyViewAccessibilityStateChangedIfNeeded(
2615                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2616         }
2617 
2618         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2619         // getTextDirectionHeuristic, needs reset
2620         mTextDir = getTextDirectionHeuristic();
2621     }
2622 
2623     /**
2624      * Returns the top padding of the view, plus space for the top
2625      * Drawable if any.
2626      */
getCompoundPaddingTop()2627     public int getCompoundPaddingTop() {
2628         final Drawables dr = mDrawables;
2629         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2630             return mPaddingTop;
2631         } else {
2632             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2633         }
2634     }
2635 
2636     /**
2637      * Returns the bottom padding of the view, plus space for the bottom
2638      * Drawable if any.
2639      */
getCompoundPaddingBottom()2640     public int getCompoundPaddingBottom() {
2641         final Drawables dr = mDrawables;
2642         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2643             return mPaddingBottom;
2644         } else {
2645             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2646         }
2647     }
2648 
2649     /**
2650      * Returns the left padding of the view, plus space for the left
2651      * Drawable if any.
2652      */
getCompoundPaddingLeft()2653     public int getCompoundPaddingLeft() {
2654         final Drawables dr = mDrawables;
2655         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2656             return mPaddingLeft;
2657         } else {
2658             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2659         }
2660     }
2661 
2662     /**
2663      * Returns the right padding of the view, plus space for the right
2664      * Drawable if any.
2665      */
getCompoundPaddingRight()2666     public int getCompoundPaddingRight() {
2667         final Drawables dr = mDrawables;
2668         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2669             return mPaddingRight;
2670         } else {
2671             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2672         }
2673     }
2674 
2675     /**
2676      * Returns the start padding of the view, plus space for the start
2677      * Drawable if any.
2678      */
getCompoundPaddingStart()2679     public int getCompoundPaddingStart() {
2680         resolveDrawables();
2681         switch(getLayoutDirection()) {
2682             default:
2683             case LAYOUT_DIRECTION_LTR:
2684                 return getCompoundPaddingLeft();
2685             case LAYOUT_DIRECTION_RTL:
2686                 return getCompoundPaddingRight();
2687         }
2688     }
2689 
2690     /**
2691      * Returns the end padding of the view, plus space for the end
2692      * Drawable if any.
2693      */
getCompoundPaddingEnd()2694     public int getCompoundPaddingEnd() {
2695         resolveDrawables();
2696         switch(getLayoutDirection()) {
2697             default:
2698             case LAYOUT_DIRECTION_LTR:
2699                 return getCompoundPaddingRight();
2700             case LAYOUT_DIRECTION_RTL:
2701                 return getCompoundPaddingLeft();
2702         }
2703     }
2704 
2705     /**
2706      * Returns the extended top padding of the view, including both the
2707      * top Drawable if any and any extra space to keep more than maxLines
2708      * of text from showing.  It is only valid to call this after measuring.
2709      */
getExtendedPaddingTop()2710     public int getExtendedPaddingTop() {
2711         if (mMaxMode != LINES) {
2712             return getCompoundPaddingTop();
2713         }
2714 
2715         if (mLayout == null) {
2716             assumeLayout();
2717         }
2718 
2719         if (mLayout.getLineCount() <= mMaximum) {
2720             return getCompoundPaddingTop();
2721         }
2722 
2723         int top = getCompoundPaddingTop();
2724         int bottom = getCompoundPaddingBottom();
2725         int viewht = getHeight() - top - bottom;
2726         int layoutht = mLayout.getLineTop(mMaximum);
2727 
2728         if (layoutht >= viewht) {
2729             return top;
2730         }
2731 
2732         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2733         if (gravity == Gravity.TOP) {
2734             return top;
2735         } else if (gravity == Gravity.BOTTOM) {
2736             return top + viewht - layoutht;
2737         } else { // (gravity == Gravity.CENTER_VERTICAL)
2738             return top + (viewht - layoutht) / 2;
2739         }
2740     }
2741 
2742     /**
2743      * Returns the extended bottom padding of the view, including both the
2744      * bottom Drawable if any and any extra space to keep more than maxLines
2745      * of text from showing.  It is only valid to call this after measuring.
2746      */
getExtendedPaddingBottom()2747     public int getExtendedPaddingBottom() {
2748         if (mMaxMode != LINES) {
2749             return getCompoundPaddingBottom();
2750         }
2751 
2752         if (mLayout == null) {
2753             assumeLayout();
2754         }
2755 
2756         if (mLayout.getLineCount() <= mMaximum) {
2757             return getCompoundPaddingBottom();
2758         }
2759 
2760         int top = getCompoundPaddingTop();
2761         int bottom = getCompoundPaddingBottom();
2762         int viewht = getHeight() - top - bottom;
2763         int layoutht = mLayout.getLineTop(mMaximum);
2764 
2765         if (layoutht >= viewht) {
2766             return bottom;
2767         }
2768 
2769         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2770         if (gravity == Gravity.TOP) {
2771             return bottom + viewht - layoutht;
2772         } else if (gravity == Gravity.BOTTOM) {
2773             return bottom;
2774         } else { // (gravity == Gravity.CENTER_VERTICAL)
2775             return bottom + (viewht - layoutht) / 2;
2776         }
2777     }
2778 
2779     /**
2780      * Returns the total left padding of the view, including the left
2781      * Drawable if any.
2782      */
getTotalPaddingLeft()2783     public int getTotalPaddingLeft() {
2784         return getCompoundPaddingLeft();
2785     }
2786 
2787     /**
2788      * Returns the total right padding of the view, including the right
2789      * Drawable if any.
2790      */
getTotalPaddingRight()2791     public int getTotalPaddingRight() {
2792         return getCompoundPaddingRight();
2793     }
2794 
2795     /**
2796      * Returns the total start padding of the view, including the start
2797      * Drawable if any.
2798      */
getTotalPaddingStart()2799     public int getTotalPaddingStart() {
2800         return getCompoundPaddingStart();
2801     }
2802 
2803     /**
2804      * Returns the total end padding of the view, including the end
2805      * Drawable if any.
2806      */
getTotalPaddingEnd()2807     public int getTotalPaddingEnd() {
2808         return getCompoundPaddingEnd();
2809     }
2810 
2811     /**
2812      * Returns the total top padding of the view, including the top
2813      * Drawable if any, the extra space to keep more than maxLines
2814      * from showing, and the vertical offset for gravity, if any.
2815      */
getTotalPaddingTop()2816     public int getTotalPaddingTop() {
2817         return getExtendedPaddingTop() + getVerticalOffset(true);
2818     }
2819 
2820     /**
2821      * Returns the total bottom padding of the view, including the bottom
2822      * Drawable if any, the extra space to keep more than maxLines
2823      * from showing, and the vertical offset for gravity, if any.
2824      */
getTotalPaddingBottom()2825     public int getTotalPaddingBottom() {
2826         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2827     }
2828 
2829     /**
2830      * Sets the Drawables (if any) to appear to the left of, above, to the
2831      * right of, and below the text. Use {@code null} if you do not want a
2832      * Drawable there. The Drawables must already have had
2833      * {@link Drawable#setBounds} called.
2834      * <p>
2835      * Calling this method will overwrite any Drawables previously set using
2836      * {@link #setCompoundDrawablesRelative} or related methods.
2837      *
2838      * @attr ref android.R.styleable#TextView_drawableLeft
2839      * @attr ref android.R.styleable#TextView_drawableTop
2840      * @attr ref android.R.styleable#TextView_drawableRight
2841      * @attr ref android.R.styleable#TextView_drawableBottom
2842      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2843     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2844             @Nullable Drawable right, @Nullable Drawable bottom) {
2845         Drawables dr = mDrawables;
2846 
2847         // We're switching to absolute, discard relative.
2848         if (dr != null) {
2849             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2850             dr.mDrawableStart = null;
2851             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2852             dr.mDrawableEnd = null;
2853             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2854             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2855         }
2856 
2857         final boolean drawables = left != null || top != null || right != null || bottom != null;
2858         if (!drawables) {
2859             // Clearing drawables...  can we free the data structure?
2860             if (dr != null) {
2861                 if (!dr.hasMetadata()) {
2862                     mDrawables = null;
2863                 } else {
2864                     // We need to retain the last set padding, so just clear
2865                     // out all of the fields in the existing structure.
2866                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2867                         if (dr.mShowing[i] != null) {
2868                             dr.mShowing[i].setCallback(null);
2869                         }
2870                         dr.mShowing[i] = null;
2871                     }
2872                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2873                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2874                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2875                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2876                 }
2877             }
2878         } else {
2879             if (dr == null) {
2880                 mDrawables = dr = new Drawables(getContext());
2881             }
2882 
2883             mDrawables.mOverride = false;
2884 
2885             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2886                 dr.mShowing[Drawables.LEFT].setCallback(null);
2887             }
2888             dr.mShowing[Drawables.LEFT] = left;
2889 
2890             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2891                 dr.mShowing[Drawables.TOP].setCallback(null);
2892             }
2893             dr.mShowing[Drawables.TOP] = top;
2894 
2895             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2896                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2897             }
2898             dr.mShowing[Drawables.RIGHT] = right;
2899 
2900             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2901                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2902             }
2903             dr.mShowing[Drawables.BOTTOM] = bottom;
2904 
2905             final Rect compoundRect = dr.mCompoundRect;
2906             int[] state;
2907 
2908             state = getDrawableState();
2909 
2910             if (left != null) {
2911                 left.setState(state);
2912                 left.copyBounds(compoundRect);
2913                 left.setCallback(this);
2914                 dr.mDrawableSizeLeft = compoundRect.width();
2915                 dr.mDrawableHeightLeft = compoundRect.height();
2916             } else {
2917                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2918             }
2919 
2920             if (right != null) {
2921                 right.setState(state);
2922                 right.copyBounds(compoundRect);
2923                 right.setCallback(this);
2924                 dr.mDrawableSizeRight = compoundRect.width();
2925                 dr.mDrawableHeightRight = compoundRect.height();
2926             } else {
2927                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2928             }
2929 
2930             if (top != null) {
2931                 top.setState(state);
2932                 top.copyBounds(compoundRect);
2933                 top.setCallback(this);
2934                 dr.mDrawableSizeTop = compoundRect.height();
2935                 dr.mDrawableWidthTop = compoundRect.width();
2936             } else {
2937                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2938             }
2939 
2940             if (bottom != null) {
2941                 bottom.setState(state);
2942                 bottom.copyBounds(compoundRect);
2943                 bottom.setCallback(this);
2944                 dr.mDrawableSizeBottom = compoundRect.height();
2945                 dr.mDrawableWidthBottom = compoundRect.width();
2946             } else {
2947                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2948             }
2949         }
2950 
2951         // Save initial left/right drawables
2952         if (dr != null) {
2953             dr.mDrawableLeftInitial = left;
2954             dr.mDrawableRightInitial = right;
2955         }
2956 
2957         resetResolvedDrawables();
2958         resolveDrawables();
2959         applyCompoundDrawableTint();
2960         invalidate();
2961         requestLayout();
2962     }
2963 
2964     /**
2965      * Sets the Drawables (if any) to appear to the left of, above, to the
2966      * right of, and below the text. Use 0 if you do not want a Drawable there.
2967      * The Drawables' bounds will be set to their intrinsic bounds.
2968      * <p>
2969      * Calling this method will overwrite any Drawables previously set using
2970      * {@link #setCompoundDrawablesRelative} or related methods.
2971      *
2972      * @param left Resource identifier of the left Drawable.
2973      * @param top Resource identifier of the top Drawable.
2974      * @param right Resource identifier of the right Drawable.
2975      * @param bottom Resource identifier of the bottom Drawable.
2976      *
2977      * @attr ref android.R.styleable#TextView_drawableLeft
2978      * @attr ref android.R.styleable#TextView_drawableTop
2979      * @attr ref android.R.styleable#TextView_drawableRight
2980      * @attr ref android.R.styleable#TextView_drawableBottom
2981      */
2982     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)2983     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2984             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2985         final Context context = getContext();
2986         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2987                 top != 0 ? context.getDrawable(top) : null,
2988                 right != 0 ? context.getDrawable(right) : null,
2989                 bottom != 0 ? context.getDrawable(bottom) : null);
2990     }
2991 
2992     /**
2993      * Sets the Drawables (if any) to appear to the left of, above, to the
2994      * right of, and below the text. Use {@code null} if you do not want a
2995      * Drawable there. The Drawables' bounds will be set to their intrinsic
2996      * bounds.
2997      * <p>
2998      * Calling this method will overwrite any Drawables previously set using
2999      * {@link #setCompoundDrawablesRelative} or related methods.
3000      *
3001      * @attr ref android.R.styleable#TextView_drawableLeft
3002      * @attr ref android.R.styleable#TextView_drawableTop
3003      * @attr ref android.R.styleable#TextView_drawableRight
3004      * @attr ref android.R.styleable#TextView_drawableBottom
3005      */
3006     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3007     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
3008             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
3009 
3010         if (left != null) {
3011             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
3012         }
3013         if (right != null) {
3014             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
3015         }
3016         if (top != null) {
3017             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3018         }
3019         if (bottom != null) {
3020             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3021         }
3022         setCompoundDrawables(left, top, right, bottom);
3023     }
3024 
3025     /**
3026      * Sets the Drawables (if any) to appear to the start of, above, to the end
3027      * of, and below the text. Use {@code null} if you do not want a Drawable
3028      * there. The Drawables must already have had {@link Drawable#setBounds}
3029      * called.
3030      * <p>
3031      * Calling this method will overwrite any Drawables previously set using
3032      * {@link #setCompoundDrawables} or related methods.
3033      *
3034      * @attr ref android.R.styleable#TextView_drawableStart
3035      * @attr ref android.R.styleable#TextView_drawableTop
3036      * @attr ref android.R.styleable#TextView_drawableEnd
3037      * @attr ref android.R.styleable#TextView_drawableBottom
3038      */
3039     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3040     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
3041             @Nullable Drawable end, @Nullable Drawable bottom) {
3042         Drawables dr = mDrawables;
3043 
3044         // We're switching to relative, discard absolute.
3045         if (dr != null) {
3046             if (dr.mShowing[Drawables.LEFT] != null) {
3047                 dr.mShowing[Drawables.LEFT].setCallback(null);
3048             }
3049             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3050             if (dr.mShowing[Drawables.RIGHT] != null) {
3051                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3052             }
3053             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3054             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3055             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3056         }
3057 
3058         final boolean drawables = start != null || top != null
3059                 || end != null || bottom != null;
3060 
3061         if (!drawables) {
3062             // Clearing drawables...  can we free the data structure?
3063             if (dr != null) {
3064                 if (!dr.hasMetadata()) {
3065                     mDrawables = null;
3066                 } else {
3067                     // We need to retain the last set padding, so just clear
3068                     // out all of the fields in the existing structure.
3069                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3070                     dr.mDrawableStart = null;
3071                     if (dr.mShowing[Drawables.TOP] != null) {
3072                         dr.mShowing[Drawables.TOP].setCallback(null);
3073                     }
3074                     dr.mShowing[Drawables.TOP] = null;
3075                     if (dr.mDrawableEnd != null) {
3076                         dr.mDrawableEnd.setCallback(null);
3077                     }
3078                     dr.mDrawableEnd = null;
3079                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3080                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3081                     }
3082                     dr.mShowing[Drawables.BOTTOM] = null;
3083                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3084                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3085                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3086                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3087                 }
3088             }
3089         } else {
3090             if (dr == null) {
3091                 mDrawables = dr = new Drawables(getContext());
3092             }
3093 
3094             mDrawables.mOverride = true;
3095 
3096             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3097                 dr.mDrawableStart.setCallback(null);
3098             }
3099             dr.mDrawableStart = start;
3100 
3101             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3102                 dr.mShowing[Drawables.TOP].setCallback(null);
3103             }
3104             dr.mShowing[Drawables.TOP] = top;
3105 
3106             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3107                 dr.mDrawableEnd.setCallback(null);
3108             }
3109             dr.mDrawableEnd = end;
3110 
3111             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3112                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3113             }
3114             dr.mShowing[Drawables.BOTTOM] = bottom;
3115 
3116             final Rect compoundRect = dr.mCompoundRect;
3117             int[] state;
3118 
3119             state = getDrawableState();
3120 
3121             if (start != null) {
3122                 start.setState(state);
3123                 start.copyBounds(compoundRect);
3124                 start.setCallback(this);
3125                 dr.mDrawableSizeStart = compoundRect.width();
3126                 dr.mDrawableHeightStart = compoundRect.height();
3127             } else {
3128                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3129             }
3130 
3131             if (end != null) {
3132                 end.setState(state);
3133                 end.copyBounds(compoundRect);
3134                 end.setCallback(this);
3135                 dr.mDrawableSizeEnd = compoundRect.width();
3136                 dr.mDrawableHeightEnd = compoundRect.height();
3137             } else {
3138                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3139             }
3140 
3141             if (top != null) {
3142                 top.setState(state);
3143                 top.copyBounds(compoundRect);
3144                 top.setCallback(this);
3145                 dr.mDrawableSizeTop = compoundRect.height();
3146                 dr.mDrawableWidthTop = compoundRect.width();
3147             } else {
3148                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3149             }
3150 
3151             if (bottom != null) {
3152                 bottom.setState(state);
3153                 bottom.copyBounds(compoundRect);
3154                 bottom.setCallback(this);
3155                 dr.mDrawableSizeBottom = compoundRect.height();
3156                 dr.mDrawableWidthBottom = compoundRect.width();
3157             } else {
3158                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3159             }
3160         }
3161 
3162         resetResolvedDrawables();
3163         resolveDrawables();
3164         invalidate();
3165         requestLayout();
3166     }
3167 
3168     /**
3169      * Sets the Drawables (if any) to appear to the start of, above, to the end
3170      * of, and below the text. Use 0 if you do not want a Drawable there. The
3171      * Drawables' bounds will be set to their intrinsic bounds.
3172      * <p>
3173      * Calling this method will overwrite any Drawables previously set using
3174      * {@link #setCompoundDrawables} or related methods.
3175      *
3176      * @param start Resource identifier of the start Drawable.
3177      * @param top Resource identifier of the top Drawable.
3178      * @param end Resource identifier of the end Drawable.
3179      * @param bottom Resource identifier of the bottom Drawable.
3180      *
3181      * @attr ref android.R.styleable#TextView_drawableStart
3182      * @attr ref android.R.styleable#TextView_drawableTop
3183      * @attr ref android.R.styleable#TextView_drawableEnd
3184      * @attr ref android.R.styleable#TextView_drawableBottom
3185      */
3186     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3187     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3188             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3189         final Context context = getContext();
3190         setCompoundDrawablesRelativeWithIntrinsicBounds(
3191                 start != 0 ? context.getDrawable(start) : null,
3192                 top != 0 ? context.getDrawable(top) : null,
3193                 end != 0 ? context.getDrawable(end) : null,
3194                 bottom != 0 ? context.getDrawable(bottom) : null);
3195     }
3196 
3197     /**
3198      * Sets the Drawables (if any) to appear to the start of, above, to the end
3199      * of, and below the text. Use {@code null} if you do not want a Drawable
3200      * there. The Drawables' bounds will be set to their intrinsic bounds.
3201      * <p>
3202      * Calling this method will overwrite any Drawables previously set using
3203      * {@link #setCompoundDrawables} or related methods.
3204      *
3205      * @attr ref android.R.styleable#TextView_drawableStart
3206      * @attr ref android.R.styleable#TextView_drawableTop
3207      * @attr ref android.R.styleable#TextView_drawableEnd
3208      * @attr ref android.R.styleable#TextView_drawableBottom
3209      */
3210     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3211     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3212             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3213 
3214         if (start != null) {
3215             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3216         }
3217         if (end != null) {
3218             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3219         }
3220         if (top != null) {
3221             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3222         }
3223         if (bottom != null) {
3224             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3225         }
3226         setCompoundDrawablesRelative(start, top, end, bottom);
3227     }
3228 
3229     /**
3230      * Returns drawables for the left, top, right, and bottom borders.
3231      *
3232      * @attr ref android.R.styleable#TextView_drawableLeft
3233      * @attr ref android.R.styleable#TextView_drawableTop
3234      * @attr ref android.R.styleable#TextView_drawableRight
3235      * @attr ref android.R.styleable#TextView_drawableBottom
3236      */
3237     @NonNull
getCompoundDrawables()3238     public Drawable[] getCompoundDrawables() {
3239         final Drawables dr = mDrawables;
3240         if (dr != null) {
3241             return dr.mShowing.clone();
3242         } else {
3243             return new Drawable[] { null, null, null, null };
3244         }
3245     }
3246 
3247     /**
3248      * Returns drawables for the start, top, end, and bottom borders.
3249      *
3250      * @attr ref android.R.styleable#TextView_drawableStart
3251      * @attr ref android.R.styleable#TextView_drawableTop
3252      * @attr ref android.R.styleable#TextView_drawableEnd
3253      * @attr ref android.R.styleable#TextView_drawableBottom
3254      */
3255     @NonNull
getCompoundDrawablesRelative()3256     public Drawable[] getCompoundDrawablesRelative() {
3257         final Drawables dr = mDrawables;
3258         if (dr != null) {
3259             return new Drawable[] {
3260                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3261                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3262             };
3263         } else {
3264             return new Drawable[] { null, null, null, null };
3265         }
3266     }
3267 
3268     /**
3269      * Sets the size of the padding between the compound drawables and
3270      * the text.
3271      *
3272      * @attr ref android.R.styleable#TextView_drawablePadding
3273      */
3274     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3275     public void setCompoundDrawablePadding(int pad) {
3276         Drawables dr = mDrawables;
3277         if (pad == 0) {
3278             if (dr != null) {
3279                 dr.mDrawablePadding = pad;
3280             }
3281         } else {
3282             if (dr == null) {
3283                 mDrawables = dr = new Drawables(getContext());
3284             }
3285             dr.mDrawablePadding = pad;
3286         }
3287 
3288         invalidate();
3289         requestLayout();
3290     }
3291 
3292     /**
3293      * Returns the padding between the compound drawables and the text.
3294      *
3295      * @attr ref android.R.styleable#TextView_drawablePadding
3296      */
3297     @InspectableProperty(name = "drawablePadding")
getCompoundDrawablePadding()3298     public int getCompoundDrawablePadding() {
3299         final Drawables dr = mDrawables;
3300         return dr != null ? dr.mDrawablePadding : 0;
3301     }
3302 
3303     /**
3304      * Applies a tint to the compound drawables. Does not modify the
3305      * current tint mode, which is {@link BlendMode#SRC_IN} by default.
3306      * <p>
3307      * Subsequent calls to
3308      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3309      * and related methods will automatically mutate the drawables and apply
3310      * the specified tint and tint mode using
3311      * {@link Drawable#setTintList(ColorStateList)}.
3312      *
3313      * @param tint the tint to apply, may be {@code null} to clear tint
3314      *
3315      * @attr ref android.R.styleable#TextView_drawableTint
3316      * @see #getCompoundDrawableTintList()
3317      * @see Drawable#setTintList(ColorStateList)
3318      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3319     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3320         if (mDrawables == null) {
3321             mDrawables = new Drawables(getContext());
3322         }
3323         mDrawables.mTintList = tint;
3324         mDrawables.mHasTint = true;
3325 
3326         applyCompoundDrawableTint();
3327     }
3328 
3329     /**
3330      * @return the tint applied to the compound drawables
3331      * @attr ref android.R.styleable#TextView_drawableTint
3332      * @see #setCompoundDrawableTintList(ColorStateList)
3333      */
3334     @InspectableProperty(name = "drawableTint")
getCompoundDrawableTintList()3335     public ColorStateList getCompoundDrawableTintList() {
3336         return mDrawables != null ? mDrawables.mTintList : null;
3337     }
3338 
3339     /**
3340      * Specifies the blending mode used to apply the tint specified by
3341      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3342      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3343      *
3344      * @param tintMode the blending mode used to apply the tint, may be
3345      *                 {@code null} to clear tint
3346      * @attr ref android.R.styleable#TextView_drawableTintMode
3347      * @see #setCompoundDrawableTintList(ColorStateList)
3348      * @see Drawable#setTintMode(PorterDuff.Mode)
3349      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3350     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3351         setCompoundDrawableTintBlendMode(tintMode != null
3352                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3353     }
3354 
3355     /**
3356      * Specifies the blending mode used to apply the tint specified by
3357      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3358      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3359      *
3360      * @param blendMode the blending mode used to apply the tint, may be
3361      *                 {@code null} to clear tint
3362      * @attr ref android.R.styleable#TextView_drawableTintMode
3363      * @see #setCompoundDrawableTintList(ColorStateList)
3364      * @see Drawable#setTintBlendMode(BlendMode)
3365      */
setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3366     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3367         if (mDrawables == null) {
3368             mDrawables = new Drawables(getContext());
3369         }
3370         mDrawables.mBlendMode = blendMode;
3371         mDrawables.mHasTintMode = true;
3372 
3373         applyCompoundDrawableTint();
3374     }
3375 
3376     /**
3377      * Returns the blending mode used to apply the tint to the compound
3378      * drawables, if specified.
3379      *
3380      * @return the blending mode used to apply the tint to the compound
3381      *         drawables
3382      * @attr ref android.R.styleable#TextView_drawableTintMode
3383      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3384      *
3385      */
3386     @InspectableProperty(name = "drawableTintMode")
getCompoundDrawableTintMode()3387     public PorterDuff.Mode getCompoundDrawableTintMode() {
3388         BlendMode mode = getCompoundDrawableTintBlendMode();
3389         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3390     }
3391 
3392     /**
3393      * Returns the blending mode used to apply the tint to the compound
3394      * drawables, if specified.
3395      *
3396      * @return the blending mode used to apply the tint to the compound
3397      *         drawables
3398      * @attr ref android.R.styleable#TextView_drawableTintMode
3399      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3400      */
3401     @InspectableProperty(name = "drawableBlendMode",
3402             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
getCompoundDrawableTintBlendMode()3403     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3404         return mDrawables != null ? mDrawables.mBlendMode : null;
3405     }
3406 
applyCompoundDrawableTint()3407     private void applyCompoundDrawableTint() {
3408         if (mDrawables == null) {
3409             return;
3410         }
3411 
3412         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3413             final ColorStateList tintList = mDrawables.mTintList;
3414             final BlendMode blendMode = mDrawables.mBlendMode;
3415             final boolean hasTint = mDrawables.mHasTint;
3416             final boolean hasTintMode = mDrawables.mHasTintMode;
3417             final int[] state = getDrawableState();
3418 
3419             for (Drawable dr : mDrawables.mShowing) {
3420                 if (dr == null) {
3421                     continue;
3422                 }
3423 
3424                 if (dr == mDrawables.mDrawableError) {
3425                     // From a developer's perspective, the error drawable isn't
3426                     // a compound drawable. Don't apply the generic compound
3427                     // drawable tint to it.
3428                     continue;
3429                 }
3430 
3431                 dr.mutate();
3432 
3433                 if (hasTint) {
3434                     dr.setTintList(tintList);
3435                 }
3436 
3437                 if (hasTintMode) {
3438                     dr.setTintBlendMode(blendMode);
3439                 }
3440 
3441                 // The drawable (or one of its children) may not have been
3442                 // stateful before applying the tint, so let's try again.
3443                 if (dr.isStateful()) {
3444                     dr.setState(state);
3445                 }
3446             }
3447         }
3448     }
3449 
3450     /**
3451      * @inheritDoc
3452      *
3453      * @see #setFirstBaselineToTopHeight(int)
3454      * @see #setLastBaselineToBottomHeight(int)
3455      */
3456     @Override
setPadding(int left, int top, int right, int bottom)3457     public void setPadding(int left, int top, int right, int bottom) {
3458         if (left != mPaddingLeft
3459                 || right != mPaddingRight
3460                 || top != mPaddingTop
3461                 ||  bottom != mPaddingBottom) {
3462             nullLayouts();
3463         }
3464 
3465         // the super call will requestLayout()
3466         super.setPadding(left, top, right, bottom);
3467         invalidate();
3468     }
3469 
3470     /**
3471      * @inheritDoc
3472      *
3473      * @see #setFirstBaselineToTopHeight(int)
3474      * @see #setLastBaselineToBottomHeight(int)
3475      */
3476     @Override
setPaddingRelative(int start, int top, int end, int bottom)3477     public void setPaddingRelative(int start, int top, int end, int bottom) {
3478         if (start != getPaddingStart()
3479                 || end != getPaddingEnd()
3480                 || top != mPaddingTop
3481                 || bottom != mPaddingBottom) {
3482             nullLayouts();
3483         }
3484 
3485         // the super call will requestLayout()
3486         super.setPaddingRelative(start, top, end, bottom);
3487         invalidate();
3488     }
3489 
3490     /**
3491      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3492      * the distance between the top of the TextView and first line's baseline.
3493      * <p>
3494      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3495      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3496      *
3497      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3498      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3499      * Moreover since this function sets the top padding, if the height of the TextView is less than
3500      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3501      * down and bottom will be clipped.
3502      *
3503      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3504      *      in pixels
3505      *
3506      * @see #getFirstBaselineToTopHeight()
3507      * @see #setLastBaselineToBottomHeight(int)
3508      * @see #setPadding(int, int, int, int)
3509      * @see #setPaddingRelative(int, int, int, int)
3510      *
3511      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3512      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3513     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3514         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3515 
3516         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3517         final int fontMetricsTop;
3518         if (getIncludeFontPadding()) {
3519             fontMetricsTop = fontMetrics.top;
3520         } else {
3521             fontMetricsTop = fontMetrics.ascent;
3522         }
3523 
3524         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3525         // in settings). At the moment, we don't.
3526 
3527         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3528             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3529             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3530         }
3531     }
3532 
3533     /**
3534      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3535      * the distance between the bottom of the TextView and the last line's baseline.
3536      * <p>
3537      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3538      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3539      *
3540      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3541      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3542      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3543      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3544      * clipped.
3545      *
3546      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3547      *      in pixels
3548      *
3549      * @see #getLastBaselineToBottomHeight()
3550      * @see #setFirstBaselineToTopHeight(int)
3551      * @see #setPadding(int, int, int, int)
3552      * @see #setPaddingRelative(int, int, int, int)
3553      *
3554      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3555      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3556     public void setLastBaselineToBottomHeight(
3557             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3558         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3559 
3560         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3561         final int fontMetricsBottom;
3562         if (getIncludeFontPadding()) {
3563             fontMetricsBottom = fontMetrics.bottom;
3564         } else {
3565             fontMetricsBottom = fontMetrics.descent;
3566         }
3567 
3568         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3569         // in settings). At the moment, we don't.
3570 
3571         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3572             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3573             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3574         }
3575     }
3576 
3577     /**
3578      * Returns the distance between the first text baseline and the top of this TextView.
3579      *
3580      * @see #setFirstBaselineToTopHeight(int)
3581      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3582      */
3583     @InspectableProperty
getFirstBaselineToTopHeight()3584     public int getFirstBaselineToTopHeight() {
3585         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3586     }
3587 
3588     /**
3589      * Returns the distance between the last text baseline and the bottom of this TextView.
3590      *
3591      * @see #setLastBaselineToBottomHeight(int)
3592      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3593      */
3594     @InspectableProperty
getLastBaselineToBottomHeight()3595     public int getLastBaselineToBottomHeight() {
3596         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3597     }
3598 
3599     /**
3600      * Gets the autolink mask of the text.
3601      *
3602      * See {@link Linkify#ALL} and peers for possible values.
3603      *
3604      * @attr ref android.R.styleable#TextView_autoLink
3605      */
3606     @InspectableProperty(name = "autoLink", flagMapping = {
3607             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3608             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3609             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3610             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3611     })
getAutoLinkMask()3612     public final int getAutoLinkMask() {
3613         return mAutoLinkMask;
3614     }
3615 
3616     /**
3617      * Sets the Drawable corresponding to the selection handle used for
3618      * positioning the cursor within text. The Drawable defaults to the value
3619      * of the textSelectHandle attribute.
3620      * Note that any change applied to the handle Drawable will not be visible
3621      * until the handle is hidden and then drawn again.
3622      *
3623      * @see #setTextSelectHandle(int)
3624      * @attr ref android.R.styleable#TextView_textSelectHandle
3625      */
3626     @android.view.RemotableViewMethod
setTextSelectHandle(@onNull Drawable textSelectHandle)3627     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3628         Preconditions.checkNotNull(textSelectHandle,
3629                 "The text select handle should not be null.");
3630         mTextSelectHandle = textSelectHandle;
3631         mTextSelectHandleRes = 0;
3632         if (mEditor != null) {
3633             mEditor.loadHandleDrawables(true /* overwrite */);
3634         }
3635     }
3636 
3637     /**
3638      * Sets the Drawable corresponding to the selection handle used for
3639      * positioning the cursor within text. The Drawable defaults to the value
3640      * of the textSelectHandle attribute.
3641      * Note that any change applied to the handle Drawable will not be visible
3642      * until the handle is hidden and then drawn again.
3643      *
3644      * @see #setTextSelectHandle(Drawable)
3645      * @attr ref android.R.styleable#TextView_textSelectHandle
3646      */
3647     @android.view.RemotableViewMethod
setTextSelectHandle(@rawableRes int textSelectHandle)3648     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3649         Preconditions.checkArgument(textSelectHandle != 0,
3650                 "The text select handle should be a valid drawable resource id.");
3651         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3652     }
3653 
3654     /**
3655      * Returns the Drawable corresponding to the selection handle used
3656      * for positioning the cursor within text.
3657      * Note that any change applied to the handle Drawable will not be visible
3658      * until the handle is hidden and then drawn again.
3659      *
3660      * @return the text select handle drawable
3661      *
3662      * @see #setTextSelectHandle(Drawable)
3663      * @see #setTextSelectHandle(int)
3664      * @attr ref android.R.styleable#TextView_textSelectHandle
3665      */
getTextSelectHandle()3666     @Nullable public Drawable getTextSelectHandle() {
3667         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3668             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3669         }
3670         return mTextSelectHandle;
3671     }
3672 
3673     /**
3674      * Sets the Drawable corresponding to the left handle used
3675      * for selecting text. The Drawable defaults to the value of the
3676      * textSelectHandleLeft attribute.
3677      * Note that any change applied to the handle Drawable will not be visible
3678      * until the handle is hidden and then drawn again.
3679      *
3680      * @see #setTextSelectHandleLeft(int)
3681      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3682      */
3683     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3684     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3685         Preconditions.checkNotNull(textSelectHandleLeft,
3686                 "The left text select handle should not be null.");
3687         mTextSelectHandleLeft = textSelectHandleLeft;
3688         mTextSelectHandleLeftRes = 0;
3689         if (mEditor != null) {
3690             mEditor.loadHandleDrawables(true /* overwrite */);
3691         }
3692     }
3693 
3694     /**
3695      * Sets the Drawable corresponding to the left handle used
3696      * for selecting text. The Drawable defaults to the value of the
3697      * textSelectHandleLeft attribute.
3698      * Note that any change applied to the handle Drawable will not be visible
3699      * until the handle is hidden and then drawn again.
3700      *
3701      * @see #setTextSelectHandleLeft(Drawable)
3702      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3703      */
3704     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3705     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
3706         Preconditions.checkArgument(textSelectHandleLeft != 0,
3707                 "The text select left handle should be a valid drawable resource id.");
3708         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
3709     }
3710 
3711     /**
3712      * Returns the Drawable corresponding to the left handle used
3713      * for selecting text.
3714      * Note that any change applied to the handle Drawable will not be visible
3715      * until the handle is hidden and then drawn again.
3716      *
3717      * @return the left text selection handle drawable
3718      *
3719      * @see #setTextSelectHandleLeft(Drawable)
3720      * @see #setTextSelectHandleLeft(int)
3721      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3722      */
getTextSelectHandleLeft()3723     @Nullable public Drawable getTextSelectHandleLeft() {
3724         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
3725             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
3726         }
3727         return mTextSelectHandleLeft;
3728     }
3729 
3730     /**
3731      * Sets the Drawable corresponding to the right handle used
3732      * for selecting text. The Drawable defaults to the value of the
3733      * textSelectHandleRight attribute.
3734      * Note that any change applied to the handle Drawable will not be visible
3735      * until the handle is hidden and then drawn again.
3736      *
3737      * @see #setTextSelectHandleRight(int)
3738      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3739      */
3740     @android.view.RemotableViewMethod
setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3741     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
3742         Preconditions.checkNotNull(textSelectHandleRight,
3743                 "The right text select handle should not be null.");
3744         mTextSelectHandleRight = textSelectHandleRight;
3745         mTextSelectHandleRightRes = 0;
3746         if (mEditor != null) {
3747             mEditor.loadHandleDrawables(true /* overwrite */);
3748         }
3749     }
3750 
3751     /**
3752      * Sets the Drawable corresponding to the right handle used
3753      * for selecting text. The Drawable defaults to the value of the
3754      * textSelectHandleRight attribute.
3755      * Note that any change applied to the handle Drawable will not be visible
3756      * until the handle is hidden and then drawn again.
3757      *
3758      * @see #setTextSelectHandleRight(Drawable)
3759      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3760      */
3761     @android.view.RemotableViewMethod
setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3762     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
3763         Preconditions.checkArgument(textSelectHandleRight != 0,
3764                 "The text select right handle should be a valid drawable resource id.");
3765         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
3766     }
3767 
3768     /**
3769      * Returns the Drawable corresponding to the right handle used
3770      * for selecting text.
3771      * Note that any change applied to the handle Drawable will not be visible
3772      * until the handle is hidden and then drawn again.
3773      *
3774      * @return the right text selection handle drawable
3775      *
3776      * @see #setTextSelectHandleRight(Drawable)
3777      * @see #setTextSelectHandleRight(int)
3778      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3779      */
getTextSelectHandleRight()3780     @Nullable public Drawable getTextSelectHandleRight() {
3781         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
3782             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
3783         }
3784         return mTextSelectHandleRight;
3785     }
3786 
3787     /**
3788      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3789      * value of the textCursorDrawable attribute.
3790      * Note that any change applied to the cursor Drawable will not be visible
3791      * until the cursor is hidden and then drawn again.
3792      *
3793      * @see #setTextCursorDrawable(int)
3794      * @attr ref android.R.styleable#TextView_textCursorDrawable
3795      */
setTextCursorDrawable(@ullable Drawable textCursorDrawable)3796     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
3797         mCursorDrawable = textCursorDrawable;
3798         mCursorDrawableRes = 0;
3799         if (mEditor != null) {
3800             mEditor.loadCursorDrawable();
3801         }
3802     }
3803 
3804     /**
3805      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3806      * value of the textCursorDrawable attribute.
3807      * Note that any change applied to the cursor Drawable will not be visible
3808      * until the cursor is hidden and then drawn again.
3809      *
3810      * @see #setTextCursorDrawable(Drawable)
3811      * @attr ref android.R.styleable#TextView_textCursorDrawable
3812      */
setTextCursorDrawable(@rawableRes int textCursorDrawable)3813     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
3814         setTextCursorDrawable(
3815                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
3816     }
3817 
3818     /**
3819      * Returns the Drawable corresponding to the text cursor.
3820      * Note that any change applied to the cursor Drawable will not be visible
3821      * until the cursor is hidden and then drawn again.
3822      *
3823      * @return the text cursor drawable
3824      *
3825      * @see #setTextCursorDrawable(Drawable)
3826      * @see #setTextCursorDrawable(int)
3827      * @attr ref android.R.styleable#TextView_textCursorDrawable
3828      */
getTextCursorDrawable()3829     @Nullable public Drawable getTextCursorDrawable() {
3830         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
3831             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
3832         }
3833         return mCursorDrawable;
3834     }
3835 
3836     /**
3837      * Sets the text appearance from the specified style resource.
3838      * <p>
3839      * Use a framework-defined {@code TextAppearance} style like
3840      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3841      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3842      * set of attributes that can be used in a custom style.
3843      *
3844      * @param resId the resource identifier of the style to apply
3845      * @attr ref android.R.styleable#TextView_textAppearance
3846      */
3847     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)3848     public void setTextAppearance(@StyleRes int resId) {
3849         setTextAppearance(mContext, resId);
3850     }
3851 
3852     /**
3853      * Sets the text color, size, style, hint color, and highlight color
3854      * from the specified TextAppearance resource.
3855      *
3856      * @deprecated Use {@link #setTextAppearance(int)} instead.
3857      */
3858     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)3859     public void setTextAppearance(Context context, @StyleRes int resId) {
3860         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3861         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
3862         readTextAppearance(context, ta, attributes, false /* styleArray */);
3863         ta.recycle();
3864         applyTextAppearance(attributes);
3865     }
3866 
3867     /**
3868      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
3869      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
3870      */
3871     private static class TextAppearanceAttributes {
3872         int mTextColorHighlight = 0;
3873         ColorStateList mTextColor = null;
3874         ColorStateList mTextColorHint = null;
3875         ColorStateList mTextColorLink = null;
3876         int mTextSize = -1;
3877         int mTextSizeUnit = -1;
3878         LocaleList mTextLocales = null;
3879         String mFontFamily = null;
3880         Typeface mFontTypeface = null;
3881         boolean mFontFamilyExplicit = false;
3882         int mTypefaceIndex = -1;
3883         int mTextStyle = 0;
3884         int mFontWeight = -1;
3885         boolean mAllCaps = false;
3886         int mShadowColor = 0;
3887         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
3888         boolean mHasElegant = false;
3889         boolean mElegant = false;
3890         boolean mHasFallbackLineSpacing = false;
3891         boolean mFallbackLineSpacing = false;
3892         boolean mHasLetterSpacing = false;
3893         float mLetterSpacing = 0;
3894         String mFontFeatureSettings = null;
3895         String mFontVariationSettings = null;
3896 
3897         @Override
toString()3898         public String toString() {
3899             return "TextAppearanceAttributes {\n"
3900                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
3901                     + "    mTextColor:" + mTextColor + "\n"
3902                     + "    mTextColorHint:" + mTextColorHint + "\n"
3903                     + "    mTextColorLink:" + mTextColorLink + "\n"
3904                     + "    mTextSize:" + mTextSize + "\n"
3905                     + "    mTextSizeUnit:" + mTextSizeUnit + "\n"
3906                     + "    mTextLocales:" + mTextLocales + "\n"
3907                     + "    mFontFamily:" + mFontFamily + "\n"
3908                     + "    mFontTypeface:" + mFontTypeface + "\n"
3909                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
3910                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
3911                     + "    mTextStyle:" + mTextStyle + "\n"
3912                     + "    mFontWeight:" + mFontWeight + "\n"
3913                     + "    mAllCaps:" + mAllCaps + "\n"
3914                     + "    mShadowColor:" + mShadowColor + "\n"
3915                     + "    mShadowDx:" + mShadowDx + "\n"
3916                     + "    mShadowDy:" + mShadowDy + "\n"
3917                     + "    mShadowRadius:" + mShadowRadius + "\n"
3918                     + "    mHasElegant:" + mHasElegant + "\n"
3919                     + "    mElegant:" + mElegant + "\n"
3920                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
3921                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
3922                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
3923                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
3924                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
3925                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
3926                     + "}";
3927         }
3928     }
3929 
3930     // Maps styleable attributes that exist both in TextView style and TextAppearance.
3931     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
3932     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3933         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
3934                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3935         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
3936                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3937         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
3938                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3939         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
3940                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3941         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
3942                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3943         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
3944                 com.android.internal.R.styleable.TextAppearance_textLocale);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3945         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
3946                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3947         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
3948                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3949         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
3950                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)3951         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
3952                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)3953         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
3954                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)3955         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
3956                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)3957         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
3958                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)3959         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
3960                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)3961         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
3962                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)3963         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
3964                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)3965         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
3966                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)3967         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
3968                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)3969         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
3970                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)3971         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
3972                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
3973     }
3974 
3975     /**
3976      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
3977      * set. If the TypedArray contains a value that was already set in the given attributes, that
3978      * will be overridden.
3979      *
3980      * @param context The Context to be used
3981      * @param appearance The TypedArray to read properties from
3982      * @param attributes the TextAppearanceAttributes to fill in
3983      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
3984      *                   what attribute indexes will be used to read the properties.
3985      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)3986     private void readTextAppearance(Context context, TypedArray appearance,
3987             TextAppearanceAttributes attributes, boolean styleArray) {
3988         final int n = appearance.getIndexCount();
3989         for (int i = 0; i < n; i++) {
3990             final int attr = appearance.getIndex(i);
3991             int index = attr;
3992             // Translate style array index ids to TextAppearance ids.
3993             if (styleArray) {
3994                 index = sAppearanceValues.get(attr, -1);
3995                 if (index == -1) {
3996                     // This value is not part of a Text Appearance and should be ignored.
3997                     continue;
3998                 }
3999             }
4000             switch (index) {
4001                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
4002                     attributes.mTextColorHighlight =
4003                             appearance.getColor(attr, attributes.mTextColorHighlight);
4004                     break;
4005                 case com.android.internal.R.styleable.TextAppearance_textColor:
4006                     attributes.mTextColor = appearance.getColorStateList(attr);
4007                     break;
4008                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
4009                     attributes.mTextColorHint = appearance.getColorStateList(attr);
4010                     break;
4011                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
4012                     attributes.mTextColorLink = appearance.getColorStateList(attr);
4013                     break;
4014                 case com.android.internal.R.styleable.TextAppearance_textSize:
4015                     attributes.mTextSize =
4016                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
4017                     attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit();
4018                     break;
4019                 case com.android.internal.R.styleable.TextAppearance_textLocale:
4020                     final String localeString = appearance.getString(attr);
4021                     if (localeString != null) {
4022                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
4023                         if (!localeList.isEmpty()) {
4024                             attributes.mTextLocales = localeList;
4025                         }
4026                     }
4027                     break;
4028                 case com.android.internal.R.styleable.TextAppearance_typeface:
4029                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
4030                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4031                         attributes.mFontFamily = null;
4032                     }
4033                     break;
4034                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
4035                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
4036                         try {
4037                             attributes.mFontTypeface = appearance.getFont(attr);
4038                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
4039                             // Expected if it is not a font resource.
4040                         }
4041                     }
4042                     if (attributes.mFontTypeface == null) {
4043                         attributes.mFontFamily = appearance.getString(attr);
4044                     }
4045                     attributes.mFontFamilyExplicit = true;
4046                     break;
4047                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4048                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4049                     break;
4050                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4051                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4052                     break;
4053                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4054                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4055                     break;
4056                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4057                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4058                     break;
4059                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4060                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4061                     break;
4062                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4063                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4064                     break;
4065                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4066                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4067                     break;
4068                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4069                     attributes.mHasElegant = true;
4070                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4071                     break;
4072                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4073                     attributes.mHasFallbackLineSpacing = true;
4074                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4075                             attributes.mFallbackLineSpacing);
4076                     break;
4077                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4078                     attributes.mHasLetterSpacing = true;
4079                     attributes.mLetterSpacing =
4080                             appearance.getFloat(attr, attributes.mLetterSpacing);
4081                     break;
4082                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4083                     attributes.mFontFeatureSettings = appearance.getString(attr);
4084                     break;
4085                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4086                     attributes.mFontVariationSettings = appearance.getString(attr);
4087                     break;
4088                 default:
4089             }
4090         }
4091     }
4092 
applyTextAppearance(TextAppearanceAttributes attributes)4093     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4094         if (attributes.mTextColor != null) {
4095             setTextColor(attributes.mTextColor);
4096         }
4097 
4098         if (attributes.mTextColorHint != null) {
4099             setHintTextColor(attributes.mTextColorHint);
4100         }
4101 
4102         if (attributes.mTextColorLink != null) {
4103             setLinkTextColor(attributes.mTextColorLink);
4104         }
4105 
4106         if (attributes.mTextColorHighlight != 0) {
4107             setHighlightColor(attributes.mTextColorHighlight);
4108         }
4109 
4110         if (attributes.mTextSize != -1) {
4111             mTextSizeUnit = attributes.mTextSizeUnit;
4112             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4113         }
4114 
4115         if (attributes.mTextLocales != null) {
4116             setTextLocales(attributes.mTextLocales);
4117         }
4118 
4119         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4120             attributes.mFontFamily = null;
4121         }
4122         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4123                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4124 
4125         if (attributes.mShadowColor != 0) {
4126             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4127                     attributes.mShadowColor);
4128         }
4129 
4130         if (attributes.mAllCaps) {
4131             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4132         }
4133 
4134         if (attributes.mHasElegant) {
4135             setElegantTextHeight(attributes.mElegant);
4136         }
4137 
4138         if (attributes.mHasFallbackLineSpacing) {
4139             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4140         }
4141 
4142         if (attributes.mHasLetterSpacing) {
4143             setLetterSpacing(attributes.mLetterSpacing);
4144         }
4145 
4146         if (attributes.mFontFeatureSettings != null) {
4147             setFontFeatureSettings(attributes.mFontFeatureSettings);
4148         }
4149 
4150         if (attributes.mFontVariationSettings != null) {
4151             setFontVariationSettings(attributes.mFontVariationSettings);
4152         }
4153     }
4154 
4155     /**
4156      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4157      * the first member of {@link #getTextLocales()}.
4158      * @return the default primary {@link Locale} of the text in this TextView.
4159      */
4160     @NonNull
getTextLocale()4161     public Locale getTextLocale() {
4162         return mTextPaint.getTextLocale();
4163     }
4164 
4165     /**
4166      * Get the default {@link LocaleList} of the text in this TextView.
4167      * @return the default {@link LocaleList} of the text in this TextView.
4168      */
4169     @NonNull @Size(min = 1)
getTextLocales()4170     public LocaleList getTextLocales() {
4171         return mTextPaint.getTextLocales();
4172     }
4173 
changeListenerLocaleTo(@ullable Locale locale)4174     private void changeListenerLocaleTo(@Nullable Locale locale) {
4175         if (mListenerChanged) {
4176             // If a listener has been explicitly set, don't change it. We may break something.
4177             return;
4178         }
4179         // The following null check is not absolutely necessary since all calling points of
4180         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4181         // here in case others would want to call this method in the future.
4182         if (mEditor != null) {
4183             KeyListener listener = mEditor.mKeyListener;
4184             if (listener instanceof DigitsKeyListener) {
4185                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4186             } else if (listener instanceof DateKeyListener) {
4187                 listener = DateKeyListener.getInstance(locale);
4188             } else if (listener instanceof TimeKeyListener) {
4189                 listener = TimeKeyListener.getInstance(locale);
4190             } else if (listener instanceof DateTimeKeyListener) {
4191                 listener = DateTimeKeyListener.getInstance(locale);
4192             } else {
4193                 return;
4194             }
4195             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4196             setKeyListenerOnly(listener);
4197             setInputTypeFromEditor();
4198             if (wasPasswordType) {
4199                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4200                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4201                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4202                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4203                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4204                 }
4205             }
4206         }
4207     }
4208 
4209     /**
4210      * Set the default {@link Locale} of the text in this TextView to a one-member
4211      * {@link LocaleList} containing just the given Locale.
4212      *
4213      * @param locale the {@link Locale} for drawing text, must not be null.
4214      *
4215      * @see #setTextLocales
4216      */
setTextLocale(@onNull Locale locale)4217     public void setTextLocale(@NonNull Locale locale) {
4218         mLocalesChanged = true;
4219         mTextPaint.setTextLocale(locale);
4220         if (mLayout != null) {
4221             nullLayouts();
4222             requestLayout();
4223             invalidate();
4224         }
4225     }
4226 
4227     /**
4228      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4229      *
4230      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4231      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4232      * other aspects of text display, including line breaking.
4233      *
4234      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4235      *
4236      * @see Paint#setTextLocales
4237      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)4238     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4239         mLocalesChanged = true;
4240         mTextPaint.setTextLocales(locales);
4241         if (mLayout != null) {
4242             nullLayouts();
4243             requestLayout();
4244             invalidate();
4245         }
4246     }
4247 
4248     @Override
onConfigurationChanged(Configuration newConfig)4249     protected void onConfigurationChanged(Configuration newConfig) {
4250         super.onConfigurationChanged(newConfig);
4251         if (!mLocalesChanged) {
4252             mTextPaint.setTextLocales(LocaleList.getDefault());
4253             if (mLayout != null) {
4254                 nullLayouts();
4255                 requestLayout();
4256                 invalidate();
4257             }
4258         }
4259     }
4260 
4261     /**
4262      * @return the size (in pixels) of the default text size in this TextView.
4263      */
4264     @InspectableProperty
4265     @ViewDebug.ExportedProperty(category = "text")
getTextSize()4266     public float getTextSize() {
4267         return mTextPaint.getTextSize();
4268     }
4269 
4270     /**
4271      * @return the size (in scaled pixels) of the default text size in this TextView.
4272      * @hide
4273      */
4274     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()4275     public float getScaledTextSize() {
4276         return mTextPaint.getTextSize() / mTextPaint.density;
4277     }
4278 
4279     /** @hide */
4280     @ViewDebug.ExportedProperty(category = "text", mapping = {
4281             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4282             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4283             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4284             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4285     })
getTypefaceStyle()4286     public int getTypefaceStyle() {
4287         Typeface typeface = mTextPaint.getTypeface();
4288         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4289     }
4290 
4291     /**
4292      * Set the default text size to the given value, interpreted as "scaled
4293      * pixel" units.  This size is adjusted based on the current density and
4294      * user font size preference.
4295      *
4296      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4297      *
4298      * @param size The scaled pixel size.
4299      *
4300      * @attr ref android.R.styleable#TextView_textSize
4301      */
4302     @android.view.RemotableViewMethod
setTextSize(float size)4303     public void setTextSize(float size) {
4304         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4305     }
4306 
4307     /**
4308      * Set the default text size to a given unit and value. See {@link
4309      * TypedValue} for the possible dimension units.
4310      *
4311      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4312      *
4313      * @param unit The desired dimension unit.
4314      * @param size The desired size in the given units.
4315      *
4316      * @attr ref android.R.styleable#TextView_textSize
4317      */
setTextSize(int unit, float size)4318     public void setTextSize(int unit, float size) {
4319         if (!isAutoSizeEnabled()) {
4320             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4321         }
4322     }
4323 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4324     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4325         Context c = getContext();
4326         Resources r;
4327 
4328         if (c == null) {
4329             r = Resources.getSystem();
4330         } else {
4331             r = c.getResources();
4332         }
4333 
4334         mTextSizeUnit = unit;
4335         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
4336                 shouldRequestLayout);
4337     }
4338 
4339     @UnsupportedAppUsage
setRawTextSize(float size, boolean shouldRequestLayout)4340     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4341         if (size != mTextPaint.getTextSize()) {
4342             mTextPaint.setTextSize(size);
4343 
4344             if (shouldRequestLayout && mLayout != null) {
4345                 // Do not auto-size right after setting the text size.
4346                 mNeedsAutoSizeText = false;
4347                 nullLayouts();
4348                 requestLayout();
4349                 invalidate();
4350             }
4351         }
4352     }
4353 
4354     /**
4355      * Gets the text size unit defined by the developer. It may be specified in resources or be
4356      * passed as the unit argument of {@link #setTextSize(int, float)} at runtime.
4357      *
4358      * @return the dimension type of the text size unit originally defined.
4359      * @see TypedValue#TYPE_DIMENSION
4360      */
getTextSizeUnit()4361     public int getTextSizeUnit() {
4362         return mTextSizeUnit;
4363     }
4364 
4365     /**
4366      * Gets the extent by which text should be stretched horizontally.
4367      * This will usually be 1.0.
4368      * @return The horizontal scale factor.
4369      */
4370     @InspectableProperty
getTextScaleX()4371     public float getTextScaleX() {
4372         return mTextPaint.getTextScaleX();
4373     }
4374 
4375     /**
4376      * Sets the horizontal scale factor for text. The default value
4377      * is 1.0. Values greater than 1.0 stretch the text wider.
4378      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4379      * @param size The horizontal scale factor.
4380      * @attr ref android.R.styleable#TextView_textScaleX
4381      */
4382     @android.view.RemotableViewMethod
setTextScaleX(float size)4383     public void setTextScaleX(float size) {
4384         if (size != mTextPaint.getTextScaleX()) {
4385             mUserSetTextScaleX = true;
4386             mTextPaint.setTextScaleX(size);
4387 
4388             if (mLayout != null) {
4389                 nullLayouts();
4390                 requestLayout();
4391                 invalidate();
4392             }
4393         }
4394     }
4395 
4396     /**
4397      * Sets the typeface and style in which the text should be displayed.
4398      * Note that not all Typeface families actually have bold and italic
4399      * variants, so you may need to use
4400      * {@link #setTypeface(Typeface, int)} to get the appearance
4401      * that you actually want.
4402      *
4403      * @see #getTypeface()
4404      *
4405      * @attr ref android.R.styleable#TextView_fontFamily
4406      * @attr ref android.R.styleable#TextView_typeface
4407      * @attr ref android.R.styleable#TextView_textStyle
4408      */
setTypeface(@ullable Typeface tf)4409     public void setTypeface(@Nullable Typeface tf) {
4410         if (mTextPaint.getTypeface() != tf) {
4411             mTextPaint.setTypeface(tf);
4412 
4413             if (mLayout != null) {
4414                 nullLayouts();
4415                 requestLayout();
4416                 invalidate();
4417             }
4418         }
4419     }
4420 
4421     /**
4422      * Gets the current {@link Typeface} that is used to style the text.
4423      * @return The current Typeface.
4424      *
4425      * @see #setTypeface(Typeface)
4426      *
4427      * @attr ref android.R.styleable#TextView_fontFamily
4428      * @attr ref android.R.styleable#TextView_typeface
4429      * @attr ref android.R.styleable#TextView_textStyle
4430      */
4431     @InspectableProperty
getTypeface()4432     public Typeface getTypeface() {
4433         return mTextPaint.getTypeface();
4434     }
4435 
4436     /**
4437      * Set the TextView's elegant height metrics flag. This setting selects font
4438      * variants that have not been compacted to fit Latin-based vertical
4439      * metrics, and also increases top and bottom bounds to provide more space.
4440      *
4441      * @param elegant set the paint's elegant metrics flag.
4442      *
4443      * @see #isElegantTextHeight()
4444      * @see Paint#isElegantTextHeight()
4445      *
4446      * @attr ref android.R.styleable#TextView_elegantTextHeight
4447      */
setElegantTextHeight(boolean elegant)4448     public void setElegantTextHeight(boolean elegant) {
4449         if (elegant != mTextPaint.isElegantTextHeight()) {
4450             mTextPaint.setElegantTextHeight(elegant);
4451             if (mLayout != null) {
4452                 nullLayouts();
4453                 requestLayout();
4454                 invalidate();
4455             }
4456         }
4457     }
4458 
4459     /**
4460      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4461      * displaying the text (which is needed to avoid text from consecutive lines running into
4462      * each other). If set, fallback fonts that end up getting used can increase the ascent
4463      * and descent of the lines that they are used on.
4464      * <p/>
4465      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4466      * is typically much taller or deeper than Latin text.
4467      *
4468      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4469      *
4470      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4471      *
4472      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4473      */
setFallbackLineSpacing(boolean enabled)4474     public void setFallbackLineSpacing(boolean enabled) {
4475         if (mUseFallbackLineSpacing != enabled) {
4476             mUseFallbackLineSpacing = enabled;
4477             if (mLayout != null) {
4478                 nullLayouts();
4479                 requestLayout();
4480                 invalidate();
4481             }
4482         }
4483     }
4484 
4485     /**
4486      * @return whether fallback line spacing is enabled, {@code true} by default
4487      *
4488      * @see #setFallbackLineSpacing(boolean)
4489      *
4490      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4491      */
4492     @InspectableProperty
isFallbackLineSpacing()4493     public boolean isFallbackLineSpacing() {
4494         return mUseFallbackLineSpacing;
4495     }
4496 
4497     /**
4498      * Get the value of the TextView's elegant height metrics flag. This setting selects font
4499      * variants that have not been compacted to fit Latin-based vertical
4500      * metrics, and also increases top and bottom bounds to provide more space.
4501      * @return {@code true} if the elegant height metrics flag is set.
4502      *
4503      * @see #setElegantTextHeight(boolean)
4504      * @see Paint#setElegantTextHeight(boolean)
4505      */
4506     @InspectableProperty
isElegantTextHeight()4507     public boolean isElegantTextHeight() {
4508         return mTextPaint.isElegantTextHeight();
4509     }
4510 
4511     /**
4512      * Gets the text letter-space value, which determines the spacing between characters.
4513      * The value returned is in ems. Normally, this value is 0.0.
4514      * @return The text letter-space value in ems.
4515      *
4516      * @see #setLetterSpacing(float)
4517      * @see Paint#setLetterSpacing
4518      */
4519     @InspectableProperty
getLetterSpacing()4520     public float getLetterSpacing() {
4521         return mTextPaint.getLetterSpacing();
4522     }
4523 
4524     /**
4525      * Sets text letter-spacing in em units.  Typical values
4526      * for slight expansion will be around 0.05.  Negative values tighten text.
4527      *
4528      * @see #getLetterSpacing()
4529      * @see Paint#getLetterSpacing
4530      *
4531      * @param letterSpacing A text letter-space value in ems.
4532      * @attr ref android.R.styleable#TextView_letterSpacing
4533      */
4534     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)4535     public void setLetterSpacing(float letterSpacing) {
4536         if (letterSpacing != mTextPaint.getLetterSpacing()) {
4537             mTextPaint.setLetterSpacing(letterSpacing);
4538 
4539             if (mLayout != null) {
4540                 nullLayouts();
4541                 requestLayout();
4542                 invalidate();
4543             }
4544         }
4545     }
4546 
4547     /**
4548      * Returns the font feature settings. The format is the same as the CSS
4549      * font-feature-settings attribute:
4550      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4551      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4552      *
4553      * @return the currently set font feature settings.  Default is null.
4554      *
4555      * @see #setFontFeatureSettings(String)
4556      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
4557      */
4558     @InspectableProperty
4559     @Nullable
getFontFeatureSettings()4560     public String getFontFeatureSettings() {
4561         return mTextPaint.getFontFeatureSettings();
4562     }
4563 
4564     /**
4565      * Returns the font variation settings.
4566      *
4567      * @return the currently set font variation settings.  Returns null if no variation is
4568      * specified.
4569      *
4570      * @see #setFontVariationSettings(String)
4571      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
4572      */
4573     @Nullable
getFontVariationSettings()4574     public String getFontVariationSettings() {
4575         return mTextPaint.getFontVariationSettings();
4576     }
4577 
4578     /**
4579      * Sets the break strategy for breaking paragraphs into lines. The default value for
4580      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
4581      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
4582      * text "dancing" when being edited.
4583      * <p/>
4584      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4585      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4586      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4587      * improves the structure of text layout however has performance impact and requires more time
4588      * to do the text layout.
4589      *
4590      * @attr ref android.R.styleable#TextView_breakStrategy
4591      * @see #getBreakStrategy()
4592      * @see #setHyphenationFrequency(int)
4593      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4594     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
4595         mBreakStrategy = breakStrategy;
4596         if (mLayout != null) {
4597             nullLayouts();
4598             requestLayout();
4599             invalidate();
4600         }
4601     }
4602 
4603     /**
4604      * Gets the current strategy for breaking paragraphs into lines.
4605      * @return the current strategy for breaking paragraphs into lines.
4606      *
4607      * @attr ref android.R.styleable#TextView_breakStrategy
4608      * @see #setBreakStrategy(int)
4609      */
4610     @InspectableProperty(enumMapping = {
4611             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
4612             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
4613             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
4614     })
4615     @Layout.BreakStrategy
getBreakStrategy()4616     public int getBreakStrategy() {
4617         return mBreakStrategy;
4618     }
4619 
4620     /**
4621      * Sets the frequency of automatic hyphenation to use when determining word breaks.
4622      * The default value for both TextView and {@link EditText} is
4623      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
4624      * is set from the theme.
4625      * <p/>
4626      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4627      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4628      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4629      * improves the structure of text layout however has performance impact and requires more time
4630      * to do the text layout.
4631      * <p/>
4632      * Note: Before Android Q, in the theme hyphenation frequency is set to
4633      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
4634      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
4635      *
4636      * @param hyphenationFrequency the hyphenation frequency to use, one of
4637      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
4638      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
4639      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
4640      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4641      * @see #getHyphenationFrequency()
4642      * @see #getBreakStrategy()
4643      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4644     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
4645         mHyphenationFrequency = hyphenationFrequency;
4646         if (mLayout != null) {
4647             nullLayouts();
4648             requestLayout();
4649             invalidate();
4650         }
4651     }
4652 
4653     /**
4654      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
4655      * @return the current frequency of automatic hyphenation to be used when determining word
4656      * breaks.
4657      *
4658      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4659      * @see #setHyphenationFrequency(int)
4660      */
4661     @InspectableProperty(enumMapping = {
4662             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
4663             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
4664             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
4665     })
4666     @Layout.HyphenationFrequency
getHyphenationFrequency()4667     public int getHyphenationFrequency() {
4668         return mHyphenationFrequency;
4669     }
4670 
4671     /**
4672      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
4673      *
4674      * @return a current {@link PrecomputedText.Params}
4675      * @see PrecomputedText
4676      */
getTextMetricsParams()4677     public @NonNull PrecomputedText.Params getTextMetricsParams() {
4678         return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
4679                 mBreakStrategy, mHyphenationFrequency);
4680     }
4681 
4682     /**
4683      * Apply the text layout parameter.
4684      *
4685      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
4686      * @see PrecomputedText
4687      */
setTextMetricsParams(@onNull PrecomputedText.Params params)4688     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
4689         mTextPaint.set(params.getTextPaint());
4690         mUserSetTextScaleX = true;
4691         mTextDir = params.getTextDirection();
4692         mBreakStrategy = params.getBreakStrategy();
4693         mHyphenationFrequency = params.getHyphenationFrequency();
4694         if (mLayout != null) {
4695             nullLayouts();
4696             requestLayout();
4697             invalidate();
4698         }
4699     }
4700 
4701     /**
4702      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
4703      * last line is too short for justification, the last line will be displayed with the
4704      * alignment set by {@link android.view.View#setTextAlignment}.
4705      *
4706      * @see #getJustificationMode()
4707      */
4708     @Layout.JustificationMode
setJustificationMode(@ayout.JustificationMode int justificationMode)4709     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
4710         mJustificationMode = justificationMode;
4711         if (mLayout != null) {
4712             nullLayouts();
4713             requestLayout();
4714             invalidate();
4715         }
4716     }
4717 
4718     /**
4719      * @return true if currently paragraph justification mode.
4720      *
4721      * @see #setJustificationMode(int)
4722      */
4723     @InspectableProperty(enumMapping = {
4724             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
4725             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
4726     })
getJustificationMode()4727     public @Layout.JustificationMode int getJustificationMode() {
4728         return mJustificationMode;
4729     }
4730 
4731     /**
4732      * Sets font feature settings. The format is the same as the CSS
4733      * font-feature-settings attribute:
4734      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4735      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4736      *
4737      * @param fontFeatureSettings font feature settings represented as CSS compatible string
4738      *
4739      * @see #getFontFeatureSettings()
4740      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
4741      *
4742      * @attr ref android.R.styleable#TextView_fontFeatureSettings
4743      */
4744     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)4745     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
4746         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
4747             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
4748 
4749             if (mLayout != null) {
4750                 nullLayouts();
4751                 requestLayout();
4752                 invalidate();
4753             }
4754         }
4755     }
4756 
4757 
4758     /**
4759      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
4760      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
4761      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
4762      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
4763      * are invalid. If a specified axis name is not defined in the font, the settings will be
4764      * ignored.
4765      *
4766      * <p>
4767      * Examples,
4768      * <ul>
4769      * <li>Set font width to 150.
4770      * <pre>
4771      * <code>
4772      *   TextView textView = (TextView) findViewById(R.id.textView);
4773      *   textView.setFontVariationSettings("'wdth' 150");
4774      * </code>
4775      * </pre>
4776      * </li>
4777      *
4778      * <li>Set the font slant to 20 degrees and ask for italic style.
4779      * <pre>
4780      * <code>
4781      *   TextView textView = (TextView) findViewById(R.id.textView);
4782      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
4783      * </code>
4784      * </pre>
4785      * </p>
4786      * </li>
4787      * </ul>
4788      *
4789      * @param fontVariationSettings font variation settings. You can pass null or empty string as
4790      *                              no variation settings.
4791      * @return true if the given settings is effective to at least one font file underlying this
4792      *         TextView. This function also returns true for empty settings string. Otherwise
4793      *         returns false.
4794      *
4795      * @throws IllegalArgumentException If given string is not a valid font variation settings
4796      *                                  format.
4797      *
4798      * @see #getFontVariationSettings()
4799      * @see FontVariationAxis
4800      *
4801      * @attr ref android.R.styleable#TextView_fontVariationSettings
4802      */
setFontVariationSettings(@ullable String fontVariationSettings)4803     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
4804         final String existingSettings = mTextPaint.getFontVariationSettings();
4805         if (fontVariationSettings == existingSettings
4806                 || (fontVariationSettings != null
4807                         && fontVariationSettings.equals(existingSettings))) {
4808             return true;
4809         }
4810         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
4811 
4812         if (effective && mLayout != null) {
4813             nullLayouts();
4814             requestLayout();
4815             invalidate();
4816         }
4817         return effective;
4818     }
4819 
4820     /**
4821      * Sets the text color for all the states (normal, selected,
4822      * focused) to be this color.
4823      *
4824      * @param color A color value in the form 0xAARRGGBB.
4825      * Do not pass a resource ID. To get a color value from a resource ID, call
4826      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
4827      *
4828      * @see #setTextColor(ColorStateList)
4829      * @see #getTextColors()
4830      *
4831      * @attr ref android.R.styleable#TextView_textColor
4832      */
4833     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)4834     public void setTextColor(@ColorInt int color) {
4835         mTextColor = ColorStateList.valueOf(color);
4836         updateTextColors();
4837     }
4838 
4839     /**
4840      * Sets the text color.
4841      *
4842      * @see #setTextColor(int)
4843      * @see #getTextColors()
4844      * @see #setHintTextColor(ColorStateList)
4845      * @see #setLinkTextColor(ColorStateList)
4846      *
4847      * @attr ref android.R.styleable#TextView_textColor
4848      */
4849     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)4850     public void setTextColor(ColorStateList colors) {
4851         if (colors == null) {
4852             throw new NullPointerException();
4853         }
4854 
4855         mTextColor = colors;
4856         updateTextColors();
4857     }
4858 
4859     /**
4860      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
4861      *
4862      * @see #setTextColor(ColorStateList)
4863      * @see #setTextColor(int)
4864      *
4865      * @attr ref android.R.styleable#TextView_textColor
4866      */
4867     @InspectableProperty(name = "textColor")
getTextColors()4868     public final ColorStateList getTextColors() {
4869         return mTextColor;
4870     }
4871 
4872     /**
4873      * Return the current color selected for normal text.
4874      *
4875      * @return Returns the current text color.
4876      */
4877     @ColorInt
getCurrentTextColor()4878     public final int getCurrentTextColor() {
4879         return mCurTextColor;
4880     }
4881 
4882     /**
4883      * Sets the color used to display the selection highlight.
4884      *
4885      * @attr ref android.R.styleable#TextView_textColorHighlight
4886      */
4887     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)4888     public void setHighlightColor(@ColorInt int color) {
4889         if (mHighlightColor != color) {
4890             mHighlightColor = color;
4891             invalidate();
4892         }
4893     }
4894 
4895     /**
4896      * @return the color used to display the selection highlight
4897      *
4898      * @see #setHighlightColor(int)
4899      *
4900      * @attr ref android.R.styleable#TextView_textColorHighlight
4901      */
4902     @InspectableProperty(name = "textColorHighlight")
4903     @ColorInt
getHighlightColor()4904     public int getHighlightColor() {
4905         return mHighlightColor;
4906     }
4907 
4908     /**
4909      * Sets whether the soft input method will be made visible when this
4910      * TextView gets focused. The default is true.
4911      */
4912     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)4913     public final void setShowSoftInputOnFocus(boolean show) {
4914         createEditorIfNeeded();
4915         mEditor.mShowSoftInputOnFocus = show;
4916     }
4917 
4918     /**
4919      * Returns whether the soft input method will be made visible when this
4920      * TextView gets focused. The default is true.
4921      */
getShowSoftInputOnFocus()4922     public final boolean getShowSoftInputOnFocus() {
4923         // When there is no Editor, return default true value
4924         return mEditor == null || mEditor.mShowSoftInputOnFocus;
4925     }
4926 
4927     /**
4928      * Gives the text a shadow of the specified blur radius and color, the specified
4929      * distance from its drawn position.
4930      * <p>
4931      * The text shadow produced does not interact with the properties on view
4932      * that are responsible for real time shadows,
4933      * {@link View#getElevation() elevation} and
4934      * {@link View#getTranslationZ() translationZ}.
4935      *
4936      * @see Paint#setShadowLayer(float, float, float, int)
4937      *
4938      * @attr ref android.R.styleable#TextView_shadowColor
4939      * @attr ref android.R.styleable#TextView_shadowDx
4940      * @attr ref android.R.styleable#TextView_shadowDy
4941      * @attr ref android.R.styleable#TextView_shadowRadius
4942      */
setShadowLayer(float radius, float dx, float dy, int color)4943     public void setShadowLayer(float radius, float dx, float dy, int color) {
4944         mTextPaint.setShadowLayer(radius, dx, dy, color);
4945 
4946         mShadowRadius = radius;
4947         mShadowDx = dx;
4948         mShadowDy = dy;
4949         mShadowColor = color;
4950 
4951         // Will change text clip region
4952         if (mEditor != null) {
4953             mEditor.invalidateTextDisplayList();
4954             mEditor.invalidateHandlesAndActionMode();
4955         }
4956         invalidate();
4957     }
4958 
4959     /**
4960      * Gets the radius of the shadow layer.
4961      *
4962      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
4963      *
4964      * @see #setShadowLayer(float, float, float, int)
4965      *
4966      * @attr ref android.R.styleable#TextView_shadowRadius
4967      */
4968     @InspectableProperty
getShadowRadius()4969     public float getShadowRadius() {
4970         return mShadowRadius;
4971     }
4972 
4973     /**
4974      * @return the horizontal offset of the shadow layer
4975      *
4976      * @see #setShadowLayer(float, float, float, int)
4977      *
4978      * @attr ref android.R.styleable#TextView_shadowDx
4979      */
4980     @InspectableProperty
getShadowDx()4981     public float getShadowDx() {
4982         return mShadowDx;
4983     }
4984 
4985     /**
4986      * Gets the vertical offset of the shadow layer.
4987      * @return The vertical offset of the shadow layer.
4988      *
4989      * @see #setShadowLayer(float, float, float, int)
4990      *
4991      * @attr ref android.R.styleable#TextView_shadowDy
4992      */
4993     @InspectableProperty
getShadowDy()4994     public float getShadowDy() {
4995         return mShadowDy;
4996     }
4997 
4998     /**
4999      * Gets the color of the shadow layer.
5000      * @return the color of the shadow layer
5001      *
5002      * @see #setShadowLayer(float, float, float, int)
5003      *
5004      * @attr ref android.R.styleable#TextView_shadowColor
5005      */
5006     @InspectableProperty
5007     @ColorInt
getShadowColor()5008     public int getShadowColor() {
5009         return mShadowColor;
5010     }
5011 
5012     /**
5013      * Gets the {@link TextPaint} used for the text.
5014      * Use this only to consult the Paint's properties and not to change them.
5015      * @return The base paint used for the text.
5016      */
getPaint()5017     public TextPaint getPaint() {
5018         return mTextPaint;
5019     }
5020 
5021     /**
5022      * Sets the autolink mask of the text.  See {@link
5023      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
5024      * possible values.
5025      *
5026      * <p class="note"><b>Note:</b>
5027      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
5028      * is deprecated and should be avoided; see its documentation.
5029      *
5030      * @attr ref android.R.styleable#TextView_autoLink
5031      */
5032     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)5033     public final void setAutoLinkMask(int mask) {
5034         mAutoLinkMask = mask;
5035     }
5036 
5037     /**
5038      * Sets whether the movement method will automatically be set to
5039      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5040      * set to nonzero and links are detected in {@link #setText}.
5041      * The default is true.
5042      *
5043      * @attr ref android.R.styleable#TextView_linksClickable
5044      */
5045     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)5046     public final void setLinksClickable(boolean whether) {
5047         mLinksClickable = whether;
5048     }
5049 
5050     /**
5051      * Returns whether the movement method will automatically be set to
5052      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5053      * set to nonzero and links are detected in {@link #setText}.
5054      * The default is true.
5055      *
5056      * @attr ref android.R.styleable#TextView_linksClickable
5057      */
5058     @InspectableProperty
getLinksClickable()5059     public final boolean getLinksClickable() {
5060         return mLinksClickable;
5061     }
5062 
5063     /**
5064      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5065      * (by {@link Linkify} or otherwise) if any.  You can call
5066      * {@link URLSpan#getURL} on them to find where they link to
5067      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5068      * to find the region of the text they are attached to.
5069      */
getUrls()5070     public URLSpan[] getUrls() {
5071         if (mText instanceof Spanned) {
5072             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5073         } else {
5074             return new URLSpan[0];
5075         }
5076     }
5077 
5078     /**
5079      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5080      * TextView.
5081      *
5082      * @see #setHintTextColor(ColorStateList)
5083      * @see #getHintTextColors()
5084      * @see #setTextColor(int)
5085      *
5086      * @attr ref android.R.styleable#TextView_textColorHint
5087      */
5088     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)5089     public final void setHintTextColor(@ColorInt int color) {
5090         mHintTextColor = ColorStateList.valueOf(color);
5091         updateTextColors();
5092     }
5093 
5094     /**
5095      * Sets the color of the hint text.
5096      *
5097      * @see #getHintTextColors()
5098      * @see #setHintTextColor(int)
5099      * @see #setTextColor(ColorStateList)
5100      * @see #setLinkTextColor(ColorStateList)
5101      *
5102      * @attr ref android.R.styleable#TextView_textColorHint
5103      */
setHintTextColor(ColorStateList colors)5104     public final void setHintTextColor(ColorStateList colors) {
5105         mHintTextColor = colors;
5106         updateTextColors();
5107     }
5108 
5109     /**
5110      * @return the color of the hint text, for the different states of this TextView.
5111      *
5112      * @see #setHintTextColor(ColorStateList)
5113      * @see #setHintTextColor(int)
5114      * @see #setTextColor(ColorStateList)
5115      * @see #setLinkTextColor(ColorStateList)
5116      *
5117      * @attr ref android.R.styleable#TextView_textColorHint
5118      */
5119     @InspectableProperty(name = "textColorHint")
getHintTextColors()5120     public final ColorStateList getHintTextColors() {
5121         return mHintTextColor;
5122     }
5123 
5124     /**
5125      * <p>Return the current color selected to paint the hint text.</p>
5126      *
5127      * @return Returns the current hint text color.
5128      */
5129     @ColorInt
getCurrentHintTextColor()5130     public final int getCurrentHintTextColor() {
5131         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5132     }
5133 
5134     /**
5135      * Sets the color of links in the text.
5136      *
5137      * @see #setLinkTextColor(ColorStateList)
5138      * @see #getLinkTextColors()
5139      *
5140      * @attr ref android.R.styleable#TextView_textColorLink
5141      */
5142     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)5143     public final void setLinkTextColor(@ColorInt int color) {
5144         mLinkTextColor = ColorStateList.valueOf(color);
5145         updateTextColors();
5146     }
5147 
5148     /**
5149      * Sets the color of links in the text.
5150      *
5151      * @see #setLinkTextColor(int)
5152      * @see #getLinkTextColors()
5153      * @see #setTextColor(ColorStateList)
5154      * @see #setHintTextColor(ColorStateList)
5155      *
5156      * @attr ref android.R.styleable#TextView_textColorLink
5157      */
setLinkTextColor(ColorStateList colors)5158     public final void setLinkTextColor(ColorStateList colors) {
5159         mLinkTextColor = colors;
5160         updateTextColors();
5161     }
5162 
5163     /**
5164      * @return the list of colors used to paint the links in the text, for the different states of
5165      * this TextView
5166      *
5167      * @see #setLinkTextColor(ColorStateList)
5168      * @see #setLinkTextColor(int)
5169      *
5170      * @attr ref android.R.styleable#TextView_textColorLink
5171      */
5172     @InspectableProperty(name = "textColorLink")
getLinkTextColors()5173     public final ColorStateList getLinkTextColors() {
5174         return mLinkTextColor;
5175     }
5176 
5177     /**
5178      * Sets the horizontal alignment of the text and the
5179      * vertical gravity that will be used when there is extra space
5180      * in the TextView beyond what is required for the text itself.
5181      *
5182      * @see android.view.Gravity
5183      * @attr ref android.R.styleable#TextView_gravity
5184      */
setGravity(int gravity)5185     public void setGravity(int gravity) {
5186         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5187             gravity |= Gravity.START;
5188         }
5189         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5190             gravity |= Gravity.TOP;
5191         }
5192 
5193         boolean newLayout = false;
5194 
5195         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5196                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5197             newLayout = true;
5198         }
5199 
5200         if (gravity != mGravity) {
5201             invalidate();
5202         }
5203 
5204         mGravity = gravity;
5205 
5206         if (mLayout != null && newLayout) {
5207             // XXX this is heavy-handed because no actual content changes.
5208             int want = mLayout.getWidth();
5209             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5210 
5211             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5212                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5213         }
5214     }
5215 
5216     /**
5217      * Returns the horizontal and vertical alignment of this TextView.
5218      *
5219      * @see android.view.Gravity
5220      * @attr ref android.R.styleable#TextView_gravity
5221      */
5222     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()5223     public int getGravity() {
5224         return mGravity;
5225     }
5226 
5227     /**
5228      * Gets the flags on the Paint being used to display the text.
5229      * @return The flags on the Paint being used to display the text.
5230      * @see Paint#getFlags
5231      */
getPaintFlags()5232     public int getPaintFlags() {
5233         return mTextPaint.getFlags();
5234     }
5235 
5236     /**
5237      * Sets flags on the Paint being used to display the text and
5238      * reflows the text if they are different from the old flags.
5239      * @see Paint#setFlags
5240      */
5241     @android.view.RemotableViewMethod
setPaintFlags(int flags)5242     public void setPaintFlags(int flags) {
5243         if (mTextPaint.getFlags() != flags) {
5244             mTextPaint.setFlags(flags);
5245 
5246             if (mLayout != null) {
5247                 nullLayouts();
5248                 requestLayout();
5249                 invalidate();
5250             }
5251         }
5252     }
5253 
5254     /**
5255      * Sets whether the text should be allowed to be wider than the
5256      * View is.  If false, it will be wrapped to the width of the View.
5257      *
5258      * @attr ref android.R.styleable#TextView_scrollHorizontally
5259      */
setHorizontallyScrolling(boolean whether)5260     public void setHorizontallyScrolling(boolean whether) {
5261         if (mHorizontallyScrolling != whether) {
5262             mHorizontallyScrolling = whether;
5263 
5264             if (mLayout != null) {
5265                 nullLayouts();
5266                 requestLayout();
5267                 invalidate();
5268             }
5269         }
5270     }
5271 
5272     /**
5273      * Returns whether the text is allowed to be wider than the View.
5274      * If false, the text will be wrapped to the width of the View.
5275      *
5276      * @attr ref android.R.styleable#TextView_scrollHorizontally
5277      * @see #setHorizontallyScrolling(boolean)
5278      */
5279     @InspectableProperty(name = "scrollHorizontally")
isHorizontallyScrollable()5280     public final boolean isHorizontallyScrollable() {
5281         return mHorizontallyScrolling;
5282     }
5283 
5284     /**
5285      * Returns whether the text is allowed to be wider than the View.
5286      * If false, the text will be wrapped to the width of the View.
5287      *
5288      * @attr ref android.R.styleable#TextView_scrollHorizontally
5289      * @hide
5290      */
5291     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
getHorizontallyScrolling()5292     public boolean getHorizontallyScrolling() {
5293         return mHorizontallyScrolling;
5294     }
5295 
5296     /**
5297      * Sets the height of the TextView to be at least {@code minLines} tall.
5298      * <p>
5299      * This value is used for height calculation if LayoutParams does not force TextView to have an
5300      * exact height. Setting this value overrides other previous minimum height configurations such
5301      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
5302      * this value to 1.
5303      *
5304      * @param minLines the minimum height of TextView in terms of number of lines
5305      *
5306      * @see #getMinLines()
5307      * @see #setLines(int)
5308      *
5309      * @attr ref android.R.styleable#TextView_minLines
5310      */
5311     @android.view.RemotableViewMethod
setMinLines(int minLines)5312     public void setMinLines(int minLines) {
5313         mMinimum = minLines;
5314         mMinMode = LINES;
5315 
5316         requestLayout();
5317         invalidate();
5318     }
5319 
5320     /**
5321      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
5322      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
5323      *
5324      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
5325      *         height is not defined in lines
5326      *
5327      * @see #setMinLines(int)
5328      * @see #setLines(int)
5329      *
5330      * @attr ref android.R.styleable#TextView_minLines
5331      */
5332     @InspectableProperty
getMinLines()5333     public int getMinLines() {
5334         return mMinMode == LINES ? mMinimum : -1;
5335     }
5336 
5337     /**
5338      * Sets the height of the TextView to be at least {@code minPixels} tall.
5339      * <p>
5340      * This value is used for height calculation if LayoutParams does not force TextView to have an
5341      * exact height. Setting this value overrides previous minimum height configurations such as
5342      * {@link #setMinLines(int)} or {@link #setLines(int)}.
5343      * <p>
5344      * The value given here is different than {@link #setMinimumHeight(int)}. Between
5345      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
5346      * used to decide the final height.
5347      *
5348      * @param minPixels the minimum height of TextView in terms of pixels
5349      *
5350      * @see #getMinHeight()
5351      * @see #setHeight(int)
5352      *
5353      * @attr ref android.R.styleable#TextView_minHeight
5354      */
5355     @android.view.RemotableViewMethod
setMinHeight(int minPixels)5356     public void setMinHeight(int minPixels) {
5357         mMinimum = minPixels;
5358         mMinMode = PIXELS;
5359 
5360         requestLayout();
5361         invalidate();
5362     }
5363 
5364     /**
5365      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
5366      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
5367      *
5368      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
5369      *         defined in pixels
5370      *
5371      * @see #setMinHeight(int)
5372      * @see #setHeight(int)
5373      *
5374      * @attr ref android.R.styleable#TextView_minHeight
5375      */
getMinHeight()5376     public int getMinHeight() {
5377         return mMinMode == PIXELS ? mMinimum : -1;
5378     }
5379 
5380     /**
5381      * Sets the height of the TextView to be at most {@code maxLines} tall.
5382      * <p>
5383      * This value is used for height calculation if LayoutParams does not force TextView to have an
5384      * exact height. Setting this value overrides previous maximum height configurations such as
5385      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
5386      *
5387      * @param maxLines the maximum height of TextView in terms of number of lines
5388      *
5389      * @see #getMaxLines()
5390      * @see #setLines(int)
5391      *
5392      * @attr ref android.R.styleable#TextView_maxLines
5393      */
5394     @android.view.RemotableViewMethod
setMaxLines(int maxLines)5395     public void setMaxLines(int maxLines) {
5396         mMaximum = maxLines;
5397         mMaxMode = LINES;
5398 
5399         requestLayout();
5400         invalidate();
5401     }
5402 
5403     /**
5404      * Returns the maximum height of TextView in terms of number of lines or -1 if the
5405      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
5406      *
5407      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
5408      *         is not defined in lines.
5409      *
5410      * @see #setMaxLines(int)
5411      * @see #setLines(int)
5412      *
5413      * @attr ref android.R.styleable#TextView_maxLines
5414      */
5415     @InspectableProperty
getMaxLines()5416     public int getMaxLines() {
5417         return mMaxMode == LINES ? mMaximum : -1;
5418     }
5419 
5420     /**
5421      * Sets the height of the TextView to be at most {@code maxPixels} tall.
5422      * <p>
5423      * This value is used for height calculation if LayoutParams does not force TextView to have an
5424      * exact height. Setting this value overrides previous maximum height configurations such as
5425      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
5426      *
5427      * @param maxPixels the maximum height of TextView in terms of pixels
5428      *
5429      * @see #getMaxHeight()
5430      * @see #setHeight(int)
5431      *
5432      * @attr ref android.R.styleable#TextView_maxHeight
5433      */
5434     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)5435     public void setMaxHeight(int maxPixels) {
5436         mMaximum = maxPixels;
5437         mMaxMode = PIXELS;
5438 
5439         requestLayout();
5440         invalidate();
5441     }
5442 
5443     /**
5444      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
5445      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
5446      *
5447      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
5448      *         is not defined in pixels
5449      *
5450      * @see #setMaxHeight(int)
5451      * @see #setHeight(int)
5452      *
5453      * @attr ref android.R.styleable#TextView_maxHeight
5454      */
5455     @InspectableProperty
getMaxHeight()5456     public int getMaxHeight() {
5457         return mMaxMode == PIXELS ? mMaximum : -1;
5458     }
5459 
5460     /**
5461      * Sets the height of the TextView to be exactly {@code lines} tall.
5462      * <p>
5463      * This value is used for height calculation if LayoutParams does not force TextView to have an
5464      * exact height. Setting this value overrides previous minimum/maximum height configurations
5465      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
5466      * set this value to 1.
5467      *
5468      * @param lines the exact height of the TextView in terms of lines
5469      *
5470      * @see #setHeight(int)
5471      *
5472      * @attr ref android.R.styleable#TextView_lines
5473      */
5474     @android.view.RemotableViewMethod
setLines(int lines)5475     public void setLines(int lines) {
5476         mMaximum = mMinimum = lines;
5477         mMaxMode = mMinMode = LINES;
5478 
5479         requestLayout();
5480         invalidate();
5481     }
5482 
5483     /**
5484      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
5485      * <p>
5486      * This value is used for height calculation if LayoutParams does not force TextView to have an
5487      * exact height. Setting this value overrides previous minimum/maximum height configurations
5488      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
5489      *
5490      * @param pixels the exact height of the TextView in terms of pixels
5491      *
5492      * @see #setLines(int)
5493      *
5494      * @attr ref android.R.styleable#TextView_height
5495      */
5496     @android.view.RemotableViewMethod
setHeight(int pixels)5497     public void setHeight(int pixels) {
5498         mMaximum = mMinimum = pixels;
5499         mMaxMode = mMinMode = PIXELS;
5500 
5501         requestLayout();
5502         invalidate();
5503     }
5504 
5505     /**
5506      * Sets the width of the TextView to be at least {@code minEms} wide.
5507      * <p>
5508      * This value is used for width calculation if LayoutParams does not force TextView to have an
5509      * exact width. Setting this value overrides previous minimum width configurations such as
5510      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5511      *
5512      * @param minEms the minimum width of TextView in terms of ems
5513      *
5514      * @see #getMinEms()
5515      * @see #setEms(int)
5516      *
5517      * @attr ref android.R.styleable#TextView_minEms
5518      */
5519     @android.view.RemotableViewMethod
setMinEms(int minEms)5520     public void setMinEms(int minEms) {
5521         mMinWidth = minEms;
5522         mMinWidthMode = EMS;
5523 
5524         requestLayout();
5525         invalidate();
5526     }
5527 
5528     /**
5529      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
5530      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5531      *
5532      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
5533      *         defined in ems
5534      *
5535      * @see #setMinEms(int)
5536      * @see #setEms(int)
5537      *
5538      * @attr ref android.R.styleable#TextView_minEms
5539      */
5540     @InspectableProperty
getMinEms()5541     public int getMinEms() {
5542         return mMinWidthMode == EMS ? mMinWidth : -1;
5543     }
5544 
5545     /**
5546      * Sets the width of the TextView to be at least {@code minPixels} wide.
5547      * <p>
5548      * This value is used for width calculation if LayoutParams does not force TextView to have an
5549      * exact width. Setting this value overrides previous minimum width configurations such as
5550      * {@link #setMinEms(int)} or {@link #setEms(int)}.
5551      * <p>
5552      * The value given here is different than {@link #setMinimumWidth(int)}. Between
5553      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
5554      * to decide the final width.
5555      *
5556      * @param minPixels the minimum width of TextView in terms of pixels
5557      *
5558      * @see #getMinWidth()
5559      * @see #setWidth(int)
5560      *
5561      * @attr ref android.R.styleable#TextView_minWidth
5562      */
5563     @android.view.RemotableViewMethod
setMinWidth(int minPixels)5564     public void setMinWidth(int minPixels) {
5565         mMinWidth = minPixels;
5566         mMinWidthMode = PIXELS;
5567 
5568         requestLayout();
5569         invalidate();
5570     }
5571 
5572     /**
5573      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
5574      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
5575      *
5576      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
5577      *         defined in pixels
5578      *
5579      * @see #setMinWidth(int)
5580      * @see #setWidth(int)
5581      *
5582      * @attr ref android.R.styleable#TextView_minWidth
5583      */
5584     @InspectableProperty
getMinWidth()5585     public int getMinWidth() {
5586         return mMinWidthMode == PIXELS ? mMinWidth : -1;
5587     }
5588 
5589     /**
5590      * Sets the width of the TextView to be at most {@code maxEms} wide.
5591      * <p>
5592      * This value is used for width calculation if LayoutParams does not force TextView to have an
5593      * exact width. Setting this value overrides previous maximum width configurations such as
5594      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5595      *
5596      * @param maxEms the maximum width of TextView in terms of ems
5597      *
5598      * @see #getMaxEms()
5599      * @see #setEms(int)
5600      *
5601      * @attr ref android.R.styleable#TextView_maxEms
5602      */
5603     @android.view.RemotableViewMethod
setMaxEms(int maxEms)5604     public void setMaxEms(int maxEms) {
5605         mMaxWidth = maxEms;
5606         mMaxWidthMode = EMS;
5607 
5608         requestLayout();
5609         invalidate();
5610     }
5611 
5612     /**
5613      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
5614      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5615      *
5616      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
5617      *         defined in ems
5618      *
5619      * @see #setMaxEms(int)
5620      * @see #setEms(int)
5621      *
5622      * @attr ref android.R.styleable#TextView_maxEms
5623      */
5624     @InspectableProperty
getMaxEms()5625     public int getMaxEms() {
5626         return mMaxWidthMode == EMS ? mMaxWidth : -1;
5627     }
5628 
5629     /**
5630      * Sets the width of the TextView to be at most {@code maxPixels} wide.
5631      * <p>
5632      * This value is used for width calculation if LayoutParams does not force TextView to have an
5633      * exact width. Setting this value overrides previous maximum width configurations such as
5634      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
5635      *
5636      * @param maxPixels the maximum width of TextView in terms of pixels
5637      *
5638      * @see #getMaxWidth()
5639      * @see #setWidth(int)
5640      *
5641      * @attr ref android.R.styleable#TextView_maxWidth
5642      */
5643     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)5644     public void setMaxWidth(int maxPixels) {
5645         mMaxWidth = maxPixels;
5646         mMaxWidthMode = PIXELS;
5647 
5648         requestLayout();
5649         invalidate();
5650     }
5651 
5652     /**
5653      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
5654      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
5655      *
5656      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
5657      *         defined in pixels
5658      *
5659      * @see #setMaxWidth(int)
5660      * @see #setWidth(int)
5661      *
5662      * @attr ref android.R.styleable#TextView_maxWidth
5663      */
5664     @InspectableProperty
getMaxWidth()5665     public int getMaxWidth() {
5666         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
5667     }
5668 
5669     /**
5670      * Sets the width of the TextView to be exactly {@code ems} wide.
5671      *
5672      * This value is used for width calculation if LayoutParams does not force TextView to have an
5673      * exact width. Setting this value overrides previous minimum/maximum configurations such as
5674      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
5675      *
5676      * @param ems the exact width of the TextView in terms of ems
5677      *
5678      * @see #setWidth(int)
5679      *
5680      * @attr ref android.R.styleable#TextView_ems
5681      */
5682     @android.view.RemotableViewMethod
setEms(int ems)5683     public void setEms(int ems) {
5684         mMaxWidth = mMinWidth = ems;
5685         mMaxWidthMode = mMinWidthMode = EMS;
5686 
5687         requestLayout();
5688         invalidate();
5689     }
5690 
5691     /**
5692      * Sets the width of the TextView to be exactly {@code pixels} wide.
5693      * <p>
5694      * This value is used for width calculation if LayoutParams does not force TextView to have an
5695      * exact width. Setting this value overrides previous minimum/maximum width configurations
5696      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
5697      *
5698      * @param pixels the exact width of the TextView in terms of pixels
5699      *
5700      * @see #setEms(int)
5701      *
5702      * @attr ref android.R.styleable#TextView_width
5703      */
5704     @android.view.RemotableViewMethod
setWidth(int pixels)5705     public void setWidth(int pixels) {
5706         mMaxWidth = mMinWidth = pixels;
5707         mMaxWidthMode = mMinWidthMode = PIXELS;
5708 
5709         requestLayout();
5710         invalidate();
5711     }
5712 
5713     /**
5714      * Sets line spacing for this TextView.  Each line other than the last line will have its height
5715      * multiplied by {@code mult} and have {@code add} added to it.
5716      *
5717      * @param add The value in pixels that should be added to each line other than the last line.
5718      *            This will be applied after the multiplier
5719      * @param mult The value by which each line height other than the last line will be multiplied
5720      *             by
5721      *
5722      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5723      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5724      */
setLineSpacing(float add, float mult)5725     public void setLineSpacing(float add, float mult) {
5726         if (mSpacingAdd != add || mSpacingMult != mult) {
5727             mSpacingAdd = add;
5728             mSpacingMult = mult;
5729 
5730             if (mLayout != null) {
5731                 nullLayouts();
5732                 requestLayout();
5733                 invalidate();
5734             }
5735         }
5736     }
5737 
5738     /**
5739      * Gets the line spacing multiplier
5740      *
5741      * @return the value by which each line's height is multiplied to get its actual height.
5742      *
5743      * @see #setLineSpacing(float, float)
5744      * @see #getLineSpacingExtra()
5745      *
5746      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5747      */
5748     @InspectableProperty
getLineSpacingMultiplier()5749     public float getLineSpacingMultiplier() {
5750         return mSpacingMult;
5751     }
5752 
5753     /**
5754      * Gets the line spacing extra space
5755      *
5756      * @return the extra space that is added to the height of each lines of this TextView.
5757      *
5758      * @see #setLineSpacing(float, float)
5759      * @see #getLineSpacingMultiplier()
5760      *
5761      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5762      */
5763     @InspectableProperty
getLineSpacingExtra()5764     public float getLineSpacingExtra() {
5765         return mSpacingAdd;
5766     }
5767 
5768     /**
5769      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
5770      * between subsequent baselines in the TextView.
5771      *
5772      * @param lineHeight the line height in pixels
5773      *
5774      * @see #setLineSpacing(float, float)
5775      * @see #getLineSpacingExtra()
5776      *
5777      * @attr ref android.R.styleable#TextView_lineHeight
5778      */
setLineHeight(@x @ntRangefrom = 0) int lineHeight)5779     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
5780         Preconditions.checkArgumentNonnegative(lineHeight);
5781 
5782         final int fontHeight = getPaint().getFontMetricsInt(null);
5783         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
5784         if (lineHeight != fontHeight) {
5785             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
5786             setLineSpacing(lineHeight - fontHeight, 1f);
5787         }
5788     }
5789 
5790     /**
5791      * Convenience method to append the specified text to the TextView's
5792      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5793      * if it was not already editable.
5794      *
5795      * @param text text to be appended to the already displayed text
5796      */
append(CharSequence text)5797     public final void append(CharSequence text) {
5798         append(text, 0, text.length());
5799     }
5800 
5801     /**
5802      * Convenience method to append the specified text slice to the TextView's
5803      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5804      * if it was not already editable.
5805      *
5806      * @param text text to be appended to the already displayed text
5807      * @param start the index of the first character in the {@code text}
5808      * @param end the index of the character following the last character in the {@code text}
5809      *
5810      * @see Appendable#append(CharSequence, int, int)
5811      */
append(CharSequence text, int start, int end)5812     public void append(CharSequence text, int start, int end) {
5813         if (!(mText instanceof Editable)) {
5814             setText(mText, BufferType.EDITABLE);
5815         }
5816 
5817         ((Editable) mText).append(text, start, end);
5818 
5819         if (mAutoLinkMask != 0) {
5820             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
5821             // Do not change the movement method for text that support text selection as it
5822             // would prevent an arbitrary cursor displacement.
5823             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
5824                 setMovementMethod(LinkMovementMethod.getInstance());
5825             }
5826         }
5827     }
5828 
updateTextColors()5829     private void updateTextColors() {
5830         boolean inval = false;
5831         final int[] drawableState = getDrawableState();
5832         int color = mTextColor.getColorForState(drawableState, 0);
5833         if (color != mCurTextColor) {
5834             mCurTextColor = color;
5835             inval = true;
5836         }
5837         if (mLinkTextColor != null) {
5838             color = mLinkTextColor.getColorForState(drawableState, 0);
5839             if (color != mTextPaint.linkColor) {
5840                 mTextPaint.linkColor = color;
5841                 inval = true;
5842             }
5843         }
5844         if (mHintTextColor != null) {
5845             color = mHintTextColor.getColorForState(drawableState, 0);
5846             if (color != mCurHintTextColor) {
5847                 mCurHintTextColor = color;
5848                 if (mText.length() == 0) {
5849                     inval = true;
5850                 }
5851             }
5852         }
5853         if (inval) {
5854             // Text needs to be redrawn with the new color
5855             if (mEditor != null) mEditor.invalidateTextDisplayList();
5856             invalidate();
5857         }
5858     }
5859 
5860     @Override
drawableStateChanged()5861     protected void drawableStateChanged() {
5862         super.drawableStateChanged();
5863 
5864         if (mTextColor != null && mTextColor.isStateful()
5865                 || (mHintTextColor != null && mHintTextColor.isStateful())
5866                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
5867             updateTextColors();
5868         }
5869 
5870         if (mDrawables != null) {
5871             final int[] state = getDrawableState();
5872             for (Drawable dr : mDrawables.mShowing) {
5873                 if (dr != null && dr.isStateful() && dr.setState(state)) {
5874                     invalidateDrawable(dr);
5875                 }
5876             }
5877         }
5878     }
5879 
5880     @Override
drawableHotspotChanged(float x, float y)5881     public void drawableHotspotChanged(float x, float y) {
5882         super.drawableHotspotChanged(x, y);
5883 
5884         if (mDrawables != null) {
5885             for (Drawable dr : mDrawables.mShowing) {
5886                 if (dr != null) {
5887                     dr.setHotspot(x, y);
5888                 }
5889             }
5890         }
5891     }
5892 
5893     @Override
onSaveInstanceState()5894     public Parcelable onSaveInstanceState() {
5895         Parcelable superState = super.onSaveInstanceState();
5896 
5897         // Save state if we are forced to
5898         final boolean freezesText = getFreezesText();
5899         boolean hasSelection = false;
5900         int start = -1;
5901         int end = -1;
5902 
5903         if (mText != null) {
5904             start = getSelectionStart();
5905             end = getSelectionEnd();
5906             if (start >= 0 || end >= 0) {
5907                 // Or save state if there is a selection
5908                 hasSelection = true;
5909             }
5910         }
5911 
5912         if (freezesText || hasSelection) {
5913             SavedState ss = new SavedState(superState);
5914 
5915             if (freezesText) {
5916                 if (mText instanceof Spanned) {
5917                     final Spannable sp = new SpannableStringBuilder(mText);
5918 
5919                     if (mEditor != null) {
5920                         removeMisspelledSpans(sp);
5921                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
5922                     }
5923 
5924                     ss.text = sp;
5925                 } else {
5926                     ss.text = mText.toString();
5927                 }
5928             }
5929 
5930             if (hasSelection) {
5931                 // XXX Should also save the current scroll position!
5932                 ss.selStart = start;
5933                 ss.selEnd = end;
5934             }
5935 
5936             if (isFocused() && start >= 0 && end >= 0) {
5937                 ss.frozenWithFocus = true;
5938             }
5939 
5940             ss.error = getError();
5941 
5942             if (mEditor != null) {
5943                 ss.editorState = mEditor.saveInstanceState();
5944             }
5945             return ss;
5946         }
5947 
5948         return superState;
5949     }
5950 
removeMisspelledSpans(Spannable spannable)5951     void removeMisspelledSpans(Spannable spannable) {
5952         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
5953                 SuggestionSpan.class);
5954         for (int i = 0; i < suggestionSpans.length; i++) {
5955             int flags = suggestionSpans[i].getFlags();
5956             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
5957                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
5958                 spannable.removeSpan(suggestionSpans[i]);
5959             }
5960         }
5961     }
5962 
5963     @Override
onRestoreInstanceState(Parcelable state)5964     public void onRestoreInstanceState(Parcelable state) {
5965         if (!(state instanceof SavedState)) {
5966             super.onRestoreInstanceState(state);
5967             return;
5968         }
5969 
5970         SavedState ss = (SavedState) state;
5971         super.onRestoreInstanceState(ss.getSuperState());
5972 
5973         // XXX restore buffer type too, as well as lots of other stuff
5974         if (ss.text != null) {
5975             setText(ss.text);
5976         }
5977 
5978         if (ss.selStart >= 0 && ss.selEnd >= 0) {
5979             if (mSpannable != null) {
5980                 int len = mText.length();
5981 
5982                 if (ss.selStart > len || ss.selEnd > len) {
5983                     String restored = "";
5984 
5985                     if (ss.text != null) {
5986                         restored = "(restored) ";
5987                     }
5988 
5989                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
5990                             + " out of range for " + restored + "text " + mText);
5991                 } else {
5992                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
5993 
5994                     if (ss.frozenWithFocus) {
5995                         createEditorIfNeeded();
5996                         mEditor.mFrozenWithFocus = true;
5997                     }
5998                 }
5999             }
6000         }
6001 
6002         if (ss.error != null) {
6003             final CharSequence error = ss.error;
6004             // Display the error later, after the first layout pass
6005             post(new Runnable() {
6006                 public void run() {
6007                     if (mEditor == null || !mEditor.mErrorWasChanged) {
6008                         setError(error);
6009                     }
6010                 }
6011             });
6012         }
6013 
6014         if (ss.editorState != null) {
6015             createEditorIfNeeded();
6016             mEditor.restoreInstanceState(ss.editorState);
6017         }
6018     }
6019 
6020     /**
6021      * Control whether this text view saves its entire text contents when
6022      * freezing to an icicle, in addition to dynamic state such as cursor
6023      * position.  By default this is false, not saving the text.  Set to true
6024      * if the text in the text view is not being saved somewhere else in
6025      * persistent storage (such as in a content provider) so that if the
6026      * view is later thawed the user will not lose their data. For
6027      * {@link android.widget.EditText} it is always enabled, regardless of
6028      * the value of the attribute.
6029      *
6030      * @param freezesText Controls whether a frozen icicle should include the
6031      * entire text data: true to include it, false to not.
6032      *
6033      * @attr ref android.R.styleable#TextView_freezesText
6034      */
6035     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)6036     public void setFreezesText(boolean freezesText) {
6037         mFreezesText = freezesText;
6038     }
6039 
6040     /**
6041      * Return whether this text view is including its entire text contents
6042      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
6043      *
6044      * @return Returns true if text is included, false if it isn't.
6045      *
6046      * @see #setFreezesText
6047      */
6048     @InspectableProperty
getFreezesText()6049     public boolean getFreezesText() {
6050         return mFreezesText;
6051     }
6052 
6053     ///////////////////////////////////////////////////////////////////////////
6054 
6055     /**
6056      * Sets the Factory used to create new {@link Editable Editables}.
6057      *
6058      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
6059      *
6060      * @see android.text.Editable.Factory
6061      * @see android.widget.TextView.BufferType#EDITABLE
6062      */
setEditableFactory(Editable.Factory factory)6063     public final void setEditableFactory(Editable.Factory factory) {
6064         mEditableFactory = factory;
6065         setText(mText);
6066     }
6067 
6068     /**
6069      * Sets the Factory used to create new {@link Spannable Spannables}.
6070      *
6071      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
6072      *
6073      * @see android.text.Spannable.Factory
6074      * @see android.widget.TextView.BufferType#SPANNABLE
6075      */
setSpannableFactory(Spannable.Factory factory)6076     public final void setSpannableFactory(Spannable.Factory factory) {
6077         mSpannableFactory = factory;
6078         setText(mText);
6079     }
6080 
6081     /**
6082      * Sets the text to be displayed. TextView <em>does not</em> accept
6083      * HTML-like formatting, which you can do with text strings in XML resource files.
6084      * To style your strings, attach android.text.style.* objects to a
6085      * {@link android.text.SpannableString}, or see the
6086      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
6087      * Available Resource Types</a> documentation for an example of setting
6088      * formatted text in the XML resource file.
6089      * <p/>
6090      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6091      * intermediate {@link Spannable Spannables}. Likewise it will use
6092      * {@link android.text.Editable.Factory} to create final or intermediate
6093      * {@link Editable Editables}.
6094      *
6095      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
6096      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
6097      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
6098      *
6099      * @param text text to be displayed
6100      *
6101      * @attr ref android.R.styleable#TextView_text
6102      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
6103      *                                  parameters used to create the PrecomputedText mismatches
6104      *                                  with this TextView.
6105      */
6106     @android.view.RemotableViewMethod
setText(CharSequence text)6107     public final void setText(CharSequence text) {
6108         setText(text, mBufferType);
6109     }
6110 
6111     /**
6112      * Sets the text to be displayed but retains the cursor position. Same as
6113      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
6114      * new text.
6115      * <p/>
6116      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6117      * intermediate {@link Spannable Spannables}. Likewise it will use
6118      * {@link android.text.Editable.Factory} to create final or intermediate
6119      * {@link Editable Editables}.
6120      *
6121      * @param text text to be displayed
6122      *
6123      * @see #setText(CharSequence)
6124      */
6125     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)6126     public final void setTextKeepState(CharSequence text) {
6127         setTextKeepState(text, mBufferType);
6128     }
6129 
6130     /**
6131      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
6132      * <p/>
6133      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6134      * intermediate {@link Spannable Spannables}. Likewise it will use
6135      * {@link android.text.Editable.Factory} to create final or intermediate
6136      * {@link Editable Editables}.
6137      *
6138      * Subclasses overriding this method should ensure that the following post condition holds,
6139      * in order to guarantee the safety of the view's measurement and layout operations:
6140      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
6141      * will be different from {@code null}.
6142      *
6143      * @param text text to be displayed
6144      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6145      *              stored as a static text, styleable/spannable text, or editable text
6146      *
6147      * @see #setText(CharSequence)
6148      * @see android.widget.TextView.BufferType
6149      * @see #setSpannableFactory(Spannable.Factory)
6150      * @see #setEditableFactory(Editable.Factory)
6151      *
6152      * @attr ref android.R.styleable#TextView_text
6153      * @attr ref android.R.styleable#TextView_bufferType
6154      */
setText(CharSequence text, BufferType type)6155     public void setText(CharSequence text, BufferType type) {
6156         setText(text, type, true, 0);
6157 
6158         if (mCharWrapper != null) {
6159             mCharWrapper.mChars = null;
6160         }
6161     }
6162 
6163     @UnsupportedAppUsage
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6164     private void setText(CharSequence text, BufferType type,
6165                          boolean notifyBefore, int oldlen) {
6166         mTextSetFromXmlOrResourceId = false;
6167         if (text == null) {
6168             text = "";
6169         }
6170 
6171         // If suggestions are not enabled, remove the suggestion spans from the text
6172         if (!isSuggestionsEnabled()) {
6173             text = removeSuggestionSpans(text);
6174         }
6175 
6176         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
6177 
6178         if (text instanceof Spanned
6179                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
6180             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
6181                 setHorizontalFadingEdgeEnabled(true);
6182                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
6183             } else {
6184                 setHorizontalFadingEdgeEnabled(false);
6185                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
6186             }
6187             setEllipsize(TextUtils.TruncateAt.MARQUEE);
6188         }
6189 
6190         int n = mFilters.length;
6191         for (int i = 0; i < n; i++) {
6192             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
6193             if (out != null) {
6194                 text = out;
6195             }
6196         }
6197 
6198         if (notifyBefore) {
6199             if (mText != null) {
6200                 oldlen = mText.length();
6201                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
6202             } else {
6203                 sendBeforeTextChanged("", 0, 0, text.length());
6204             }
6205         }
6206 
6207         boolean needEditableForNotification = false;
6208 
6209         if (mListeners != null && mListeners.size() != 0) {
6210             needEditableForNotification = true;
6211         }
6212 
6213         PrecomputedText precomputed =
6214                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
6215         if (type == BufferType.EDITABLE || getKeyListener() != null
6216                 || needEditableForNotification) {
6217             createEditorIfNeeded();
6218             mEditor.forgetUndoRedo();
6219             Editable t = mEditableFactory.newEditable(text);
6220             text = t;
6221             setFilters(t, mFilters);
6222             InputMethodManager imm = getInputMethodManager();
6223             if (imm != null) imm.restartInput(this);
6224         } else if (precomputed != null) {
6225             if (mTextDir == null) {
6226                 mTextDir = getTextDirectionHeuristic();
6227             }
6228             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
6229                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
6230                             mHyphenationFrequency);
6231             switch (checkResult) {
6232                 case PrecomputedText.Params.UNUSABLE:
6233                     throw new IllegalArgumentException(
6234                         "PrecomputedText's Parameters don't match the parameters of this TextView."
6235                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
6236                         + "to override the settings of this TextView: "
6237                         + "PrecomputedText: " + precomputed.getParams()
6238                         + "TextView: " + getTextMetricsParams());
6239                 case PrecomputedText.Params.NEED_RECOMPUTE:
6240                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
6241                     break;
6242                 case PrecomputedText.Params.USABLE:
6243                     // pass through
6244             }
6245         } else if (type == BufferType.SPANNABLE || mMovement != null) {
6246             text = mSpannableFactory.newSpannable(text);
6247         } else if (!(text instanceof CharWrapper)) {
6248             text = TextUtils.stringOrSpannedString(text);
6249         }
6250 
6251         if (mAutoLinkMask != 0) {
6252             Spannable s2;
6253 
6254             if (type == BufferType.EDITABLE || text instanceof Spannable) {
6255                 s2 = (Spannable) text;
6256             } else {
6257                 s2 = mSpannableFactory.newSpannable(text);
6258             }
6259 
6260             if (Linkify.addLinks(s2, mAutoLinkMask)) {
6261                 text = s2;
6262                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
6263 
6264                 /*
6265                  * We must go ahead and set the text before changing the
6266                  * movement method, because setMovementMethod() may call
6267                  * setText() again to try to upgrade the buffer type.
6268                  */
6269                 setTextInternal(text);
6270 
6271                 // Do not change the movement method for text that support text selection as it
6272                 // would prevent an arbitrary cursor displacement.
6273                 if (mLinksClickable && !textCanBeSelected()) {
6274                     setMovementMethod(LinkMovementMethod.getInstance());
6275                 }
6276             }
6277         }
6278 
6279         mBufferType = type;
6280         setTextInternal(text);
6281 
6282         if (mTransformation == null) {
6283             mTransformed = text;
6284         } else {
6285             mTransformed = mTransformation.getTransformation(text, this);
6286         }
6287         if (mTransformed == null) {
6288             // Should not happen if the transformation method follows the non-null postcondition.
6289             mTransformed = "";
6290         }
6291 
6292         final int textLength = text.length();
6293 
6294         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
6295             Spannable sp = (Spannable) text;
6296 
6297             // Remove any ChangeWatchers that might have come from other TextViews.
6298             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
6299             final int count = watchers.length;
6300             for (int i = 0; i < count; i++) {
6301                 sp.removeSpan(watchers[i]);
6302             }
6303 
6304             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
6305 
6306             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
6307                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
6308 
6309             if (mEditor != null) mEditor.addSpanWatchers(sp);
6310 
6311             if (mTransformation != null) {
6312                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
6313             }
6314 
6315             if (mMovement != null) {
6316                 mMovement.initialize(this, (Spannable) text);
6317 
6318                 /*
6319                  * Initializing the movement method will have set the
6320                  * selection, so reset mSelectionMoved to keep that from
6321                  * interfering with the normal on-focus selection-setting.
6322                  */
6323                 if (mEditor != null) mEditor.mSelectionMoved = false;
6324             }
6325         }
6326 
6327         if (mLayout != null) {
6328             checkForRelayout();
6329         }
6330 
6331         sendOnTextChanged(text, 0, oldlen, textLength);
6332         onTextChanged(text, 0, oldlen, textLength);
6333 
6334         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
6335 
6336         if (needEditableForNotification) {
6337             sendAfterTextChanged((Editable) text);
6338         } else {
6339             notifyListeningManagersAfterTextChanged();
6340         }
6341 
6342         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
6343         if (mEditor != null) mEditor.prepareCursorControllers();
6344     }
6345 
6346     /**
6347      * Sets the TextView to display the specified slice of the specified
6348      * char array. You must promise that you will not change the contents
6349      * of the array except for right before another call to setText(),
6350      * since the TextView has no way to know that the text
6351      * has changed and that it needs to invalidate and re-layout.
6352      *
6353      * @param text char array to be displayed
6354      * @param start start index in the char array
6355      * @param len length of char count after {@code start}
6356      */
setText(char[] text, int start, int len)6357     public final void setText(char[] text, int start, int len) {
6358         int oldlen = 0;
6359 
6360         if (start < 0 || len < 0 || start + len > text.length) {
6361             throw new IndexOutOfBoundsException(start + ", " + len);
6362         }
6363 
6364         /*
6365          * We must do the before-notification here ourselves because if
6366          * the old text is a CharWrapper we destroy it before calling
6367          * into the normal path.
6368          */
6369         if (mText != null) {
6370             oldlen = mText.length();
6371             sendBeforeTextChanged(mText, 0, oldlen, len);
6372         } else {
6373             sendBeforeTextChanged("", 0, 0, len);
6374         }
6375 
6376         if (mCharWrapper == null) {
6377             mCharWrapper = new CharWrapper(text, start, len);
6378         } else {
6379             mCharWrapper.set(text, start, len);
6380         }
6381 
6382         setText(mCharWrapper, mBufferType, false, oldlen);
6383     }
6384 
6385     /**
6386      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
6387      * the cursor position. Same as
6388      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
6389      * position (if any) is retained in the new text.
6390      * <p/>
6391      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6392      * intermediate {@link Spannable Spannables}. Likewise it will use
6393      * {@link android.text.Editable.Factory} to create final or intermediate
6394      * {@link Editable Editables}.
6395      *
6396      * @param text text to be displayed
6397      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6398      *              stored as a static text, styleable/spannable text, or editable text
6399      *
6400      * @see #setText(CharSequence, android.widget.TextView.BufferType)
6401      */
setTextKeepState(CharSequence text, BufferType type)6402     public final void setTextKeepState(CharSequence text, BufferType type) {
6403         int start = getSelectionStart();
6404         int end = getSelectionEnd();
6405         int len = text.length();
6406 
6407         setText(text, type);
6408 
6409         if (start >= 0 || end >= 0) {
6410             if (mSpannable != null) {
6411                 Selection.setSelection(mSpannable,
6412                                        Math.max(0, Math.min(start, len)),
6413                                        Math.max(0, Math.min(end, len)));
6414             }
6415         }
6416     }
6417 
6418     /**
6419      * Sets the text to be displayed using a string resource identifier.
6420      *
6421      * @param resid the resource identifier of the string resource to be displayed
6422      *
6423      * @see #setText(CharSequence)
6424      *
6425      * @attr ref android.R.styleable#TextView_text
6426      */
6427     @android.view.RemotableViewMethod
setText(@tringRes int resid)6428     public final void setText(@StringRes int resid) {
6429         setText(getContext().getResources().getText(resid));
6430         mTextSetFromXmlOrResourceId = true;
6431         mTextId = resid;
6432     }
6433 
6434     /**
6435      * Sets the text to be displayed using a string resource identifier and the
6436      * {@link android.widget.TextView.BufferType}.
6437      * <p/>
6438      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6439      * intermediate {@link Spannable Spannables}. Likewise it will use
6440      * {@link android.text.Editable.Factory} to create final or intermediate
6441      * {@link Editable Editables}.
6442      *
6443      * @param resid the resource identifier of the string resource to be displayed
6444      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6445      *              stored as a static text, styleable/spannable text, or editable text
6446      *
6447      * @see #setText(int)
6448      * @see #setText(CharSequence)
6449      * @see android.widget.TextView.BufferType
6450      * @see #setSpannableFactory(Spannable.Factory)
6451      * @see #setEditableFactory(Editable.Factory)
6452      *
6453      * @attr ref android.R.styleable#TextView_text
6454      * @attr ref android.R.styleable#TextView_bufferType
6455      */
setText(@tringRes int resid, BufferType type)6456     public final void setText(@StringRes int resid, BufferType type) {
6457         setText(getContext().getResources().getText(resid), type);
6458         mTextSetFromXmlOrResourceId = true;
6459         mTextId = resid;
6460     }
6461 
6462     /**
6463      * Sets the text to be displayed when the text of the TextView is empty.
6464      * Null means to use the normal empty text. The hint does not currently
6465      * participate in determining the size of the view.
6466      *
6467      * @attr ref android.R.styleable#TextView_hint
6468      */
6469     @android.view.RemotableViewMethod
setHint(CharSequence hint)6470     public final void setHint(CharSequence hint) {
6471         setHintInternal(hint);
6472 
6473         if (mEditor != null && isInputMethodTarget()) {
6474             mEditor.reportExtractedText();
6475         }
6476     }
6477 
setHintInternal(CharSequence hint)6478     private void setHintInternal(CharSequence hint) {
6479         mHint = TextUtils.stringOrSpannedString(hint);
6480 
6481         if (mLayout != null) {
6482             checkForRelayout();
6483         }
6484 
6485         if (mText.length() == 0) {
6486             invalidate();
6487         }
6488 
6489         // Invalidate display list if hint is currently used
6490         if (mEditor != null && mText.length() == 0 && mHint != null) {
6491             mEditor.invalidateTextDisplayList();
6492         }
6493     }
6494 
6495     /**
6496      * Sets the text to be displayed when the text of the TextView is empty,
6497      * from a resource.
6498      *
6499      * @attr ref android.R.styleable#TextView_hint
6500      */
6501     @android.view.RemotableViewMethod
setHint(@tringRes int resid)6502     public final void setHint(@StringRes int resid) {
6503         mHintId = resid;
6504         setHint(getContext().getResources().getText(resid));
6505     }
6506 
6507     /**
6508      * Returns the hint that is displayed when the text of the TextView
6509      * is empty.
6510      *
6511      * @attr ref android.R.styleable#TextView_hint
6512      */
6513     @InspectableProperty
6514     @ViewDebug.CapturedViewProperty
getHint()6515     public CharSequence getHint() {
6516         return mHint;
6517     }
6518 
6519     /**
6520      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
6521      * line characters instead of letting it wrap onto multiple lines.
6522      *
6523      * @attr ref android.R.styleable#TextView_singleLine
6524      */
6525     @InspectableProperty
isSingleLine()6526     public boolean isSingleLine() {
6527         return mSingleLine;
6528     }
6529 
isMultilineInputType(int type)6530     private static boolean isMultilineInputType(int type) {
6531         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
6532                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
6533     }
6534 
6535     /**
6536      * Removes the suggestion spans.
6537      */
removeSuggestionSpans(CharSequence text)6538     CharSequence removeSuggestionSpans(CharSequence text) {
6539         if (text instanceof Spanned) {
6540             Spannable spannable;
6541             if (text instanceof Spannable) {
6542                 spannable = (Spannable) text;
6543             } else {
6544                 spannable = mSpannableFactory.newSpannable(text);
6545             }
6546 
6547             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
6548             if (spans.length == 0) {
6549                 return text;
6550             } else {
6551                 text = spannable;
6552             }
6553 
6554             for (int i = 0; i < spans.length; i++) {
6555                 spannable.removeSpan(spans[i]);
6556             }
6557         }
6558         return text;
6559     }
6560 
6561     /**
6562      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
6563      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
6564      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
6565      * then a soft keyboard will not be displayed for this text view.
6566      *
6567      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
6568      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
6569      * type.
6570      *
6571      * @see #getInputType()
6572      * @see #setRawInputType(int)
6573      * @see android.text.InputType
6574      * @attr ref android.R.styleable#TextView_inputType
6575      */
setInputType(int type)6576     public void setInputType(int type) {
6577         final boolean wasPassword = isPasswordInputType(getInputType());
6578         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
6579         setInputType(type, false);
6580         final boolean isPassword = isPasswordInputType(type);
6581         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
6582         boolean forceUpdate = false;
6583         if (isPassword) {
6584             setTransformationMethod(PasswordTransformationMethod.getInstance());
6585             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6586                     Typeface.NORMAL, -1 /* weight, not specifeid */);
6587         } else if (isVisiblePassword) {
6588             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6589                 forceUpdate = true;
6590             }
6591             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6592                     Typeface.NORMAL, -1 /* weight, not specified */);
6593         } else if (wasPassword || wasVisiblePassword) {
6594             // not in password mode, clean up typeface and transformation
6595             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
6596                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
6597                     -1 /* weight, not specified */);
6598             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6599                 forceUpdate = true;
6600             }
6601         }
6602 
6603         boolean singleLine = !isMultilineInputType(type);
6604 
6605         // We need to update the single line mode if it has changed or we
6606         // were previously in password mode.
6607         if (mSingleLine != singleLine || forceUpdate) {
6608             // Change single line mode, but only change the transformation if
6609             // we are not in password mode.
6610             applySingleLine(singleLine, !isPassword, true, true);
6611         }
6612 
6613         if (!isSuggestionsEnabled()) {
6614             setTextInternal(removeSuggestionSpans(mText));
6615         }
6616 
6617         InputMethodManager imm = getInputMethodManager();
6618         if (imm != null) imm.restartInput(this);
6619     }
6620 
6621     /**
6622      * It would be better to rely on the input type for everything. A password inputType should have
6623      * a password transformation. We should hence use isPasswordInputType instead of this method.
6624      *
6625      * We should:
6626      * - Call setInputType in setKeyListener instead of changing the input type directly (which
6627      * would install the correct transformation).
6628      * - Refuse the installation of a non-password transformation in setTransformation if the input
6629      * type is password.
6630      *
6631      * However, this is like this for legacy reasons and we cannot break existing apps. This method
6632      * is useful since it matches what the user can see (obfuscated text or not).
6633      *
6634      * @return true if the current transformation method is of the password type.
6635      */
hasPasswordTransformationMethod()6636     boolean hasPasswordTransformationMethod() {
6637         return mTransformation instanceof PasswordTransformationMethod;
6638     }
6639 
6640     /**
6641      * Returns true if the current inputType is any type of password.
6642      *
6643      * @hide
6644      */
isAnyPasswordInputType()6645     public boolean isAnyPasswordInputType() {
6646         final int inputType = getInputType();
6647         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
6648     }
6649 
isPasswordInputType(int inputType)6650     static boolean isPasswordInputType(int inputType) {
6651         final int variation =
6652                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6653         return variation
6654                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
6655                 || variation
6656                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
6657                 || variation
6658                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
6659     }
6660 
isVisiblePasswordInputType(int inputType)6661     private static boolean isVisiblePasswordInputType(int inputType) {
6662         final int variation =
6663                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6664         return variation
6665                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
6666     }
6667 
6668     /**
6669      * Directly change the content type integer of the text view, without
6670      * modifying any other state.
6671      * @see #setInputType(int)
6672      * @see android.text.InputType
6673      * @attr ref android.R.styleable#TextView_inputType
6674      */
setRawInputType(int type)6675     public void setRawInputType(int type) {
6676         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
6677         createEditorIfNeeded();
6678         mEditor.mInputType = type;
6679     }
6680 
6681     /**
6682      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
6683      *         a {@code Locale} object that can be used to customize key various listeners.
6684      * @see DateKeyListener#getInstance(Locale)
6685      * @see DateTimeKeyListener#getInstance(Locale)
6686      * @see DigitsKeyListener#getInstance(Locale)
6687      * @see TimeKeyListener#getInstance(Locale)
6688      */
6689     @Nullable
getCustomLocaleForKeyListenerOrNull()6690     private Locale getCustomLocaleForKeyListenerOrNull() {
6691         if (!mUseInternationalizedInput) {
6692             // If the application does not target O, stick to the previous behavior.
6693             return null;
6694         }
6695         final LocaleList locales = getImeHintLocales();
6696         if (locales == null) {
6697             // If the application does not explicitly specify IME hint locale, also stick to the
6698             // previous behavior.
6699             return null;
6700         }
6701         return locales.get(0);
6702     }
6703 
6704     @UnsupportedAppUsage
setInputType(int type, boolean direct)6705     private void setInputType(int type, boolean direct) {
6706         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
6707         KeyListener input;
6708         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
6709             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
6710             TextKeyListener.Capitalize cap;
6711             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
6712                 cap = TextKeyListener.Capitalize.CHARACTERS;
6713             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
6714                 cap = TextKeyListener.Capitalize.WORDS;
6715             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
6716                 cap = TextKeyListener.Capitalize.SENTENCES;
6717             } else {
6718                 cap = TextKeyListener.Capitalize.NONE;
6719             }
6720             input = TextKeyListener.getInstance(autotext, cap);
6721         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
6722             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6723             input = DigitsKeyListener.getInstance(
6724                     locale,
6725                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
6726                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
6727             if (locale != null) {
6728                 // Override type, if necessary for i18n.
6729                 int newType = input.getInputType();
6730                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
6731                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
6732                     // The class is different from the original class. So we need to override
6733                     // 'type'. But we want to keep the password flag if it's there.
6734                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
6735                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
6736                     }
6737                     type = newType;
6738                 }
6739             }
6740         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
6741             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6742             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
6743                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
6744                     input = DateKeyListener.getInstance(locale);
6745                     break;
6746                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
6747                     input = TimeKeyListener.getInstance(locale);
6748                     break;
6749                 default:
6750                     input = DateTimeKeyListener.getInstance(locale);
6751                     break;
6752             }
6753             if (mUseInternationalizedInput) {
6754                 type = input.getInputType(); // Override type, if necessary for i18n.
6755             }
6756         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
6757             input = DialerKeyListener.getInstance();
6758         } else {
6759             input = TextKeyListener.getInstance();
6760         }
6761         setRawInputType(type);
6762         mListenerChanged = false;
6763         if (direct) {
6764             createEditorIfNeeded();
6765             mEditor.mKeyListener = input;
6766         } else {
6767             setKeyListenerOnly(input);
6768         }
6769     }
6770 
6771     /**
6772      * Get the type of the editable content.
6773      *
6774      * @see #setInputType(int)
6775      * @see android.text.InputType
6776      */
6777     @InspectableProperty(flagMapping = {
6778             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
6779             @FlagEntry(
6780                     name = "text",
6781                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6782                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
6783             @FlagEntry(
6784                     name = "textUri",
6785                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6786                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
6787             @FlagEntry(
6788                     name = "textEmailAddress",
6789                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6790                     target = InputType.TYPE_CLASS_TEXT
6791                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
6792             @FlagEntry(
6793                     name = "textEmailSubject",
6794                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6795                     target = InputType.TYPE_CLASS_TEXT
6796                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
6797             @FlagEntry(
6798                     name = "textShortMessage",
6799                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6800                     target = InputType.TYPE_CLASS_TEXT
6801                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
6802             @FlagEntry(
6803                     name = "textLongMessage",
6804                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6805                     target = InputType.TYPE_CLASS_TEXT
6806                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
6807             @FlagEntry(
6808                     name = "textPersonName",
6809                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6810                     target = InputType.TYPE_CLASS_TEXT
6811                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
6812             @FlagEntry(
6813                     name = "textPostalAddress",
6814                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6815                     target = InputType.TYPE_CLASS_TEXT
6816                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
6817             @FlagEntry(
6818                     name = "textPassword",
6819                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6820                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
6821             @FlagEntry(
6822                     name = "textVisiblePassword",
6823                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6824                     target = InputType.TYPE_CLASS_TEXT
6825                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
6826             @FlagEntry(
6827                     name = "textWebEditText",
6828                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6829                     target = InputType.TYPE_CLASS_TEXT
6830                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
6831             @FlagEntry(
6832                     name = "textFilter",
6833                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6834                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
6835             @FlagEntry(
6836                     name = "textPhonetic",
6837                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6838                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
6839             @FlagEntry(
6840                     name = "textWebEmailAddress",
6841                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6842                     target = InputType.TYPE_CLASS_TEXT
6843                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
6844             @FlagEntry(
6845                     name = "textWebPassword",
6846                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6847                     target = InputType.TYPE_CLASS_TEXT
6848                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
6849             @FlagEntry(
6850                     name = "number",
6851                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6852                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
6853             @FlagEntry(
6854                     name = "numberPassword",
6855                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6856                     target = InputType.TYPE_CLASS_NUMBER
6857                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
6858             @FlagEntry(
6859                     name = "phone",
6860                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6861                     target = InputType.TYPE_CLASS_PHONE),
6862             @FlagEntry(
6863                     name = "datetime",
6864                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6865                     target = InputType.TYPE_CLASS_DATETIME
6866                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
6867             @FlagEntry(
6868                     name = "date",
6869                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6870                     target = InputType.TYPE_CLASS_DATETIME
6871                             | InputType.TYPE_DATETIME_VARIATION_DATE),
6872             @FlagEntry(
6873                     name = "time",
6874                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6875                     target = InputType.TYPE_CLASS_DATETIME
6876                             | InputType.TYPE_DATETIME_VARIATION_TIME),
6877             @FlagEntry(
6878                     name = "textCapCharacters",
6879                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6880                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
6881             @FlagEntry(
6882                     name = "textCapWords",
6883                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6884                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
6885             @FlagEntry(
6886                     name = "textCapSentences",
6887                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6888                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
6889             @FlagEntry(
6890                     name = "textAutoCorrect",
6891                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6892                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
6893             @FlagEntry(
6894                     name = "textAutoComplete",
6895                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6896                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
6897             @FlagEntry(
6898                     name = "textMultiLine",
6899                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6900                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
6901             @FlagEntry(
6902                     name = "textImeMultiLine",
6903                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6904                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
6905             @FlagEntry(
6906                     name = "textNoSuggestions",
6907                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6908                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
6909             @FlagEntry(
6910                     name = "numberSigned",
6911                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6912                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
6913             @FlagEntry(
6914                     name = "numberDecimal",
6915                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6916                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
6917     })
getInputType()6918     public int getInputType() {
6919         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
6920     }
6921 
6922     /**
6923      * Change the editor type integer associated with the text view, which
6924      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
6925      * when it has focus.
6926      * @see #getImeOptions
6927      * @see android.view.inputmethod.EditorInfo
6928      * @attr ref android.R.styleable#TextView_imeOptions
6929      */
setImeOptions(int imeOptions)6930     public void setImeOptions(int imeOptions) {
6931         createEditorIfNeeded();
6932         mEditor.createInputContentTypeIfNeeded();
6933         mEditor.mInputContentType.imeOptions = imeOptions;
6934     }
6935 
6936     /**
6937      * Get the type of the Input Method Editor (IME).
6938      * @return the type of the IME
6939      * @see #setImeOptions(int)
6940      * @see EditorInfo
6941      */
6942     @InspectableProperty(flagMapping = {
6943             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
6944             @FlagEntry(
6945                     name = "actionUnspecified",
6946                     mask = EditorInfo.IME_MASK_ACTION,
6947                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
6948             @FlagEntry(
6949                     name = "actionNone",
6950                     mask = EditorInfo.IME_MASK_ACTION,
6951                     target = EditorInfo.IME_ACTION_NONE),
6952             @FlagEntry(
6953                     name = "actionGo",
6954                     mask = EditorInfo.IME_MASK_ACTION,
6955                     target = EditorInfo.IME_ACTION_GO),
6956             @FlagEntry(
6957                     name = "actionSearch",
6958                     mask = EditorInfo.IME_MASK_ACTION,
6959                     target = EditorInfo.IME_ACTION_SEARCH),
6960             @FlagEntry(
6961                     name = "actionSend",
6962                     mask = EditorInfo.IME_MASK_ACTION,
6963                     target = EditorInfo.IME_ACTION_SEND),
6964             @FlagEntry(
6965                     name = "actionNext",
6966                     mask = EditorInfo.IME_MASK_ACTION,
6967                     target = EditorInfo.IME_ACTION_NEXT),
6968             @FlagEntry(
6969                     name = "actionDone",
6970                     mask = EditorInfo.IME_MASK_ACTION,
6971                     target = EditorInfo.IME_ACTION_DONE),
6972             @FlagEntry(
6973                     name = "actionPrevious",
6974                     mask = EditorInfo.IME_MASK_ACTION,
6975                     target = EditorInfo.IME_ACTION_PREVIOUS),
6976             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
6977             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
6978             @FlagEntry(
6979                     name = "flagNavigatePrevious",
6980                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
6981             @FlagEntry(
6982                     name = "flagNoAccessoryAction",
6983                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
6984             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
6985             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
6986             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
6987             @FlagEntry(
6988                     name = "flagNoPersonalizedLearning",
6989                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
6990     })
getImeOptions()6991     public int getImeOptions() {
6992         return mEditor != null && mEditor.mInputContentType != null
6993                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
6994     }
6995 
6996     /**
6997      * Change the custom IME action associated with the text view, which
6998      * will be reported to an IME with {@link EditorInfo#actionLabel}
6999      * and {@link EditorInfo#actionId} when it has focus.
7000      * @see #getImeActionLabel
7001      * @see #getImeActionId
7002      * @see android.view.inputmethod.EditorInfo
7003      * @attr ref android.R.styleable#TextView_imeActionLabel
7004      * @attr ref android.R.styleable#TextView_imeActionId
7005      */
setImeActionLabel(CharSequence label, int actionId)7006     public void setImeActionLabel(CharSequence label, int actionId) {
7007         createEditorIfNeeded();
7008         mEditor.createInputContentTypeIfNeeded();
7009         mEditor.mInputContentType.imeActionLabel = label;
7010         mEditor.mInputContentType.imeActionId = actionId;
7011     }
7012 
7013     /**
7014      * Get the IME action label previous set with {@link #setImeActionLabel}.
7015      *
7016      * @see #setImeActionLabel
7017      * @see android.view.inputmethod.EditorInfo
7018      */
7019     @InspectableProperty
getImeActionLabel()7020     public CharSequence getImeActionLabel() {
7021         return mEditor != null && mEditor.mInputContentType != null
7022                 ? mEditor.mInputContentType.imeActionLabel : null;
7023     }
7024 
7025     /**
7026      * Get the IME action ID previous set with {@link #setImeActionLabel}.
7027      *
7028      * @see #setImeActionLabel
7029      * @see android.view.inputmethod.EditorInfo
7030      */
7031     @InspectableProperty
getImeActionId()7032     public int getImeActionId() {
7033         return mEditor != null && mEditor.mInputContentType != null
7034                 ? mEditor.mInputContentType.imeActionId : 0;
7035     }
7036 
7037     /**
7038      * Set a special listener to be called when an action is performed
7039      * on the text view.  This will be called when the enter key is pressed,
7040      * or when an action supplied to the IME is selected by the user.  Setting
7041      * this means that the normal hard key event will not insert a newline
7042      * into the text view, even if it is multi-line; holding down the ALT
7043      * modifier will, however, allow the user to insert a newline character.
7044      */
setOnEditorActionListener(OnEditorActionListener l)7045     public void setOnEditorActionListener(OnEditorActionListener l) {
7046         createEditorIfNeeded();
7047         mEditor.createInputContentTypeIfNeeded();
7048         mEditor.mInputContentType.onEditorActionListener = l;
7049     }
7050 
7051     /**
7052      * Called when an attached input method calls
7053      * {@link InputConnection#performEditorAction(int)
7054      * InputConnection.performEditorAction()}
7055      * for this text view.  The default implementation will call your action
7056      * listener supplied to {@link #setOnEditorActionListener}, or perform
7057      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
7058      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
7059      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
7060      * EditorInfo.IME_ACTION_DONE}.
7061      *
7062      * <p>For backwards compatibility, if no IME options have been set and the
7063      * text view would not normally advance focus on enter, then
7064      * the NEXT and DONE actions received here will be turned into an enter
7065      * key down/up pair to go through the normal key handling.
7066      *
7067      * @param actionCode The code of the action being performed.
7068      *
7069      * @see #setOnEditorActionListener
7070      */
onEditorAction(int actionCode)7071     public void onEditorAction(int actionCode) {
7072         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
7073         if (ict != null) {
7074             if (ict.onEditorActionListener != null) {
7075                 if (ict.onEditorActionListener.onEditorAction(this,
7076                         actionCode, null)) {
7077                     return;
7078                 }
7079             }
7080 
7081             // This is the handling for some default action.
7082             // Note that for backwards compatibility we don't do this
7083             // default handling if explicit ime options have not been given,
7084             // instead turning this into the normal enter key codes that an
7085             // app may be expecting.
7086             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
7087                 View v = focusSearch(FOCUS_FORWARD);
7088                 if (v != null) {
7089                     if (!v.requestFocus(FOCUS_FORWARD)) {
7090                         throw new IllegalStateException("focus search returned a view "
7091                                 + "that wasn't able to take focus!");
7092                     }
7093                 }
7094                 return;
7095 
7096             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
7097                 View v = focusSearch(FOCUS_BACKWARD);
7098                 if (v != null) {
7099                     if (!v.requestFocus(FOCUS_BACKWARD)) {
7100                         throw new IllegalStateException("focus search returned a view "
7101                                 + "that wasn't able to take focus!");
7102                     }
7103                 }
7104                 return;
7105 
7106             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
7107                 InputMethodManager imm = getInputMethodManager();
7108                 if (imm != null && imm.isActive(this)) {
7109                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7110                 }
7111                 return;
7112             }
7113         }
7114 
7115         ViewRootImpl viewRootImpl = getViewRootImpl();
7116         if (viewRootImpl != null) {
7117             long eventTime = SystemClock.uptimeMillis();
7118             viewRootImpl.dispatchKeyFromIme(
7119                     new KeyEvent(eventTime, eventTime,
7120                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
7121                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7122                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7123                     | KeyEvent.FLAG_EDITOR_ACTION));
7124             viewRootImpl.dispatchKeyFromIme(
7125                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
7126                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
7127                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7128                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7129                     | KeyEvent.FLAG_EDITOR_ACTION));
7130         }
7131     }
7132 
7133     /**
7134      * Set the private content type of the text, which is the
7135      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
7136      * field that will be filled in when creating an input connection.
7137      *
7138      * @see #getPrivateImeOptions()
7139      * @see EditorInfo#privateImeOptions
7140      * @attr ref android.R.styleable#TextView_privateImeOptions
7141      */
setPrivateImeOptions(String type)7142     public void setPrivateImeOptions(String type) {
7143         createEditorIfNeeded();
7144         mEditor.createInputContentTypeIfNeeded();
7145         mEditor.mInputContentType.privateImeOptions = type;
7146     }
7147 
7148     /**
7149      * Get the private type of the content.
7150      *
7151      * @see #setPrivateImeOptions(String)
7152      * @see EditorInfo#privateImeOptions
7153      */
7154     @InspectableProperty
getPrivateImeOptions()7155     public String getPrivateImeOptions() {
7156         return mEditor != null && mEditor.mInputContentType != null
7157                 ? mEditor.mInputContentType.privateImeOptions : null;
7158     }
7159 
7160     /**
7161      * Set the extra input data of the text, which is the
7162      * {@link EditorInfo#extras TextBoxAttribute.extras}
7163      * Bundle that will be filled in when creating an input connection.  The
7164      * given integer is the resource identifier of an XML resource holding an
7165      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
7166      *
7167      * @see #getInputExtras(boolean)
7168      * @see EditorInfo#extras
7169      * @attr ref android.R.styleable#TextView_editorExtras
7170      */
setInputExtras(@mlRes int xmlResId)7171     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
7172         createEditorIfNeeded();
7173         XmlResourceParser parser = getResources().getXml(xmlResId);
7174         mEditor.createInputContentTypeIfNeeded();
7175         mEditor.mInputContentType.extras = new Bundle();
7176         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
7177     }
7178 
7179     /**
7180      * Retrieve the input extras currently associated with the text view, which
7181      * can be viewed as well as modified.
7182      *
7183      * @param create If true, the extras will be created if they don't already
7184      * exist.  Otherwise, null will be returned if none have been created.
7185      * @see #setInputExtras(int)
7186      * @see EditorInfo#extras
7187      * @attr ref android.R.styleable#TextView_editorExtras
7188      */
getInputExtras(boolean create)7189     public Bundle getInputExtras(boolean create) {
7190         if (mEditor == null && !create) return null;
7191         createEditorIfNeeded();
7192         if (mEditor.mInputContentType == null) {
7193             if (!create) return null;
7194             mEditor.createInputContentTypeIfNeeded();
7195         }
7196         if (mEditor.mInputContentType.extras == null) {
7197             if (!create) return null;
7198             mEditor.mInputContentType.extras = new Bundle();
7199         }
7200         return mEditor.mInputContentType.extras;
7201     }
7202 
7203     /**
7204      * Change "hint" locales associated with the text view, which will be reported to an IME with
7205      * {@link EditorInfo#hintLocales} when it has focus.
7206      *
7207      * Starting with Android O, this also causes internationalized listeners to be created (or
7208      * change locale) based on the first locale in the input locale list.
7209      *
7210      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
7211      * call {@link InputMethodManager#restartInput(View)}.</p>
7212      * @param hintLocales List of the languages that the user is supposed to switch to no matter
7213      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
7214      * @see #getImeHintLocales()
7215      * @see android.view.inputmethod.EditorInfo#hintLocales
7216      */
setImeHintLocales(@ullable LocaleList hintLocales)7217     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
7218         createEditorIfNeeded();
7219         mEditor.createInputContentTypeIfNeeded();
7220         mEditor.mInputContentType.imeHintLocales = hintLocales;
7221         if (mUseInternationalizedInput) {
7222             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
7223         }
7224     }
7225 
7226     /**
7227      * @return The current languages list "hint". {@code null} when no "hint" is available.
7228      * @see #setImeHintLocales(LocaleList)
7229      * @see android.view.inputmethod.EditorInfo#hintLocales
7230      */
7231     @Nullable
getImeHintLocales()7232     public LocaleList getImeHintLocales() {
7233         if (mEditor == null) {
7234             return null;
7235         }
7236         if (mEditor.mInputContentType == null) {
7237             return null;
7238         }
7239         return mEditor.mInputContentType.imeHintLocales;
7240     }
7241 
7242     /**
7243      * Returns the error message that was set to be displayed with
7244      * {@link #setError}, or <code>null</code> if no error was set
7245      * or if it the error was cleared by the widget after user input.
7246      */
getError()7247     public CharSequence getError() {
7248         return mEditor == null ? null : mEditor.mError;
7249     }
7250 
7251     /**
7252      * Sets the right-hand compound drawable of the TextView to the "error"
7253      * icon and sets an error message that will be displayed in a popup when
7254      * the TextView has focus.  The icon and error message will be reset to
7255      * null when any key events cause changes to the TextView's text.  If the
7256      * <code>error</code> is <code>null</code>, the error message and icon
7257      * will be cleared.
7258      */
7259     @android.view.RemotableViewMethod
setError(CharSequence error)7260     public void setError(CharSequence error) {
7261         if (error == null) {
7262             setError(null, null);
7263         } else {
7264             Drawable dr = getContext().getDrawable(
7265                     com.android.internal.R.drawable.indicator_input_error);
7266 
7267             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
7268             setError(error, dr);
7269         }
7270     }
7271 
7272     /**
7273      * Sets the right-hand compound drawable of the TextView to the specified
7274      * icon and sets an error message that will be displayed in a popup when
7275      * the TextView has focus.  The icon and error message will be reset to
7276      * null when any key events cause changes to the TextView's text.  The
7277      * drawable must already have had {@link Drawable#setBounds} set on it.
7278      * If the <code>error</code> is <code>null</code>, the error message will
7279      * be cleared (and you should provide a <code>null</code> icon as well).
7280      */
setError(CharSequence error, Drawable icon)7281     public void setError(CharSequence error, Drawable icon) {
7282         createEditorIfNeeded();
7283         mEditor.setError(error, icon);
7284         notifyViewAccessibilityStateChangedIfNeeded(
7285                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7286     }
7287 
7288     @Override
setFrame(int l, int t, int r, int b)7289     protected boolean setFrame(int l, int t, int r, int b) {
7290         boolean result = super.setFrame(l, t, r, b);
7291 
7292         if (mEditor != null) mEditor.setFrame();
7293 
7294         restartMarqueeIfNeeded();
7295 
7296         return result;
7297     }
7298 
restartMarqueeIfNeeded()7299     private void restartMarqueeIfNeeded() {
7300         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7301             mRestartMarquee = false;
7302             startMarquee();
7303         }
7304     }
7305 
7306     /**
7307      * Sets the list of input filters that will be used if the buffer is
7308      * Editable. Has no effect otherwise.
7309      *
7310      * @attr ref android.R.styleable#TextView_maxLength
7311      */
setFilters(InputFilter[] filters)7312     public void setFilters(InputFilter[] filters) {
7313         if (filters == null) {
7314             throw new IllegalArgumentException();
7315         }
7316 
7317         mFilters = filters;
7318 
7319         if (mText instanceof Editable) {
7320             setFilters((Editable) mText, filters);
7321         }
7322     }
7323 
7324     /**
7325      * Sets the list of input filters on the specified Editable,
7326      * and includes mInput in the list if it is an InputFilter.
7327      */
setFilters(Editable e, InputFilter[] filters)7328     private void setFilters(Editable e, InputFilter[] filters) {
7329         if (mEditor != null) {
7330             final boolean undoFilter = mEditor.mUndoInputFilter != null;
7331             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
7332             int num = 0;
7333             if (undoFilter) num++;
7334             if (keyFilter) num++;
7335             if (num > 0) {
7336                 InputFilter[] nf = new InputFilter[filters.length + num];
7337 
7338                 System.arraycopy(filters, 0, nf, 0, filters.length);
7339                 num = 0;
7340                 if (undoFilter) {
7341                     nf[filters.length] = mEditor.mUndoInputFilter;
7342                     num++;
7343                 }
7344                 if (keyFilter) {
7345                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
7346                 }
7347 
7348                 e.setFilters(nf);
7349                 return;
7350             }
7351         }
7352         e.setFilters(filters);
7353     }
7354 
7355     /**
7356      * Returns the current list of input filters.
7357      *
7358      * @attr ref android.R.styleable#TextView_maxLength
7359      */
getFilters()7360     public InputFilter[] getFilters() {
7361         return mFilters;
7362     }
7363 
7364     /////////////////////////////////////////////////////////////////////////
7365 
getBoxHeight(Layout l)7366     private int getBoxHeight(Layout l) {
7367         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
7368         int padding = (l == mHintLayout)
7369                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
7370                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
7371         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
7372     }
7373 
7374     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)7375     int getVerticalOffset(boolean forceNormal) {
7376         int voffset = 0;
7377         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7378 
7379         Layout l = mLayout;
7380         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7381             l = mHintLayout;
7382         }
7383 
7384         if (gravity != Gravity.TOP) {
7385             int boxht = getBoxHeight(l);
7386             int textht = l.getHeight();
7387 
7388             if (textht < boxht) {
7389                 if (gravity == Gravity.BOTTOM) {
7390                     voffset = boxht - textht;
7391                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7392                     voffset = (boxht - textht) >> 1;
7393                 }
7394             }
7395         }
7396         return voffset;
7397     }
7398 
getBottomVerticalOffset(boolean forceNormal)7399     private int getBottomVerticalOffset(boolean forceNormal) {
7400         int voffset = 0;
7401         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7402 
7403         Layout l = mLayout;
7404         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7405             l = mHintLayout;
7406         }
7407 
7408         if (gravity != Gravity.BOTTOM) {
7409             int boxht = getBoxHeight(l);
7410             int textht = l.getHeight();
7411 
7412             if (textht < boxht) {
7413                 if (gravity == Gravity.TOP) {
7414                     voffset = boxht - textht;
7415                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7416                     voffset = (boxht - textht) >> 1;
7417                 }
7418             }
7419         }
7420         return voffset;
7421     }
7422 
invalidateCursorPath()7423     void invalidateCursorPath() {
7424         if (mHighlightPathBogus) {
7425             invalidateCursor();
7426         } else {
7427             final int horizontalPadding = getCompoundPaddingLeft();
7428             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7429 
7430             if (mEditor.mDrawableForCursor == null) {
7431                 synchronized (TEMP_RECTF) {
7432                     /*
7433                      * The reason for this concern about the thickness of the
7434                      * cursor and doing the floor/ceil on the coordinates is that
7435                      * some EditTexts (notably textfields in the Browser) have
7436                      * anti-aliased text where not all the characters are
7437                      * necessarily at integer-multiple locations.  This should
7438                      * make sure the entire cursor gets invalidated instead of
7439                      * sometimes missing half a pixel.
7440                      */
7441                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
7442                     if (thick < 1.0f) {
7443                         thick = 1.0f;
7444                     }
7445 
7446                     thick /= 2.0f;
7447 
7448                     // mHighlightPath is guaranteed to be non null at that point.
7449                     mHighlightPath.computeBounds(TEMP_RECTF, false);
7450 
7451                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
7452                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
7453                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
7454                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
7455                 }
7456             } else {
7457                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7458                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
7459                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
7460             }
7461         }
7462     }
7463 
invalidateCursor()7464     void invalidateCursor() {
7465         int where = getSelectionEnd();
7466 
7467         invalidateCursor(where, where, where);
7468     }
7469 
invalidateCursor(int a, int b, int c)7470     private void invalidateCursor(int a, int b, int c) {
7471         if (a >= 0 || b >= 0 || c >= 0) {
7472             int start = Math.min(Math.min(a, b), c);
7473             int end = Math.max(Math.max(a, b), c);
7474             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
7475         }
7476     }
7477 
7478     /**
7479      * Invalidates the region of text enclosed between the start and end text offsets.
7480      */
invalidateRegion(int start, int end, boolean invalidateCursor)7481     void invalidateRegion(int start, int end, boolean invalidateCursor) {
7482         if (mLayout == null) {
7483             invalidate();
7484         } else {
7485             int lineStart = mLayout.getLineForOffset(start);
7486             int top = mLayout.getLineTop(lineStart);
7487 
7488             // This is ridiculous, but the descent from the line above
7489             // can hang down into the line we really want to redraw,
7490             // so we have to invalidate part of the line above to make
7491             // sure everything that needs to be redrawn really is.
7492             // (But not the whole line above, because that would cause
7493             // the same problem with the descenders on the line above it!)
7494             if (lineStart > 0) {
7495                 top -= mLayout.getLineDescent(lineStart - 1);
7496             }
7497 
7498             int lineEnd;
7499 
7500             if (start == end) {
7501                 lineEnd = lineStart;
7502             } else {
7503                 lineEnd = mLayout.getLineForOffset(end);
7504             }
7505 
7506             int bottom = mLayout.getLineBottom(lineEnd);
7507 
7508             // mEditor can be null in case selection is set programmatically.
7509             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
7510                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7511                 top = Math.min(top, bounds.top);
7512                 bottom = Math.max(bottom, bounds.bottom);
7513             }
7514 
7515             final int compoundPaddingLeft = getCompoundPaddingLeft();
7516             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7517 
7518             int left, right;
7519             if (lineStart == lineEnd && !invalidateCursor) {
7520                 left = (int) mLayout.getPrimaryHorizontal(start);
7521                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
7522                 left += compoundPaddingLeft;
7523                 right += compoundPaddingLeft;
7524             } else {
7525                 // Rectangle bounding box when the region spans several lines
7526                 left = compoundPaddingLeft;
7527                 right = getWidth() - getCompoundPaddingRight();
7528             }
7529 
7530             invalidate(mScrollX + left, verticalPadding + top,
7531                     mScrollX + right, verticalPadding + bottom);
7532         }
7533     }
7534 
registerForPreDraw()7535     private void registerForPreDraw() {
7536         if (!mPreDrawRegistered) {
7537             getViewTreeObserver().addOnPreDrawListener(this);
7538             mPreDrawRegistered = true;
7539         }
7540     }
7541 
unregisterForPreDraw()7542     private void unregisterForPreDraw() {
7543         getViewTreeObserver().removeOnPreDrawListener(this);
7544         mPreDrawRegistered = false;
7545         mPreDrawListenerDetached = false;
7546     }
7547 
7548     /**
7549      * {@inheritDoc}
7550      */
7551     @Override
onPreDraw()7552     public boolean onPreDraw() {
7553         if (mLayout == null) {
7554             assumeLayout();
7555         }
7556 
7557         if (mMovement != null) {
7558             /* This code also provides auto-scrolling when a cursor is moved using a
7559              * CursorController (insertion point or selection limits).
7560              * For selection, ensure start or end is visible depending on controller's state.
7561              */
7562             int curs = getSelectionEnd();
7563             // Do not create the controller if it is not already created.
7564             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
7565                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
7566                 curs = getSelectionStart();
7567             }
7568 
7569             /*
7570              * TODO: This should really only keep the end in view if
7571              * it already was before the text changed.  I'm not sure
7572              * of a good way to tell from here if it was.
7573              */
7574             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7575                 curs = mText.length();
7576             }
7577 
7578             if (curs >= 0) {
7579                 bringPointIntoView(curs);
7580             }
7581         } else {
7582             bringTextIntoView();
7583         }
7584 
7585         // This has to be checked here since:
7586         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
7587         //   a screen rotation) since layout is not yet initialized at that point.
7588         if (mEditor != null && mEditor.mCreatedWithASelection) {
7589             mEditor.refreshTextActionMode();
7590             mEditor.mCreatedWithASelection = false;
7591         }
7592 
7593         unregisterForPreDraw();
7594 
7595         return true;
7596     }
7597 
7598     @Override
onAttachedToWindow()7599     protected void onAttachedToWindow() {
7600         super.onAttachedToWindow();
7601 
7602         if (mEditor != null) mEditor.onAttachedToWindow();
7603 
7604         if (mPreDrawListenerDetached) {
7605             getViewTreeObserver().addOnPreDrawListener(this);
7606             mPreDrawListenerDetached = false;
7607         }
7608     }
7609 
7610     /** @hide */
7611     @Override
onDetachedFromWindowInternal()7612     protected void onDetachedFromWindowInternal() {
7613         if (mPreDrawRegistered) {
7614             getViewTreeObserver().removeOnPreDrawListener(this);
7615             mPreDrawListenerDetached = true;
7616         }
7617 
7618         resetResolvedDrawables();
7619 
7620         if (mEditor != null) mEditor.onDetachedFromWindow();
7621 
7622         super.onDetachedFromWindowInternal();
7623     }
7624 
7625     @Override
onScreenStateChanged(int screenState)7626     public void onScreenStateChanged(int screenState) {
7627         super.onScreenStateChanged(screenState);
7628         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
7629     }
7630 
7631     @Override
isPaddingOffsetRequired()7632     protected boolean isPaddingOffsetRequired() {
7633         return mShadowRadius != 0 || mDrawables != null;
7634     }
7635 
7636     @Override
getLeftPaddingOffset()7637     protected int getLeftPaddingOffset() {
7638         return getCompoundPaddingLeft() - mPaddingLeft
7639                 + (int) Math.min(0, mShadowDx - mShadowRadius);
7640     }
7641 
7642     @Override
getTopPaddingOffset()7643     protected int getTopPaddingOffset() {
7644         return (int) Math.min(0, mShadowDy - mShadowRadius);
7645     }
7646 
7647     @Override
getBottomPaddingOffset()7648     protected int getBottomPaddingOffset() {
7649         return (int) Math.max(0, mShadowDy + mShadowRadius);
7650     }
7651 
7652     @Override
getRightPaddingOffset()7653     protected int getRightPaddingOffset() {
7654         return -(getCompoundPaddingRight() - mPaddingRight)
7655                 + (int) Math.max(0, mShadowDx + mShadowRadius);
7656     }
7657 
7658     @Override
verifyDrawable(@onNull Drawable who)7659     protected boolean verifyDrawable(@NonNull Drawable who) {
7660         final boolean verified = super.verifyDrawable(who);
7661         if (!verified && mDrawables != null) {
7662             for (Drawable dr : mDrawables.mShowing) {
7663                 if (who == dr) {
7664                     return true;
7665                 }
7666             }
7667         }
7668         return verified;
7669     }
7670 
7671     @Override
jumpDrawablesToCurrentState()7672     public void jumpDrawablesToCurrentState() {
7673         super.jumpDrawablesToCurrentState();
7674         if (mDrawables != null) {
7675             for (Drawable dr : mDrawables.mShowing) {
7676                 if (dr != null) {
7677                     dr.jumpToCurrentState();
7678                 }
7679             }
7680         }
7681     }
7682 
7683     @Override
invalidateDrawable(@onNull Drawable drawable)7684     public void invalidateDrawable(@NonNull Drawable drawable) {
7685         boolean handled = false;
7686 
7687         if (verifyDrawable(drawable)) {
7688             final Rect dirty = drawable.getBounds();
7689             int scrollX = mScrollX;
7690             int scrollY = mScrollY;
7691 
7692             // IMPORTANT: The coordinates below are based on the coordinates computed
7693             // for each compound drawable in onDraw(). Make sure to update each section
7694             // accordingly.
7695             final TextView.Drawables drawables = mDrawables;
7696             if (drawables != null) {
7697                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
7698                     final int compoundPaddingTop = getCompoundPaddingTop();
7699                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7700                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7701 
7702                     scrollX += mPaddingLeft;
7703                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
7704                     handled = true;
7705                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
7706                     final int compoundPaddingTop = getCompoundPaddingTop();
7707                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7708                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7709 
7710                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
7711                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
7712                     handled = true;
7713                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
7714                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7715                     final int compoundPaddingRight = getCompoundPaddingRight();
7716                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7717 
7718                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
7719                     scrollY += mPaddingTop;
7720                     handled = true;
7721                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
7722                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7723                     final int compoundPaddingRight = getCompoundPaddingRight();
7724                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7725 
7726                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
7727                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
7728                     handled = true;
7729                 }
7730             }
7731 
7732             if (handled) {
7733                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
7734                         dirty.right + scrollX, dirty.bottom + scrollY);
7735             }
7736         }
7737 
7738         if (!handled) {
7739             super.invalidateDrawable(drawable);
7740         }
7741     }
7742 
7743     @Override
hasOverlappingRendering()7744     public boolean hasOverlappingRendering() {
7745         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
7746         return ((getBackground() != null && getBackground().getCurrent() != null)
7747                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
7748                 || mShadowColor != 0);
7749     }
7750 
7751     /**
7752      *
7753      * Returns the state of the {@code textIsSelectable} flag (See
7754      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
7755      * to allow users to select and copy text in a non-editable TextView, the content of an
7756      * {@link EditText} can always be selected, independently of the value of this flag.
7757      * <p>
7758      *
7759      * @return True if the text displayed in this TextView can be selected by the user.
7760      *
7761      * @attr ref android.R.styleable#TextView_textIsSelectable
7762      */
7763     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()7764     public boolean isTextSelectable() {
7765         return mEditor == null ? false : mEditor.mTextIsSelectable;
7766     }
7767 
7768     /**
7769      * Sets whether the content of this view is selectable by the user. The default is
7770      * {@code false}, meaning that the content is not selectable.
7771      * <p>
7772      * When you use a TextView to display a useful piece of information to the user (such as a
7773      * contact's address), make it selectable, so that the user can select and copy its
7774      * content. You can also use set the XML attribute
7775      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
7776      * <p>
7777      * When you call this method to set the value of {@code textIsSelectable}, it sets
7778      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
7779      * and {@code longClickable} to the same value. These flags correspond to the attributes
7780      * {@link android.R.styleable#View_focusable android:focusable},
7781      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
7782      * {@link android.R.styleable#View_clickable android:clickable}, and
7783      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
7784      * flags to a state you had set previously, call one or more of the following methods:
7785      * {@link #setFocusable(boolean) setFocusable()},
7786      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
7787      * {@link #setClickable(boolean) setClickable()} or
7788      * {@link #setLongClickable(boolean) setLongClickable()}.
7789      *
7790      * @param selectable Whether the content of this TextView should be selectable.
7791      */
setTextIsSelectable(boolean selectable)7792     public void setTextIsSelectable(boolean selectable) {
7793         if (!selectable && mEditor == null) return; // false is default value with no edit data
7794 
7795         createEditorIfNeeded();
7796         if (mEditor.mTextIsSelectable == selectable) return;
7797 
7798         mEditor.mTextIsSelectable = selectable;
7799         setFocusableInTouchMode(selectable);
7800         setFocusable(FOCUSABLE_AUTO);
7801         setClickable(selectable);
7802         setLongClickable(selectable);
7803 
7804         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
7805 
7806         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
7807         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
7808 
7809         // Called by setText above, but safer in case of future code changes
7810         mEditor.prepareCursorControllers();
7811     }
7812 
7813     @Override
onCreateDrawableState(int extraSpace)7814     protected int[] onCreateDrawableState(int extraSpace) {
7815         final int[] drawableState;
7816 
7817         if (mSingleLine) {
7818             drawableState = super.onCreateDrawableState(extraSpace);
7819         } else {
7820             drawableState = super.onCreateDrawableState(extraSpace + 1);
7821             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
7822         }
7823 
7824         if (isTextSelectable()) {
7825             // Disable pressed state, which was introduced when TextView was made clickable.
7826             // Prevents text color change.
7827             // setClickable(false) would have a similar effect, but it also disables focus changes
7828             // and long press actions, which are both needed by text selection.
7829             final int length = drawableState.length;
7830             for (int i = 0; i < length; i++) {
7831                 if (drawableState[i] == R.attr.state_pressed) {
7832                     final int[] nonPressedState = new int[length - 1];
7833                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
7834                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
7835                     return nonPressedState;
7836                 }
7837             }
7838         }
7839 
7840         return drawableState;
7841     }
7842 
7843     @UnsupportedAppUsage
getUpdatedHighlightPath()7844     private Path getUpdatedHighlightPath() {
7845         Path highlight = null;
7846         Paint highlightPaint = mHighlightPaint;
7847 
7848         final int selStart = getSelectionStart();
7849         final int selEnd = getSelectionEnd();
7850         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
7851             if (selStart == selEnd) {
7852                 if (mEditor != null && mEditor.shouldRenderCursor()) {
7853                     if (mHighlightPathBogus) {
7854                         if (mHighlightPath == null) mHighlightPath = new Path();
7855                         mHighlightPath.reset();
7856                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
7857                         mEditor.updateCursorPosition();
7858                         mHighlightPathBogus = false;
7859                     }
7860 
7861                     // XXX should pass to skin instead of drawing directly
7862                     highlightPaint.setColor(mCurTextColor);
7863                     highlightPaint.setStyle(Paint.Style.STROKE);
7864                     highlight = mHighlightPath;
7865                 }
7866             } else {
7867                 if (mHighlightPathBogus) {
7868                     if (mHighlightPath == null) mHighlightPath = new Path();
7869                     mHighlightPath.reset();
7870                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7871                     mHighlightPathBogus = false;
7872                 }
7873 
7874                 // XXX should pass to skin instead of drawing directly
7875                 highlightPaint.setColor(mHighlightColor);
7876                 highlightPaint.setStyle(Paint.Style.FILL);
7877 
7878                 highlight = mHighlightPath;
7879             }
7880         }
7881         return highlight;
7882     }
7883 
7884     /**
7885      * @hide
7886      */
getHorizontalOffsetForDrawables()7887     public int getHorizontalOffsetForDrawables() {
7888         return 0;
7889     }
7890 
7891     @Override
onDraw(Canvas canvas)7892     protected void onDraw(Canvas canvas) {
7893         restartMarqueeIfNeeded();
7894 
7895         // Draw the background for this view
7896         super.onDraw(canvas);
7897 
7898         final int compoundPaddingLeft = getCompoundPaddingLeft();
7899         final int compoundPaddingTop = getCompoundPaddingTop();
7900         final int compoundPaddingRight = getCompoundPaddingRight();
7901         final int compoundPaddingBottom = getCompoundPaddingBottom();
7902         final int scrollX = mScrollX;
7903         final int scrollY = mScrollY;
7904         final int right = mRight;
7905         final int left = mLeft;
7906         final int bottom = mBottom;
7907         final int top = mTop;
7908         final boolean isLayoutRtl = isLayoutRtl();
7909         final int offset = getHorizontalOffsetForDrawables();
7910         final int leftOffset = isLayoutRtl ? 0 : offset;
7911         final int rightOffset = isLayoutRtl ? offset : 0;
7912 
7913         final Drawables dr = mDrawables;
7914         if (dr != null) {
7915             /*
7916              * Compound, not extended, because the icon is not clipped
7917              * if the text height is smaller.
7918              */
7919 
7920             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
7921             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
7922 
7923             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7924             // Make sure to update invalidateDrawable() when changing this code.
7925             if (dr.mShowing[Drawables.LEFT] != null) {
7926                 canvas.save();
7927                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
7928                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
7929                 dr.mShowing[Drawables.LEFT].draw(canvas);
7930                 canvas.restore();
7931             }
7932 
7933             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7934             // Make sure to update invalidateDrawable() when changing this code.
7935             if (dr.mShowing[Drawables.RIGHT] != null) {
7936                 canvas.save();
7937                 canvas.translate(scrollX + right - left - mPaddingRight
7938                         - dr.mDrawableSizeRight - rightOffset,
7939                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
7940                 dr.mShowing[Drawables.RIGHT].draw(canvas);
7941                 canvas.restore();
7942             }
7943 
7944             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7945             // Make sure to update invalidateDrawable() when changing this code.
7946             if (dr.mShowing[Drawables.TOP] != null) {
7947                 canvas.save();
7948                 canvas.translate(scrollX + compoundPaddingLeft
7949                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
7950                 dr.mShowing[Drawables.TOP].draw(canvas);
7951                 canvas.restore();
7952             }
7953 
7954             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7955             // Make sure to update invalidateDrawable() when changing this code.
7956             if (dr.mShowing[Drawables.BOTTOM] != null) {
7957                 canvas.save();
7958                 canvas.translate(scrollX + compoundPaddingLeft
7959                         + (hspace - dr.mDrawableWidthBottom) / 2,
7960                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
7961                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
7962                 canvas.restore();
7963             }
7964         }
7965 
7966         int color = mCurTextColor;
7967 
7968         if (mLayout == null) {
7969             assumeLayout();
7970         }
7971 
7972         Layout layout = mLayout;
7973 
7974         if (mHint != null && mText.length() == 0) {
7975             if (mHintTextColor != null) {
7976                 color = mCurHintTextColor;
7977             }
7978 
7979             layout = mHintLayout;
7980         }
7981 
7982         mTextPaint.setColor(color);
7983         mTextPaint.drawableState = getDrawableState();
7984 
7985         canvas.save();
7986         /*  Would be faster if we didn't have to do this. Can we chop the
7987             (displayable) text so that we don't need to do this ever?
7988         */
7989 
7990         int extendedPaddingTop = getExtendedPaddingTop();
7991         int extendedPaddingBottom = getExtendedPaddingBottom();
7992 
7993         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7994         final int maxScrollY = mLayout.getHeight() - vspace;
7995 
7996         float clipLeft = compoundPaddingLeft + scrollX;
7997         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
7998         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
7999         float clipBottom = bottom - top + scrollY
8000                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
8001 
8002         if (mShadowRadius != 0) {
8003             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
8004             clipRight += Math.max(0, mShadowDx + mShadowRadius);
8005 
8006             clipTop += Math.min(0, mShadowDy - mShadowRadius);
8007             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
8008         }
8009 
8010         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
8011 
8012         int voffsetText = 0;
8013         int voffsetCursor = 0;
8014 
8015         // translate in by our padding
8016         /* shortcircuit calling getVerticaOffset() */
8017         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8018             voffsetText = getVerticalOffset(false);
8019             voffsetCursor = getVerticalOffset(true);
8020         }
8021         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
8022 
8023         final int layoutDirection = getLayoutDirection();
8024         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8025         if (isMarqueeFadeEnabled()) {
8026             if (!mSingleLine && getLineCount() == 1 && canMarquee()
8027                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
8028                 final int width = mRight - mLeft;
8029                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
8030                 final float dx = mLayout.getLineRight(0) - (width - padding);
8031                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8032             }
8033 
8034             if (mMarquee != null && mMarquee.isRunning()) {
8035                 final float dx = -mMarquee.getScroll();
8036                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8037             }
8038         }
8039 
8040         final int cursorOffsetVertical = voffsetCursor - voffsetText;
8041 
8042         Path highlight = getUpdatedHighlightPath();
8043         if (mEditor != null) {
8044             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
8045         } else {
8046             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
8047         }
8048 
8049         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
8050             final float dx = mMarquee.getGhostOffset();
8051             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8052             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
8053         }
8054 
8055         canvas.restore();
8056     }
8057 
8058     @Override
getFocusedRect(Rect r)8059     public void getFocusedRect(Rect r) {
8060         if (mLayout == null) {
8061             super.getFocusedRect(r);
8062             return;
8063         }
8064 
8065         int selEnd = getSelectionEnd();
8066         if (selEnd < 0) {
8067             super.getFocusedRect(r);
8068             return;
8069         }
8070 
8071         int selStart = getSelectionStart();
8072         if (selStart < 0 || selStart >= selEnd) {
8073             int line = mLayout.getLineForOffset(selEnd);
8074             r.top = mLayout.getLineTop(line);
8075             r.bottom = mLayout.getLineBottom(line);
8076             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
8077             r.right = r.left + 4;
8078         } else {
8079             int lineStart = mLayout.getLineForOffset(selStart);
8080             int lineEnd = mLayout.getLineForOffset(selEnd);
8081             r.top = mLayout.getLineTop(lineStart);
8082             r.bottom = mLayout.getLineBottom(lineEnd);
8083             if (lineStart == lineEnd) {
8084                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
8085                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
8086             } else {
8087                 // Selection extends across multiple lines -- make the focused
8088                 // rect cover the entire width.
8089                 if (mHighlightPathBogus) {
8090                     if (mHighlightPath == null) mHighlightPath = new Path();
8091                     mHighlightPath.reset();
8092                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8093                     mHighlightPathBogus = false;
8094                 }
8095                 synchronized (TEMP_RECTF) {
8096                     mHighlightPath.computeBounds(TEMP_RECTF, true);
8097                     r.left = (int) TEMP_RECTF.left - 1;
8098                     r.right = (int) TEMP_RECTF.right + 1;
8099                 }
8100             }
8101         }
8102 
8103         // Adjust for padding and gravity.
8104         int paddingLeft = getCompoundPaddingLeft();
8105         int paddingTop = getExtendedPaddingTop();
8106         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8107             paddingTop += getVerticalOffset(false);
8108         }
8109         r.offset(paddingLeft, paddingTop);
8110         int paddingBottom = getExtendedPaddingBottom();
8111         r.bottom += paddingBottom;
8112     }
8113 
8114     /**
8115      * Return the number of lines of text, or 0 if the internal Layout has not
8116      * been built.
8117      */
getLineCount()8118     public int getLineCount() {
8119         return mLayout != null ? mLayout.getLineCount() : 0;
8120     }
8121 
8122     /**
8123      * Return the baseline for the specified line (0...getLineCount() - 1)
8124      * If bounds is not null, return the top, left, right, bottom extents
8125      * of the specified line in it. If the internal Layout has not been built,
8126      * return 0 and set bounds to (0, 0, 0, 0)
8127      * @param line which line to examine (0..getLineCount() - 1)
8128      * @param bounds Optional. If not null, it returns the extent of the line
8129      * @return the Y-coordinate of the baseline
8130      */
getLineBounds(int line, Rect bounds)8131     public int getLineBounds(int line, Rect bounds) {
8132         if (mLayout == null) {
8133             if (bounds != null) {
8134                 bounds.set(0, 0, 0, 0);
8135             }
8136             return 0;
8137         } else {
8138             int baseline = mLayout.getLineBounds(line, bounds);
8139 
8140             int voffset = getExtendedPaddingTop();
8141             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8142                 voffset += getVerticalOffset(true);
8143             }
8144             if (bounds != null) {
8145                 bounds.offset(getCompoundPaddingLeft(), voffset);
8146             }
8147             return baseline + voffset;
8148         }
8149     }
8150 
8151     @Override
getBaseline()8152     public int getBaseline() {
8153         if (mLayout == null) {
8154             return super.getBaseline();
8155         }
8156 
8157         return getBaselineOffset() + mLayout.getLineBaseline(0);
8158     }
8159 
getBaselineOffset()8160     int getBaselineOffset() {
8161         int voffset = 0;
8162         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8163             voffset = getVerticalOffset(true);
8164         }
8165 
8166         if (isLayoutModeOptical(mParent)) {
8167             voffset -= getOpticalInsets().top;
8168         }
8169 
8170         return getExtendedPaddingTop() + voffset;
8171     }
8172 
8173     /**
8174      * @hide
8175      */
8176     @Override
getFadeTop(boolean offsetRequired)8177     protected int getFadeTop(boolean offsetRequired) {
8178         if (mLayout == null) return 0;
8179 
8180         int voffset = 0;
8181         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8182             voffset = getVerticalOffset(true);
8183         }
8184 
8185         if (offsetRequired) voffset += getTopPaddingOffset();
8186 
8187         return getExtendedPaddingTop() + voffset;
8188     }
8189 
8190     /**
8191      * @hide
8192      */
8193     @Override
getFadeHeight(boolean offsetRequired)8194     protected int getFadeHeight(boolean offsetRequired) {
8195         return mLayout != null ? mLayout.getHeight() : 0;
8196     }
8197 
8198     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)8199     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
8200         if (mSpannable != null && mLinksClickable) {
8201             final float x = event.getX(pointerIndex);
8202             final float y = event.getY(pointerIndex);
8203             final int offset = getOffsetForPosition(x, y);
8204             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
8205                     ClickableSpan.class);
8206             if (clickables.length > 0) {
8207                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
8208             }
8209         }
8210         if (isTextSelectable() || isTextEditable()) {
8211             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
8212         }
8213         return super.onResolvePointerIcon(event, pointerIndex);
8214     }
8215 
8216     @Override
onKeyPreIme(int keyCode, KeyEvent event)8217     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
8218         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
8219         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
8220         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
8221         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
8222             return true;
8223         }
8224         return super.onKeyPreIme(keyCode, event);
8225     }
8226 
8227     /**
8228      * @hide
8229      */
handleBackInTextActionModeIfNeeded(KeyEvent event)8230     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
8231         // Do nothing unless mEditor is in text action mode.
8232         if (mEditor == null || mEditor.getTextActionMode() == null) {
8233             return false;
8234         }
8235 
8236         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
8237             KeyEvent.DispatcherState state = getKeyDispatcherState();
8238             if (state != null) {
8239                 state.startTracking(event, this);
8240             }
8241             return true;
8242         } else if (event.getAction() == KeyEvent.ACTION_UP) {
8243             KeyEvent.DispatcherState state = getKeyDispatcherState();
8244             if (state != null) {
8245                 state.handleUpEvent(event);
8246             }
8247             if (event.isTracking() && !event.isCanceled()) {
8248                 stopTextActionMode();
8249                 return true;
8250             }
8251         }
8252         return false;
8253     }
8254 
8255     @Override
onKeyDown(int keyCode, KeyEvent event)8256     public boolean onKeyDown(int keyCode, KeyEvent event) {
8257         final int which = doKeyDown(keyCode, event, null);
8258         if (which == KEY_EVENT_NOT_HANDLED) {
8259             return super.onKeyDown(keyCode, event);
8260         }
8261 
8262         return true;
8263     }
8264 
8265     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8266     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
8267         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
8268         final int which = doKeyDown(keyCode, down, event);
8269         if (which == KEY_EVENT_NOT_HANDLED) {
8270             // Go through default dispatching.
8271             return super.onKeyMultiple(keyCode, repeatCount, event);
8272         }
8273         if (which == KEY_EVENT_HANDLED) {
8274             // Consumed the whole thing.
8275             return true;
8276         }
8277 
8278         repeatCount--;
8279 
8280         // We are going to dispatch the remaining events to either the input
8281         // or movement method.  To do this, we will just send a repeated stream
8282         // of down and up events until we have done the complete repeatCount.
8283         // It would be nice if those interfaces had an onKeyMultiple() method,
8284         // but adding that is a more complicated change.
8285         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
8286         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
8287             // mEditor and mEditor.mInput are not null from doKeyDown
8288             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8289             while (--repeatCount > 0) {
8290                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
8291                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8292             }
8293             hideErrorIfUnchanged();
8294 
8295         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
8296             // mMovement is not null from doKeyDown
8297             mMovement.onKeyUp(this, mSpannable, keyCode, up);
8298             while (--repeatCount > 0) {
8299                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
8300                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
8301             }
8302         }
8303 
8304         return true;
8305     }
8306 
8307     /**
8308      * Returns true if pressing ENTER in this field advances focus instead
8309      * of inserting the character.  This is true mostly in single-line fields,
8310      * but also in mail addresses and subjects which will display on multiple
8311      * lines but where it doesn't make sense to insert newlines.
8312      */
shouldAdvanceFocusOnEnter()8313     private boolean shouldAdvanceFocusOnEnter() {
8314         if (getKeyListener() == null) {
8315             return false;
8316         }
8317 
8318         if (mSingleLine) {
8319             return true;
8320         }
8321 
8322         if (mEditor != null
8323                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8324                         == EditorInfo.TYPE_CLASS_TEXT) {
8325             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8326             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
8327                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
8328                 return true;
8329             }
8330         }
8331 
8332         return false;
8333     }
8334 
isDirectionalNavigationKey(int keyCode)8335     private boolean isDirectionalNavigationKey(int keyCode) {
8336         switch(keyCode) {
8337             case KeyEvent.KEYCODE_DPAD_UP:
8338             case KeyEvent.KEYCODE_DPAD_DOWN:
8339             case KeyEvent.KEYCODE_DPAD_LEFT:
8340             case KeyEvent.KEYCODE_DPAD_RIGHT:
8341                 return true;
8342         }
8343         return false;
8344     }
8345 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8346     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
8347         if (!isEnabled()) {
8348             return KEY_EVENT_NOT_HANDLED;
8349         }
8350 
8351         // If this is the initial keydown, we don't want to prevent a movement away from this view.
8352         // While this shouldn't be necessary because any time we're preventing default movement we
8353         // should be restricting the focus to remain within this view, thus we'll also receive
8354         // the key up event, occasionally key up events will get dropped and we don't want to
8355         // prevent the user from traversing out of this on the next key down.
8356         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8357             mPreventDefaultMovement = false;
8358         }
8359 
8360         switch (keyCode) {
8361             case KeyEvent.KEYCODE_ENTER:
8362             case KeyEvent.KEYCODE_NUMPAD_ENTER:
8363                 if (event.hasNoModifiers()) {
8364                     // When mInputContentType is set, we know that we are
8365                     // running in a "modern" cupcake environment, so don't need
8366                     // to worry about the application trying to capture
8367                     // enter key events.
8368                     if (mEditor != null && mEditor.mInputContentType != null) {
8369                         // If there is an action listener, given them a
8370                         // chance to consume the event.
8371                         if (mEditor.mInputContentType.onEditorActionListener != null
8372                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8373                                         this, EditorInfo.IME_NULL, event)) {
8374                             mEditor.mInputContentType.enterDown = true;
8375                             // We are consuming the enter key for them.
8376                             return KEY_EVENT_HANDLED;
8377                         }
8378                     }
8379 
8380                     // If our editor should move focus when enter is pressed, or
8381                     // this is a generated event from an IME action button, then
8382                     // don't let it be inserted into the text.
8383                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8384                             || shouldAdvanceFocusOnEnter()) {
8385                         if (hasOnClickListeners()) {
8386                             return KEY_EVENT_NOT_HANDLED;
8387                         }
8388                         return KEY_EVENT_HANDLED;
8389                     }
8390                 }
8391                 break;
8392 
8393             case KeyEvent.KEYCODE_DPAD_CENTER:
8394                 if (event.hasNoModifiers()) {
8395                     if (shouldAdvanceFocusOnEnter()) {
8396                         return KEY_EVENT_NOT_HANDLED;
8397                     }
8398                 }
8399                 break;
8400 
8401             case KeyEvent.KEYCODE_TAB:
8402                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
8403                     // Tab is used to move focus.
8404                     return KEY_EVENT_NOT_HANDLED;
8405                 }
8406                 break;
8407 
8408                 // Has to be done on key down (and not on key up) to correctly be intercepted.
8409             case KeyEvent.KEYCODE_BACK:
8410                 if (mEditor != null && mEditor.getTextActionMode() != null) {
8411                     stopTextActionMode();
8412                     return KEY_EVENT_HANDLED;
8413                 }
8414                 break;
8415 
8416             case KeyEvent.KEYCODE_CUT:
8417                 if (event.hasNoModifiers() && canCut()) {
8418                     if (onTextContextMenuItem(ID_CUT)) {
8419                         return KEY_EVENT_HANDLED;
8420                     }
8421                 }
8422                 break;
8423 
8424             case KeyEvent.KEYCODE_COPY:
8425                 if (event.hasNoModifiers() && canCopy()) {
8426                     if (onTextContextMenuItem(ID_COPY)) {
8427                         return KEY_EVENT_HANDLED;
8428                     }
8429                 }
8430                 break;
8431 
8432             case KeyEvent.KEYCODE_PASTE:
8433                 if (event.hasNoModifiers() && canPaste()) {
8434                     if (onTextContextMenuItem(ID_PASTE)) {
8435                         return KEY_EVENT_HANDLED;
8436                     }
8437                 }
8438                 break;
8439 
8440             case KeyEvent.KEYCODE_FORWARD_DEL:
8441                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
8442                     if (onTextContextMenuItem(ID_CUT)) {
8443                         return KEY_EVENT_HANDLED;
8444                     }
8445                 }
8446                 break;
8447 
8448             case KeyEvent.KEYCODE_INSERT:
8449                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
8450                     if (onTextContextMenuItem(ID_COPY)) {
8451                         return KEY_EVENT_HANDLED;
8452                     }
8453                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
8454                     if (onTextContextMenuItem(ID_PASTE)) {
8455                         return KEY_EVENT_HANDLED;
8456                     }
8457                 }
8458                 break;
8459         }
8460 
8461         if (mEditor != null && mEditor.mKeyListener != null) {
8462             boolean doDown = true;
8463             if (otherEvent != null) {
8464                 try {
8465                     beginBatchEdit();
8466                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
8467                             otherEvent);
8468                     hideErrorIfUnchanged();
8469                     doDown = false;
8470                     if (handled) {
8471                         return KEY_EVENT_HANDLED;
8472                     }
8473                 } catch (AbstractMethodError e) {
8474                     // onKeyOther was added after 1.0, so if it isn't
8475                     // implemented we need to try to dispatch as a regular down.
8476                 } finally {
8477                     endBatchEdit();
8478                 }
8479             }
8480 
8481             if (doDown) {
8482                 beginBatchEdit();
8483                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
8484                         keyCode, event);
8485                 endBatchEdit();
8486                 hideErrorIfUnchanged();
8487                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
8488             }
8489         }
8490 
8491         // bug 650865: sometimes we get a key event before a layout.
8492         // don't try to move around if we don't know the layout.
8493 
8494         if (mMovement != null && mLayout != null) {
8495             boolean doDown = true;
8496             if (otherEvent != null) {
8497                 try {
8498                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
8499                     doDown = false;
8500                     if (handled) {
8501                         return KEY_EVENT_HANDLED;
8502                     }
8503                 } catch (AbstractMethodError e) {
8504                     // onKeyOther was added after 1.0, so if it isn't
8505                     // implemented we need to try to dispatch as a regular down.
8506                 }
8507             }
8508             if (doDown) {
8509                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
8510                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8511                         mPreventDefaultMovement = true;
8512                     }
8513                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
8514                 }
8515             }
8516             // Consume arrows from keyboard devices to prevent focus leaving the editor.
8517             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
8518             // to move focus with arrows.
8519             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
8520                     && isDirectionalNavigationKey(keyCode)) {
8521                 return KEY_EVENT_HANDLED;
8522             }
8523         }
8524 
8525         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
8526                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
8527     }
8528 
8529     /**
8530      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
8531      * can be recorded.
8532      * @hide
8533      */
resetErrorChangedFlag()8534     public void resetErrorChangedFlag() {
8535         /*
8536          * Keep track of what the error was before doing the input
8537          * so that if an input filter changed the error, we leave
8538          * that error showing.  Otherwise, we take down whatever
8539          * error was showing when the user types something.
8540          */
8541         if (mEditor != null) mEditor.mErrorWasChanged = false;
8542     }
8543 
8544     /**
8545      * @hide
8546      */
hideErrorIfUnchanged()8547     public void hideErrorIfUnchanged() {
8548         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
8549             setError(null, null);
8550         }
8551     }
8552 
8553     @Override
onKeyUp(int keyCode, KeyEvent event)8554     public boolean onKeyUp(int keyCode, KeyEvent event) {
8555         if (!isEnabled()) {
8556             return super.onKeyUp(keyCode, event);
8557         }
8558 
8559         if (!KeyEvent.isModifierKey(keyCode)) {
8560             mPreventDefaultMovement = false;
8561         }
8562 
8563         switch (keyCode) {
8564             case KeyEvent.KEYCODE_DPAD_CENTER:
8565                 if (event.hasNoModifiers()) {
8566                     /*
8567                      * If there is a click listener, just call through to
8568                      * super, which will invoke it.
8569                      *
8570                      * If there isn't a click listener, try to show the soft
8571                      * input method.  (It will also
8572                      * call performClick(), but that won't do anything in
8573                      * this case.)
8574                      */
8575                     if (!hasOnClickListeners()) {
8576                         if (mMovement != null && mText instanceof Editable
8577                                 && mLayout != null && onCheckIsTextEditor()) {
8578                             InputMethodManager imm = getInputMethodManager();
8579                             viewClicked(imm);
8580                             if (imm != null && getShowSoftInputOnFocus()) {
8581                                 imm.showSoftInput(this, 0);
8582                             }
8583                         }
8584                     }
8585                 }
8586                 return super.onKeyUp(keyCode, event);
8587 
8588             case KeyEvent.KEYCODE_ENTER:
8589             case KeyEvent.KEYCODE_NUMPAD_ENTER:
8590                 if (event.hasNoModifiers()) {
8591                     if (mEditor != null && mEditor.mInputContentType != null
8592                             && mEditor.mInputContentType.onEditorActionListener != null
8593                             && mEditor.mInputContentType.enterDown) {
8594                         mEditor.mInputContentType.enterDown = false;
8595                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8596                                 this, EditorInfo.IME_NULL, event)) {
8597                             return true;
8598                         }
8599                     }
8600 
8601                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8602                             || shouldAdvanceFocusOnEnter()) {
8603                         /*
8604                          * If there is a click listener, just call through to
8605                          * super, which will invoke it.
8606                          *
8607                          * If there isn't a click listener, try to advance focus,
8608                          * but still call through to super, which will reset the
8609                          * pressed state and longpress state.  (It will also
8610                          * call performClick(), but that won't do anything in
8611                          * this case.)
8612                          */
8613                         if (!hasOnClickListeners()) {
8614                             View v = focusSearch(FOCUS_DOWN);
8615 
8616                             if (v != null) {
8617                                 if (!v.requestFocus(FOCUS_DOWN)) {
8618                                     throw new IllegalStateException("focus search returned a view "
8619                                             + "that wasn't able to take focus!");
8620                                 }
8621 
8622                                 /*
8623                                  * Return true because we handled the key; super
8624                                  * will return false because there was no click
8625                                  * listener.
8626                                  */
8627                                 super.onKeyUp(keyCode, event);
8628                                 return true;
8629                             } else if ((event.getFlags()
8630                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
8631                                 // No target for next focus, but make sure the IME
8632                                 // if this came from it.
8633                                 InputMethodManager imm = getInputMethodManager();
8634                                 if (imm != null && imm.isActive(this)) {
8635                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
8636                                 }
8637                             }
8638                         }
8639                     }
8640                     return super.onKeyUp(keyCode, event);
8641                 }
8642                 break;
8643         }
8644 
8645         if (mEditor != null && mEditor.mKeyListener != null) {
8646             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
8647                 return true;
8648             }
8649         }
8650 
8651         if (mMovement != null && mLayout != null) {
8652             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
8653                 return true;
8654             }
8655         }
8656 
8657         return super.onKeyUp(keyCode, event);
8658     }
8659 
8660     @Override
onCheckIsTextEditor()8661     public boolean onCheckIsTextEditor() {
8662         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
8663     }
8664 
8665     @Override
onCreateInputConnection(EditorInfo outAttrs)8666     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
8667         if (onCheckIsTextEditor() && isEnabled()) {
8668             mEditor.createInputMethodStateIfNeeded();
8669             outAttrs.inputType = getInputType();
8670             if (mEditor.mInputContentType != null) {
8671                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
8672                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
8673                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
8674                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
8675                 outAttrs.extras = mEditor.mInputContentType.extras;
8676                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
8677             } else {
8678                 outAttrs.imeOptions = EditorInfo.IME_NULL;
8679                 outAttrs.hintLocales = null;
8680             }
8681             if (focusSearch(FOCUS_DOWN) != null) {
8682                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
8683             }
8684             if (focusSearch(FOCUS_UP) != null) {
8685                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
8686             }
8687             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
8688                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
8689                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
8690                     // An action has not been set, but the enter key will move to
8691                     // the next focus, so set the action to that.
8692                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
8693                 } else {
8694                     // An action has not been set, and there is no focus to move
8695                     // to, so let's just supply a "done" action.
8696                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
8697                 }
8698                 if (!shouldAdvanceFocusOnEnter()) {
8699                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8700                 }
8701             }
8702             if (isMultilineInputType(outAttrs.inputType)) {
8703                 // Multi-line text editors should always show an enter key.
8704                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8705             }
8706             outAttrs.hintText = mHint;
8707             outAttrs.targetInputMethodUser = mTextOperationUser;
8708             if (mText instanceof Editable) {
8709                 InputConnection ic = new EditableInputConnection(this);
8710                 outAttrs.initialSelStart = getSelectionStart();
8711                 outAttrs.initialSelEnd = getSelectionEnd();
8712                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
8713                 outAttrs.setInitialSurroundingText(mText);
8714                 return ic;
8715             }
8716         }
8717         return null;
8718     }
8719 
8720     /**
8721      * If this TextView contains editable content, extract a portion of it
8722      * based on the information in <var>request</var> in to <var>outText</var>.
8723      * @return Returns true if the text was successfully extracted, else false.
8724      */
extractText(ExtractedTextRequest request, ExtractedText outText)8725     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
8726         createEditorIfNeeded();
8727         return mEditor.extractText(request, outText);
8728     }
8729 
8730     /**
8731      * This is used to remove all style-impacting spans from text before new
8732      * extracted text is being replaced into it, so that we don't have any
8733      * lingering spans applied during the replace.
8734      */
removeParcelableSpans(Spannable spannable, int start, int end)8735     static void removeParcelableSpans(Spannable spannable, int start, int end) {
8736         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
8737         int i = spans.length;
8738         while (i > 0) {
8739             i--;
8740             spannable.removeSpan(spans[i]);
8741         }
8742     }
8743 
8744     /**
8745      * Apply to this text view the given extracted text, as previously
8746      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
8747      */
setExtractedText(ExtractedText text)8748     public void setExtractedText(ExtractedText text) {
8749         Editable content = getEditableText();
8750         if (text.text != null) {
8751             if (content == null) {
8752                 setText(text.text, TextView.BufferType.EDITABLE);
8753             } else {
8754                 int start = 0;
8755                 int end = content.length();
8756 
8757                 if (text.partialStartOffset >= 0) {
8758                     final int N = content.length();
8759                     start = text.partialStartOffset;
8760                     if (start > N) start = N;
8761                     end = text.partialEndOffset;
8762                     if (end > N) end = N;
8763                 }
8764 
8765                 removeParcelableSpans(content, start, end);
8766                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
8767                     if (text.text instanceof Spanned) {
8768                         // OK to copy spans only.
8769                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
8770                                 Object.class, content, start);
8771                     }
8772                 } else {
8773                     content.replace(start, end, text.text);
8774                 }
8775             }
8776         }
8777 
8778         // Now set the selection position...  make sure it is in range, to
8779         // avoid crashes.  If this is a partial update, it is possible that
8780         // the underlying text may have changed, causing us problems here.
8781         // Also we just don't want to trust clients to do the right thing.
8782         Spannable sp = (Spannable) getText();
8783         final int N = sp.length();
8784         int start = text.selectionStart;
8785         if (start < 0) {
8786             start = 0;
8787         } else if (start > N) {
8788             start = N;
8789         }
8790         int end = text.selectionEnd;
8791         if (end < 0) {
8792             end = 0;
8793         } else if (end > N) {
8794             end = N;
8795         }
8796         Selection.setSelection(sp, start, end);
8797 
8798         // Finally, update the selection mode.
8799         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
8800             MetaKeyKeyListener.startSelecting(this, sp);
8801         } else {
8802             MetaKeyKeyListener.stopSelecting(this, sp);
8803         }
8804 
8805         setHintInternal(text.hint);
8806     }
8807 
8808     /**
8809      * @hide
8810      */
setExtracting(ExtractedTextRequest req)8811     public void setExtracting(ExtractedTextRequest req) {
8812         if (mEditor.mInputMethodState != null) {
8813             mEditor.mInputMethodState.mExtractedTextRequest = req;
8814         }
8815         // This would stop a possible selection mode, but no such mode is started in case
8816         // extracted mode will start. Some text is selected though, and will trigger an action mode
8817         // in the extracted view.
8818         mEditor.hideCursorAndSpanControllers();
8819         stopTextActionMode();
8820         if (mEditor.mSelectionModifierCursorController != null) {
8821             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
8822         }
8823     }
8824 
8825     /**
8826      * Called by the framework in response to a text completion from
8827      * the current input method, provided by it calling
8828      * {@link InputConnection#commitCompletion
8829      * InputConnection.commitCompletion()}.  The default implementation does
8830      * nothing; text views that are supporting auto-completion should override
8831      * this to do their desired behavior.
8832      *
8833      * @param text The auto complete text the user has selected.
8834      */
onCommitCompletion(CompletionInfo text)8835     public void onCommitCompletion(CompletionInfo text) {
8836         // intentionally empty
8837     }
8838 
8839     /**
8840      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
8841      * dictionary) from the current input method, provided by it calling
8842      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
8843      * The default implementation flashes the background of the corrected word to provide
8844      * feedback to the user.
8845      *
8846      * @param info The auto correct info about the text that was corrected.
8847      */
onCommitCorrection(CorrectionInfo info)8848     public void onCommitCorrection(CorrectionInfo info) {
8849         if (mEditor != null) mEditor.onCommitCorrection(info);
8850     }
8851 
beginBatchEdit()8852     public void beginBatchEdit() {
8853         if (mEditor != null) mEditor.beginBatchEdit();
8854     }
8855 
endBatchEdit()8856     public void endBatchEdit() {
8857         if (mEditor != null) mEditor.endBatchEdit();
8858     }
8859 
8860     /**
8861      * Called by the framework in response to a request to begin a batch
8862      * of edit operations through a call to link {@link #beginBatchEdit()}.
8863      */
onBeginBatchEdit()8864     public void onBeginBatchEdit() {
8865         // intentionally empty
8866     }
8867 
8868     /**
8869      * Called by the framework in response to a request to end a batch
8870      * of edit operations through a call to link {@link #endBatchEdit}.
8871      */
onEndBatchEdit()8872     public void onEndBatchEdit() {
8873         // intentionally empty
8874     }
8875 
8876     /**
8877      * Called by the framework in response to a private command from the
8878      * current method, provided by it calling
8879      * {@link InputConnection#performPrivateCommand
8880      * InputConnection.performPrivateCommand()}.
8881      *
8882      * @param action The action name of the command.
8883      * @param data Any additional data for the command.  This may be null.
8884      * @return Return true if you handled the command, else false.
8885      */
onPrivateIMECommand(String action, Bundle data)8886     public boolean onPrivateIMECommand(String action, Bundle data) {
8887         return false;
8888     }
8889 
8890     /** @hide */
8891     @VisibleForTesting
8892     @UnsupportedAppUsage
nullLayouts()8893     public void nullLayouts() {
8894         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
8895             mSavedLayout = (BoringLayout) mLayout;
8896         }
8897         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
8898             mSavedHintLayout = (BoringLayout) mHintLayout;
8899         }
8900 
8901         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
8902 
8903         mBoring = mHintBoring = null;
8904 
8905         // Since it depends on the value of mLayout
8906         if (mEditor != null) mEditor.prepareCursorControllers();
8907     }
8908 
8909     /**
8910      * Make a new Layout based on the already-measured size of the view,
8911      * on the assumption that it was measured correctly at some point.
8912      */
8913     @UnsupportedAppUsage
assumeLayout()8914     private void assumeLayout() {
8915         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8916 
8917         if (width < 1) {
8918             width = 0;
8919         }
8920 
8921         int physicalWidth = width;
8922 
8923         if (mHorizontallyScrolling) {
8924             width = VERY_WIDE;
8925         }
8926 
8927         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
8928                       physicalWidth, false);
8929     }
8930 
8931     @UnsupportedAppUsage
getLayoutAlignment()8932     private Layout.Alignment getLayoutAlignment() {
8933         Layout.Alignment alignment;
8934         switch (getTextAlignment()) {
8935             case TEXT_ALIGNMENT_GRAVITY:
8936                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
8937                     case Gravity.START:
8938                         alignment = Layout.Alignment.ALIGN_NORMAL;
8939                         break;
8940                     case Gravity.END:
8941                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
8942                         break;
8943                     case Gravity.LEFT:
8944                         alignment = Layout.Alignment.ALIGN_LEFT;
8945                         break;
8946                     case Gravity.RIGHT:
8947                         alignment = Layout.Alignment.ALIGN_RIGHT;
8948                         break;
8949                     case Gravity.CENTER_HORIZONTAL:
8950                         alignment = Layout.Alignment.ALIGN_CENTER;
8951                         break;
8952                     default:
8953                         alignment = Layout.Alignment.ALIGN_NORMAL;
8954                         break;
8955                 }
8956                 break;
8957             case TEXT_ALIGNMENT_TEXT_START:
8958                 alignment = Layout.Alignment.ALIGN_NORMAL;
8959                 break;
8960             case TEXT_ALIGNMENT_TEXT_END:
8961                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
8962                 break;
8963             case TEXT_ALIGNMENT_CENTER:
8964                 alignment = Layout.Alignment.ALIGN_CENTER;
8965                 break;
8966             case TEXT_ALIGNMENT_VIEW_START:
8967                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8968                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8969                 break;
8970             case TEXT_ALIGNMENT_VIEW_END:
8971                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8972                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8973                 break;
8974             case TEXT_ALIGNMENT_INHERIT:
8975                 // This should never happen as we have already resolved the text alignment
8976                 // but better safe than sorry so we just fall through
8977             default:
8978                 alignment = Layout.Alignment.ALIGN_NORMAL;
8979                 break;
8980         }
8981         return alignment;
8982     }
8983 
8984     /**
8985      * The width passed in is now the desired layout width,
8986      * not the full view width with padding.
8987      * {@hide}
8988      */
8989     @VisibleForTesting
8990     @UnsupportedAppUsage
makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8991     public void makeNewLayout(int wantWidth, int hintWidth,
8992                                  BoringLayout.Metrics boring,
8993                                  BoringLayout.Metrics hintBoring,
8994                                  int ellipsisWidth, boolean bringIntoView) {
8995         stopMarquee();
8996 
8997         // Update "old" cached values
8998         mOldMaximum = mMaximum;
8999         mOldMaxMode = mMaxMode;
9000 
9001         mHighlightPathBogus = true;
9002 
9003         if (wantWidth < 0) {
9004             wantWidth = 0;
9005         }
9006         if (hintWidth < 0) {
9007             hintWidth = 0;
9008         }
9009 
9010         Layout.Alignment alignment = getLayoutAlignment();
9011         final boolean testDirChange = mSingleLine && mLayout != null
9012                 && (alignment == Layout.Alignment.ALIGN_NORMAL
9013                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
9014         int oldDir = 0;
9015         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
9016         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
9017         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
9018                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
9019         TruncateAt effectiveEllipsize = mEllipsize;
9020         if (mEllipsize == TruncateAt.MARQUEE
9021                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
9022             effectiveEllipsize = TruncateAt.END_SMALL;
9023         }
9024 
9025         if (mTextDir == null) {
9026             mTextDir = getTextDirectionHeuristic();
9027         }
9028 
9029         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
9030                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
9031         if (switchEllipsize) {
9032             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
9033                     ? TruncateAt.END : TruncateAt.MARQUEE;
9034             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
9035                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
9036         }
9037 
9038         shouldEllipsize = mEllipsize != null;
9039         mHintLayout = null;
9040 
9041         if (mHint != null) {
9042             if (shouldEllipsize) hintWidth = wantWidth;
9043 
9044             if (hintBoring == UNKNOWN_BORING) {
9045                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
9046                                                    mHintBoring);
9047                 if (hintBoring != null) {
9048                     mHintBoring = hintBoring;
9049                 }
9050             }
9051 
9052             if (hintBoring != null) {
9053                 if (hintBoring.width <= hintWidth
9054                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
9055                     if (mSavedHintLayout != null) {
9056                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9057                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9058                                 hintBoring, mIncludePad);
9059                     } else {
9060                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9061                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9062                                 hintBoring, mIncludePad);
9063                     }
9064 
9065                     mSavedHintLayout = (BoringLayout) mHintLayout;
9066                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
9067                     if (mSavedHintLayout != null) {
9068                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9069                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9070                                 hintBoring, mIncludePad, mEllipsize,
9071                                 ellipsisWidth);
9072                     } else {
9073                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9074                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9075                                 hintBoring, mIncludePad, mEllipsize,
9076                                 ellipsisWidth);
9077                     }
9078                 }
9079             }
9080             // TODO: code duplication with makeSingleLayout()
9081             if (mHintLayout == null) {
9082                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
9083                         mHint.length(), mTextPaint, hintWidth)
9084                         .setAlignment(alignment)
9085                         .setTextDirection(mTextDir)
9086                         .setLineSpacing(mSpacingAdd, mSpacingMult)
9087                         .setIncludePad(mIncludePad)
9088                         .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9089                         .setBreakStrategy(mBreakStrategy)
9090                         .setHyphenationFrequency(mHyphenationFrequency)
9091                         .setJustificationMode(mJustificationMode)
9092                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9093                 if (shouldEllipsize) {
9094                     builder.setEllipsize(mEllipsize)
9095                             .setEllipsizedWidth(ellipsisWidth);
9096                 }
9097                 mHintLayout = builder.build();
9098             }
9099         }
9100 
9101         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
9102             registerForPreDraw();
9103         }
9104 
9105         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9106             if (!compressText(ellipsisWidth)) {
9107                 final int height = mLayoutParams.height;
9108                 // If the size of the view does not depend on the size of the text, try to
9109                 // start the marquee immediately
9110                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
9111                     startMarquee();
9112                 } else {
9113                     // Defer the start of the marquee until we know our width (see setFrame())
9114                     mRestartMarquee = true;
9115                 }
9116             }
9117         }
9118 
9119         // CursorControllers need a non-null mLayout
9120         if (mEditor != null) mEditor.prepareCursorControllers();
9121     }
9122 
9123     /**
9124      * Returns true if DynamicLayout is required
9125      *
9126      * @hide
9127      */
9128     @VisibleForTesting
useDynamicLayout()9129     public boolean useDynamicLayout() {
9130         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
9131     }
9132 
9133     /**
9134      * @hide
9135      */
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9136     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
9137             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
9138             boolean useSaved) {
9139         Layout result = null;
9140         if (useDynamicLayout()) {
9141             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
9142                     wantWidth)
9143                     .setDisplayText(mTransformed)
9144                     .setAlignment(alignment)
9145                     .setTextDirection(mTextDir)
9146                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9147                     .setIncludePad(mIncludePad)
9148                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9149                     .setBreakStrategy(mBreakStrategy)
9150                     .setHyphenationFrequency(mHyphenationFrequency)
9151                     .setJustificationMode(mJustificationMode)
9152                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
9153                     .setEllipsizedWidth(ellipsisWidth);
9154             result = builder.build();
9155         } else {
9156             if (boring == UNKNOWN_BORING) {
9157                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9158                 if (boring != null) {
9159                     mBoring = boring;
9160                 }
9161             }
9162 
9163             if (boring != null) {
9164                 if (boring.width <= wantWidth
9165                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
9166                     if (useSaved && mSavedLayout != null) {
9167                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9168                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9169                                 boring, mIncludePad);
9170                     } else {
9171                         result = BoringLayout.make(mTransformed, mTextPaint,
9172                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9173                                 boring, mIncludePad);
9174                     }
9175 
9176                     if (useSaved) {
9177                         mSavedLayout = (BoringLayout) result;
9178                     }
9179                 } else if (shouldEllipsize && boring.width <= wantWidth) {
9180                     if (useSaved && mSavedLayout != null) {
9181                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9182                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9183                                 boring, mIncludePad, effectiveEllipsize,
9184                                 ellipsisWidth);
9185                     } else {
9186                         result = BoringLayout.make(mTransformed, mTextPaint,
9187                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9188                                 boring, mIncludePad, effectiveEllipsize,
9189                                 ellipsisWidth);
9190                     }
9191                 }
9192             }
9193         }
9194         if (result == null) {
9195             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
9196                     0, mTransformed.length(), mTextPaint, wantWidth)
9197                     .setAlignment(alignment)
9198                     .setTextDirection(mTextDir)
9199                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9200                     .setIncludePad(mIncludePad)
9201                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9202                     .setBreakStrategy(mBreakStrategy)
9203                     .setHyphenationFrequency(mHyphenationFrequency)
9204                     .setJustificationMode(mJustificationMode)
9205                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9206             if (shouldEllipsize) {
9207                 builder.setEllipsize(effectiveEllipsize)
9208                         .setEllipsizedWidth(ellipsisWidth);
9209             }
9210             result = builder.build();
9211         }
9212         return result;
9213     }
9214 
9215     @UnsupportedAppUsage
compressText(float width)9216     private boolean compressText(float width) {
9217         if (isHardwareAccelerated()) return false;
9218 
9219         // Only compress the text if it hasn't been compressed by the previous pass
9220         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
9221                 && mTextPaint.getTextScaleX() == 1.0f) {
9222             final float textWidth = mLayout.getLineWidth(0);
9223             final float overflow = (textWidth + 1.0f - width) / width;
9224             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
9225                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
9226                 post(new Runnable() {
9227                     public void run() {
9228                         requestLayout();
9229                     }
9230                 });
9231                 return true;
9232             }
9233         }
9234 
9235         return false;
9236     }
9237 
desired(Layout layout)9238     private static int desired(Layout layout) {
9239         int n = layout.getLineCount();
9240         CharSequence text = layout.getText();
9241         float max = 0;
9242 
9243         // if any line was wrapped, we can't use it.
9244         // but it's ok for the last line not to have a newline
9245 
9246         for (int i = 0; i < n - 1; i++) {
9247             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
9248                 return -1;
9249             }
9250         }
9251 
9252         for (int i = 0; i < n; i++) {
9253             max = Math.max(max, layout.getLineWidth(i));
9254         }
9255 
9256         return (int) Math.ceil(max);
9257     }
9258 
9259     /**
9260      * Set whether the TextView includes extra top and bottom padding to make
9261      * room for accents that go above the normal ascent and descent.
9262      * The default is true.
9263      *
9264      * @see #getIncludeFontPadding()
9265      *
9266      * @attr ref android.R.styleable#TextView_includeFontPadding
9267      */
setIncludeFontPadding(boolean includepad)9268     public void setIncludeFontPadding(boolean includepad) {
9269         if (mIncludePad != includepad) {
9270             mIncludePad = includepad;
9271 
9272             if (mLayout != null) {
9273                 nullLayouts();
9274                 requestLayout();
9275                 invalidate();
9276             }
9277         }
9278     }
9279 
9280     /**
9281      * Gets whether the TextView includes extra top and bottom padding to make
9282      * room for accents that go above the normal ascent and descent.
9283      *
9284      * @see #setIncludeFontPadding(boolean)
9285      *
9286      * @attr ref android.R.styleable#TextView_includeFontPadding
9287      */
9288     @InspectableProperty
getIncludeFontPadding()9289     public boolean getIncludeFontPadding() {
9290         return mIncludePad;
9291     }
9292 
9293     /** @hide */
9294     @VisibleForTesting
9295     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
9296 
9297     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)9298     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
9299         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
9300         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
9301         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
9302         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
9303 
9304         int width;
9305         int height;
9306 
9307         BoringLayout.Metrics boring = UNKNOWN_BORING;
9308         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
9309 
9310         if (mTextDir == null) {
9311             mTextDir = getTextDirectionHeuristic();
9312         }
9313 
9314         int des = -1;
9315         boolean fromexisting = false;
9316         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
9317                 ?  (float) widthSize : Float.MAX_VALUE;
9318 
9319         if (widthMode == MeasureSpec.EXACTLY) {
9320             // Parent has told us how big to be. So be it.
9321             width = widthSize;
9322         } else {
9323             if (mLayout != null && mEllipsize == null) {
9324                 des = desired(mLayout);
9325             }
9326 
9327             if (des < 0) {
9328                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9329                 if (boring != null) {
9330                     mBoring = boring;
9331                 }
9332             } else {
9333                 fromexisting = true;
9334             }
9335 
9336             if (boring == null || boring == UNKNOWN_BORING) {
9337                 if (des < 0) {
9338                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
9339                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
9340                 }
9341                 width = des;
9342             } else {
9343                 width = boring.width;
9344             }
9345 
9346             final Drawables dr = mDrawables;
9347             if (dr != null) {
9348                 width = Math.max(width, dr.mDrawableWidthTop);
9349                 width = Math.max(width, dr.mDrawableWidthBottom);
9350             }
9351 
9352             if (mHint != null) {
9353                 int hintDes = -1;
9354                 int hintWidth;
9355 
9356                 if (mHintLayout != null && mEllipsize == null) {
9357                     hintDes = desired(mHintLayout);
9358                 }
9359 
9360                 if (hintDes < 0) {
9361                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
9362                     if (hintBoring != null) {
9363                         mHintBoring = hintBoring;
9364                     }
9365                 }
9366 
9367                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
9368                     if (hintDes < 0) {
9369                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
9370                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
9371                     }
9372                     hintWidth = hintDes;
9373                 } else {
9374                     hintWidth = hintBoring.width;
9375                 }
9376 
9377                 if (hintWidth > width) {
9378                     width = hintWidth;
9379                 }
9380             }
9381 
9382             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
9383 
9384             if (mMaxWidthMode == EMS) {
9385                 width = Math.min(width, mMaxWidth * getLineHeight());
9386             } else {
9387                 width = Math.min(width, mMaxWidth);
9388             }
9389 
9390             if (mMinWidthMode == EMS) {
9391                 width = Math.max(width, mMinWidth * getLineHeight());
9392             } else {
9393                 width = Math.max(width, mMinWidth);
9394             }
9395 
9396             // Check against our minimum width
9397             width = Math.max(width, getSuggestedMinimumWidth());
9398 
9399             if (widthMode == MeasureSpec.AT_MOST) {
9400                 width = Math.min(widthSize, width);
9401             }
9402         }
9403 
9404         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
9405         int unpaddedWidth = want;
9406 
9407         if (mHorizontallyScrolling) want = VERY_WIDE;
9408 
9409         int hintWant = want;
9410         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
9411 
9412         if (mLayout == null) {
9413             makeNewLayout(want, hintWant, boring, hintBoring,
9414                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9415         } else {
9416             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
9417                     || (mLayout.getEllipsizedWidth()
9418                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
9419 
9420             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
9421                     && (want > mLayout.getWidth())
9422                     && (mLayout instanceof BoringLayout
9423                             || (fromexisting && des >= 0 && des <= want));
9424 
9425             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
9426 
9427             if (layoutChanged || maximumChanged) {
9428                 if (!maximumChanged && widthChanged) {
9429                     mLayout.increaseWidthTo(want);
9430                 } else {
9431                     makeNewLayout(want, hintWant, boring, hintBoring,
9432                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9433                 }
9434             } else {
9435                 // Nothing has changed
9436             }
9437         }
9438 
9439         if (heightMode == MeasureSpec.EXACTLY) {
9440             // Parent has told us how big to be. So be it.
9441             height = heightSize;
9442             mDesiredHeightAtMeasure = -1;
9443         } else {
9444             int desired = getDesiredHeight();
9445 
9446             height = desired;
9447             mDesiredHeightAtMeasure = desired;
9448 
9449             if (heightMode == MeasureSpec.AT_MOST) {
9450                 height = Math.min(desired, heightSize);
9451             }
9452         }
9453 
9454         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
9455         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
9456             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
9457         }
9458 
9459         /*
9460          * We didn't let makeNewLayout() register to bring the cursor into view,
9461          * so do it here if there is any possibility that it is needed.
9462          */
9463         if (mMovement != null
9464                 || mLayout.getWidth() > unpaddedWidth
9465                 || mLayout.getHeight() > unpaddedHeight) {
9466             registerForPreDraw();
9467         } else {
9468             scrollTo(0, 0);
9469         }
9470 
9471         setMeasuredDimension(width, height);
9472     }
9473 
9474     /**
9475      * Automatically computes and sets the text size.
9476      */
autoSizeText()9477     private void autoSizeText() {
9478         if (!isAutoSizeEnabled()) {
9479             return;
9480         }
9481 
9482         if (mNeedsAutoSizeText) {
9483             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
9484                 return;
9485             }
9486 
9487             final int availableWidth = mHorizontallyScrolling
9488                     ? VERY_WIDE
9489                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
9490             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
9491                     - getExtendedPaddingTop();
9492 
9493             if (availableWidth <= 0 || availableHeight <= 0) {
9494                 return;
9495             }
9496 
9497             synchronized (TEMP_RECTF) {
9498                 TEMP_RECTF.setEmpty();
9499                 TEMP_RECTF.right = availableWidth;
9500                 TEMP_RECTF.bottom = availableHeight;
9501                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
9502 
9503                 if (optimalTextSize != getTextSize()) {
9504                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
9505                             false /* shouldRequestLayout */);
9506 
9507                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
9508                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9509                             false /* bringIntoView */);
9510                 }
9511             }
9512         }
9513         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
9514         // after the next layout pass should set this to false.
9515         mNeedsAutoSizeText = true;
9516     }
9517 
9518     /**
9519      * Performs a binary search to find the largest text size that will still fit within the size
9520      * available to this view.
9521      */
findLargestTextSizeWhichFits(RectF availableSpace)9522     private int findLargestTextSizeWhichFits(RectF availableSpace) {
9523         final int sizesCount = mAutoSizeTextSizesInPx.length;
9524         if (sizesCount == 0) {
9525             throw new IllegalStateException("No available text sizes to choose from.");
9526         }
9527 
9528         int bestSizeIndex = 0;
9529         int lowIndex = bestSizeIndex + 1;
9530         int highIndex = sizesCount - 1;
9531         int sizeToTryIndex;
9532         while (lowIndex <= highIndex) {
9533             sizeToTryIndex = (lowIndex + highIndex) / 2;
9534             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
9535                 bestSizeIndex = lowIndex;
9536                 lowIndex = sizeToTryIndex + 1;
9537             } else {
9538                 highIndex = sizeToTryIndex - 1;
9539                 bestSizeIndex = highIndex;
9540             }
9541         }
9542 
9543         return mAutoSizeTextSizesInPx[bestSizeIndex];
9544     }
9545 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9546     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
9547         final CharSequence text = mTransformed != null
9548                 ? mTransformed
9549                 : getText();
9550         final int maxLines = getMaxLines();
9551         if (mTempTextPaint == null) {
9552             mTempTextPaint = new TextPaint();
9553         } else {
9554             mTempTextPaint.reset();
9555         }
9556         mTempTextPaint.set(getPaint());
9557         mTempTextPaint.setTextSize(suggestedSizeInPx);
9558 
9559         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
9560                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
9561 
9562         layoutBuilder.setAlignment(getLayoutAlignment())
9563                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
9564                 .setIncludePad(getIncludeFontPadding())
9565                 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9566                 .setBreakStrategy(getBreakStrategy())
9567                 .setHyphenationFrequency(getHyphenationFrequency())
9568                 .setJustificationMode(getJustificationMode())
9569                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9570                 .setTextDirection(getTextDirectionHeuristic());
9571 
9572         final StaticLayout layout = layoutBuilder.build();
9573 
9574         // Lines overflow.
9575         if (maxLines != -1 && layout.getLineCount() > maxLines) {
9576             return false;
9577         }
9578 
9579         // Height overflow.
9580         if (layout.getHeight() > availableSpace.bottom) {
9581             return false;
9582         }
9583 
9584         return true;
9585     }
9586 
getDesiredHeight()9587     private int getDesiredHeight() {
9588         return Math.max(
9589                 getDesiredHeight(mLayout, true),
9590                 getDesiredHeight(mHintLayout, mEllipsize != null));
9591     }
9592 
getDesiredHeight(Layout layout, boolean cap)9593     private int getDesiredHeight(Layout layout, boolean cap) {
9594         if (layout == null) {
9595             return 0;
9596         }
9597 
9598         /*
9599         * Don't cap the hint to a certain number of lines.
9600         * (Do cap it, though, if we have a maximum pixel height.)
9601         */
9602         int desired = layout.getHeight(cap);
9603 
9604         final Drawables dr = mDrawables;
9605         if (dr != null) {
9606             desired = Math.max(desired, dr.mDrawableHeightLeft);
9607             desired = Math.max(desired, dr.mDrawableHeightRight);
9608         }
9609 
9610         int linecount = layout.getLineCount();
9611         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
9612         desired += padding;
9613 
9614         if (mMaxMode != LINES) {
9615             desired = Math.min(desired, mMaximum);
9616         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
9617                 || layout instanceof BoringLayout)) {
9618             desired = layout.getLineTop(mMaximum);
9619 
9620             if (dr != null) {
9621                 desired = Math.max(desired, dr.mDrawableHeightLeft);
9622                 desired = Math.max(desired, dr.mDrawableHeightRight);
9623             }
9624 
9625             desired += padding;
9626             linecount = mMaximum;
9627         }
9628 
9629         if (mMinMode == LINES) {
9630             if (linecount < mMinimum) {
9631                 desired += getLineHeight() * (mMinimum - linecount);
9632             }
9633         } else {
9634             desired = Math.max(desired, mMinimum);
9635         }
9636 
9637         // Check against our minimum height
9638         desired = Math.max(desired, getSuggestedMinimumHeight());
9639 
9640         return desired;
9641     }
9642 
9643     /**
9644      * Check whether a change to the existing text layout requires a
9645      * new view layout.
9646      */
checkForResize()9647     private void checkForResize() {
9648         boolean sizeChanged = false;
9649 
9650         if (mLayout != null) {
9651             // Check if our width changed
9652             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
9653                 sizeChanged = true;
9654                 invalidate();
9655             }
9656 
9657             // Check if our height changed
9658             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
9659                 int desiredHeight = getDesiredHeight();
9660 
9661                 if (desiredHeight != this.getHeight()) {
9662                     sizeChanged = true;
9663                 }
9664             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
9665                 if (mDesiredHeightAtMeasure >= 0) {
9666                     int desiredHeight = getDesiredHeight();
9667 
9668                     if (desiredHeight != mDesiredHeightAtMeasure) {
9669                         sizeChanged = true;
9670                     }
9671                 }
9672             }
9673         }
9674 
9675         if (sizeChanged) {
9676             requestLayout();
9677             // caller will have already invalidated
9678         }
9679     }
9680 
9681     /**
9682      * Check whether entirely new text requires a new view layout
9683      * or merely a new text layout.
9684      */
9685     @UnsupportedAppUsage
checkForRelayout()9686     private void checkForRelayout() {
9687         // If we have a fixed width, we can just swap in a new text layout
9688         // if the text height stays the same or if the view height is fixed.
9689 
9690         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
9691                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
9692                 && (mHint == null || mHintLayout != null)
9693                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
9694             // Static width, so try making a new text layout.
9695 
9696             int oldht = mLayout.getHeight();
9697             int want = mLayout.getWidth();
9698             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
9699 
9700             /*
9701              * No need to bring the text into view, since the size is not
9702              * changing (unless we do the requestLayout(), in which case it
9703              * will happen at measure).
9704              */
9705             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
9706                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9707                           false);
9708 
9709             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
9710                 // In a fixed-height view, so use our new text layout.
9711                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
9712                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
9713                     autoSizeText();
9714                     invalidate();
9715                     return;
9716                 }
9717 
9718                 // Dynamic height, but height has stayed the same,
9719                 // so use our new text layout.
9720                 if (mLayout.getHeight() == oldht
9721                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
9722                     autoSizeText();
9723                     invalidate();
9724                     return;
9725                 }
9726             }
9727 
9728             // We lose: the height has changed and we have a dynamic height.
9729             // Request a new view layout using our new text layout.
9730             requestLayout();
9731             invalidate();
9732         } else {
9733             // Dynamic width, so we have no choice but to request a new
9734             // view layout with a new text layout.
9735             nullLayouts();
9736             requestLayout();
9737             invalidate();
9738         }
9739     }
9740 
9741     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)9742     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
9743         super.onLayout(changed, left, top, right, bottom);
9744         if (mDeferScroll >= 0) {
9745             int curs = mDeferScroll;
9746             mDeferScroll = -1;
9747             bringPointIntoView(Math.min(curs, mText.length()));
9748         }
9749         // Call auto-size after the width and height have been calculated.
9750         autoSizeText();
9751     }
9752 
isShowingHint()9753     private boolean isShowingHint() {
9754         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
9755     }
9756 
9757     /**
9758      * Returns true if anything changed.
9759      */
9760     @UnsupportedAppUsage
bringTextIntoView()9761     private boolean bringTextIntoView() {
9762         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9763         int line = 0;
9764         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9765             line = layout.getLineCount() - 1;
9766         }
9767 
9768         Layout.Alignment a = layout.getParagraphAlignment(line);
9769         int dir = layout.getParagraphDirection(line);
9770         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9771         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9772         int ht = layout.getHeight();
9773 
9774         int scrollx, scrolly;
9775 
9776         // Convert to left, center, or right alignment.
9777         if (a == Layout.Alignment.ALIGN_NORMAL) {
9778             a = dir == Layout.DIR_LEFT_TO_RIGHT
9779                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
9780         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
9781             a = dir == Layout.DIR_LEFT_TO_RIGHT
9782                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
9783         }
9784 
9785         if (a == Layout.Alignment.ALIGN_CENTER) {
9786             /*
9787              * Keep centered if possible, or, if it is too wide to fit,
9788              * keep leading edge in view.
9789              */
9790 
9791             int left = (int) Math.floor(layout.getLineLeft(line));
9792             int right = (int) Math.ceil(layout.getLineRight(line));
9793 
9794             if (right - left < hspace) {
9795                 scrollx = (right + left) / 2 - hspace / 2;
9796             } else {
9797                 if (dir < 0) {
9798                     scrollx = right - hspace;
9799                 } else {
9800                     scrollx = left;
9801                 }
9802             }
9803         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
9804             int right = (int) Math.ceil(layout.getLineRight(line));
9805             scrollx = right - hspace;
9806         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
9807             scrollx = (int) Math.floor(layout.getLineLeft(line));
9808         }
9809 
9810         if (ht < vspace) {
9811             scrolly = 0;
9812         } else {
9813             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9814                 scrolly = ht - vspace;
9815             } else {
9816                 scrolly = 0;
9817             }
9818         }
9819 
9820         if (scrollx != mScrollX || scrolly != mScrollY) {
9821             scrollTo(scrollx, scrolly);
9822             return true;
9823         } else {
9824             return false;
9825         }
9826     }
9827 
9828     /**
9829      * Move the point, specified by the offset, into the view if it is needed.
9830      * This has to be called after layout. Returns true if anything changed.
9831      */
bringPointIntoView(int offset)9832     public boolean bringPointIntoView(int offset) {
9833         if (isLayoutRequested()) {
9834             mDeferScroll = offset;
9835             return false;
9836         }
9837         boolean changed = false;
9838 
9839         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9840 
9841         if (layout == null) return changed;
9842 
9843         int line = layout.getLineForOffset(offset);
9844 
9845         int grav;
9846 
9847         switch (layout.getParagraphAlignment(line)) {
9848             case ALIGN_LEFT:
9849                 grav = 1;
9850                 break;
9851             case ALIGN_RIGHT:
9852                 grav = -1;
9853                 break;
9854             case ALIGN_NORMAL:
9855                 grav = layout.getParagraphDirection(line);
9856                 break;
9857             case ALIGN_OPPOSITE:
9858                 grav = -layout.getParagraphDirection(line);
9859                 break;
9860             case ALIGN_CENTER:
9861             default:
9862                 grav = 0;
9863                 break;
9864         }
9865 
9866         // We only want to clamp the cursor to fit within the layout width
9867         // in left-to-right modes, because in a right to left alignment,
9868         // we want to scroll to keep the line-right on the screen, as other
9869         // lines are likely to have text flush with the right margin, which
9870         // we want to keep visible.
9871         // A better long-term solution would probably be to measure both
9872         // the full line and a blank-trimmed version, and, for example, use
9873         // the latter measurement for centering and right alignment, but for
9874         // the time being we only implement the cursor clamping in left to
9875         // right where it is most likely to be annoying.
9876         final boolean clamped = grav > 0;
9877         // FIXME: Is it okay to truncate this, or should we round?
9878         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
9879         final int top = layout.getLineTop(line);
9880         final int bottom = layout.getLineTop(line + 1);
9881 
9882         int left = (int) Math.floor(layout.getLineLeft(line));
9883         int right = (int) Math.ceil(layout.getLineRight(line));
9884         int ht = layout.getHeight();
9885 
9886         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9887         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9888         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
9889             // If cursor has been clamped, make sure we don't scroll.
9890             right = Math.max(x, left + hspace);
9891         }
9892 
9893         int hslack = (bottom - top) / 2;
9894         int vslack = hslack;
9895 
9896         if (vslack > vspace / 4) {
9897             vslack = vspace / 4;
9898         }
9899         if (hslack > hspace / 4) {
9900             hslack = hspace / 4;
9901         }
9902 
9903         int hs = mScrollX;
9904         int vs = mScrollY;
9905 
9906         if (top - vs < vslack) {
9907             vs = top - vslack;
9908         }
9909         if (bottom - vs > vspace - vslack) {
9910             vs = bottom - (vspace - vslack);
9911         }
9912         if (ht - vs < vspace) {
9913             vs = ht - vspace;
9914         }
9915         if (0 - vs > 0) {
9916             vs = 0;
9917         }
9918 
9919         if (grav != 0) {
9920             if (x - hs < hslack) {
9921                 hs = x - hslack;
9922             }
9923             if (x - hs > hspace - hslack) {
9924                 hs = x - (hspace - hslack);
9925             }
9926         }
9927 
9928         if (grav < 0) {
9929             if (left - hs > 0) {
9930                 hs = left;
9931             }
9932             if (right - hs < hspace) {
9933                 hs = right - hspace;
9934             }
9935         } else if (grav > 0) {
9936             if (right - hs < hspace) {
9937                 hs = right - hspace;
9938             }
9939             if (left - hs > 0) {
9940                 hs = left;
9941             }
9942         } else /* grav == 0 */ {
9943             if (right - left <= hspace) {
9944                 /*
9945                  * If the entire text fits, center it exactly.
9946                  */
9947                 hs = left - (hspace - (right - left)) / 2;
9948             } else if (x > right - hslack) {
9949                 /*
9950                  * If we are near the right edge, keep the right edge
9951                  * at the edge of the view.
9952                  */
9953                 hs = right - hspace;
9954             } else if (x < left + hslack) {
9955                 /*
9956                  * If we are near the left edge, keep the left edge
9957                  * at the edge of the view.
9958                  */
9959                 hs = left;
9960             } else if (left > hs) {
9961                 /*
9962                  * Is there whitespace visible at the left?  Fix it if so.
9963                  */
9964                 hs = left;
9965             } else if (right < hs + hspace) {
9966                 /*
9967                  * Is there whitespace visible at the right?  Fix it if so.
9968                  */
9969                 hs = right - hspace;
9970             } else {
9971                 /*
9972                  * Otherwise, float as needed.
9973                  */
9974                 if (x - hs < hslack) {
9975                     hs = x - hslack;
9976                 }
9977                 if (x - hs > hspace - hslack) {
9978                     hs = x - (hspace - hslack);
9979                 }
9980             }
9981         }
9982 
9983         if (hs != mScrollX || vs != mScrollY) {
9984             if (mScroller == null) {
9985                 scrollTo(hs, vs);
9986             } else {
9987                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
9988                 int dx = hs - mScrollX;
9989                 int dy = vs - mScrollY;
9990 
9991                 if (duration > ANIMATED_SCROLL_GAP) {
9992                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
9993                     awakenScrollBars(mScroller.getDuration());
9994                     invalidate();
9995                 } else {
9996                     if (!mScroller.isFinished()) {
9997                         mScroller.abortAnimation();
9998                     }
9999 
10000                     scrollBy(dx, dy);
10001                 }
10002 
10003                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
10004             }
10005 
10006             changed = true;
10007         }
10008 
10009         if (isFocused()) {
10010             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
10011             // requestRectangleOnScreen() is in terms of content coordinates.
10012 
10013             // The offsets here are to ensure the rectangle we are using is
10014             // within our view bounds, in case the cursor is on the far left
10015             // or right.  If it isn't withing the bounds, then this request
10016             // will be ignored.
10017             if (mTempRect == null) mTempRect = new Rect();
10018             mTempRect.set(x - 2, top, x + 2, bottom);
10019             getInterestingRect(mTempRect, line);
10020             mTempRect.offset(mScrollX, mScrollY);
10021 
10022             if (requestRectangleOnScreen(mTempRect)) {
10023                 changed = true;
10024             }
10025         }
10026 
10027         return changed;
10028     }
10029 
10030     /**
10031      * Move the cursor, if needed, so that it is at an offset that is visible
10032      * to the user.  This will not move the cursor if it represents more than
10033      * one character (a selection range).  This will only work if the
10034      * TextView contains spannable text; otherwise it will do nothing.
10035      *
10036      * @return True if the cursor was actually moved, false otherwise.
10037      */
moveCursorToVisibleOffset()10038     public boolean moveCursorToVisibleOffset() {
10039         if (!(mText instanceof Spannable)) {
10040             return false;
10041         }
10042         int start = getSelectionStart();
10043         int end = getSelectionEnd();
10044         if (start != end) {
10045             return false;
10046         }
10047 
10048         // First: make sure the line is visible on screen:
10049 
10050         int line = mLayout.getLineForOffset(start);
10051 
10052         final int top = mLayout.getLineTop(line);
10053         final int bottom = mLayout.getLineTop(line + 1);
10054         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10055         int vslack = (bottom - top) / 2;
10056         if (vslack > vspace / 4) {
10057             vslack = vspace / 4;
10058         }
10059         final int vs = mScrollY;
10060 
10061         if (top < (vs + vslack)) {
10062             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
10063         } else if (bottom > (vspace + vs - vslack)) {
10064             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
10065         }
10066 
10067         // Next: make sure the character is visible on screen:
10068 
10069         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10070         final int hs = mScrollX;
10071         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
10072         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
10073 
10074         // line might contain bidirectional text
10075         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
10076         final int highChar = leftChar > rightChar ? leftChar : rightChar;
10077 
10078         int newStart = start;
10079         if (newStart < lowChar) {
10080             newStart = lowChar;
10081         } else if (newStart > highChar) {
10082             newStart = highChar;
10083         }
10084 
10085         if (newStart != start) {
10086             Selection.setSelection(mSpannable, newStart);
10087             return true;
10088         }
10089 
10090         return false;
10091     }
10092 
10093     @Override
computeScroll()10094     public void computeScroll() {
10095         if (mScroller != null) {
10096             if (mScroller.computeScrollOffset()) {
10097                 mScrollX = mScroller.getCurrX();
10098                 mScrollY = mScroller.getCurrY();
10099                 invalidateParentCaches();
10100                 postInvalidate();  // So we draw again
10101             }
10102         }
10103     }
10104 
getInterestingRect(Rect r, int line)10105     private void getInterestingRect(Rect r, int line) {
10106         convertFromViewportToContentCoordinates(r);
10107 
10108         // Rectangle can can be expanded on first and last line to take
10109         // padding into account.
10110         // TODO Take left/right padding into account too?
10111         if (line == 0) r.top -= getExtendedPaddingTop();
10112         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
10113     }
10114 
convertFromViewportToContentCoordinates(Rect r)10115     private void convertFromViewportToContentCoordinates(Rect r) {
10116         final int horizontalOffset = viewportToContentHorizontalOffset();
10117         r.left += horizontalOffset;
10118         r.right += horizontalOffset;
10119 
10120         final int verticalOffset = viewportToContentVerticalOffset();
10121         r.top += verticalOffset;
10122         r.bottom += verticalOffset;
10123     }
10124 
viewportToContentHorizontalOffset()10125     int viewportToContentHorizontalOffset() {
10126         return getCompoundPaddingLeft() - mScrollX;
10127     }
10128 
10129     @UnsupportedAppUsage
viewportToContentVerticalOffset()10130     int viewportToContentVerticalOffset() {
10131         int offset = getExtendedPaddingTop() - mScrollY;
10132         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
10133             offset += getVerticalOffset(false);
10134         }
10135         return offset;
10136     }
10137 
10138     @Override
debug(int depth)10139     public void debug(int depth) {
10140         super.debug(depth);
10141 
10142         String output = debugIndent(depth);
10143         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
10144                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
10145                 + "} ";
10146 
10147         if (mText != null) {
10148 
10149             output += "mText=\"" + mText + "\" ";
10150             if (mLayout != null) {
10151                 output += "mLayout width=" + mLayout.getWidth()
10152                         + " height=" + mLayout.getHeight();
10153             }
10154         } else {
10155             output += "mText=NULL";
10156         }
10157         Log.d(VIEW_LOG_TAG, output);
10158     }
10159 
10160     /**
10161      * Convenience for {@link Selection#getSelectionStart}.
10162      */
10163     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()10164     public int getSelectionStart() {
10165         return Selection.getSelectionStart(getText());
10166     }
10167 
10168     /**
10169      * Convenience for {@link Selection#getSelectionEnd}.
10170      */
10171     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()10172     public int getSelectionEnd() {
10173         return Selection.getSelectionEnd(getText());
10174     }
10175 
10176     /**
10177      * Return true iff there is a selection of nonzero length inside this text view.
10178      */
hasSelection()10179     public boolean hasSelection() {
10180         final int selectionStart = getSelectionStart();
10181         final int selectionEnd = getSelectionEnd();
10182 
10183         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
10184     }
10185 
getSelectedText()10186     String getSelectedText() {
10187         if (!hasSelection()) {
10188             return null;
10189         }
10190 
10191         final int start = getSelectionStart();
10192         final int end = getSelectionEnd();
10193         return String.valueOf(
10194                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
10195     }
10196 
10197     /**
10198      * Sets the properties of this field (lines, horizontally scrolling,
10199      * transformation method) to be for a single-line input.
10200      *
10201      * @attr ref android.R.styleable#TextView_singleLine
10202      */
setSingleLine()10203     public void setSingleLine() {
10204         setSingleLine(true);
10205     }
10206 
10207     /**
10208      * Sets the properties of this field to transform input to ALL CAPS
10209      * display. This may use a "small caps" formatting if available.
10210      * This setting will be ignored if this field is editable or selectable.
10211      *
10212      * This call replaces the current transformation method. Disabling this
10213      * will not necessarily restore the previous behavior from before this
10214      * was enabled.
10215      *
10216      * @see #setTransformationMethod(TransformationMethod)
10217      * @attr ref android.R.styleable#TextView_textAllCaps
10218      */
setAllCaps(boolean allCaps)10219     public void setAllCaps(boolean allCaps) {
10220         if (allCaps) {
10221             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
10222         } else {
10223             setTransformationMethod(null);
10224         }
10225     }
10226 
10227     /**
10228      *
10229      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
10230      * @return Whether the current transformation method is for ALL CAPS.
10231      *
10232      * @see #setAllCaps(boolean)
10233      * @see #setTransformationMethod(TransformationMethod)
10234      */
10235     @InspectableProperty(name = "textAllCaps")
isAllCaps()10236     public boolean isAllCaps() {
10237         final TransformationMethod method = getTransformationMethod();
10238         return method != null && method instanceof AllCapsTransformationMethod;
10239     }
10240 
10241     /**
10242      * If true, sets the properties of this field (number of lines, horizontally scrolling,
10243      * transformation method) to be for a single-line input; if false, restores these to the default
10244      * conditions.
10245      *
10246      * Note that the default conditions are not necessarily those that were in effect prior this
10247      * method, and you may want to reset these properties to your custom values.
10248      *
10249      * Note that due to performance reasons, by setting single line for the EditText, the maximum
10250      * text length is set to 5000 if no other character limitation are applied.
10251      *
10252      * @attr ref android.R.styleable#TextView_singleLine
10253      */
10254     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)10255     public void setSingleLine(boolean singleLine) {
10256         // Could be used, but may break backward compatibility.
10257         // if (mSingleLine == singleLine) return;
10258         setInputTypeSingleLine(singleLine);
10259         applySingleLine(singleLine, true, true, true);
10260     }
10261 
10262     /**
10263      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
10264      * @param singleLine
10265      */
setInputTypeSingleLine(boolean singleLine)10266     private void setInputTypeSingleLine(boolean singleLine) {
10267         if (mEditor != null
10268                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
10269                         == EditorInfo.TYPE_CLASS_TEXT) {
10270             if (singleLine) {
10271                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10272             } else {
10273                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10274             }
10275         }
10276     }
10277 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)10278     private void applySingleLine(boolean singleLine, boolean applyTransformation,
10279             boolean changeMaxLines, boolean changeMaxLength) {
10280         mSingleLine = singleLine;
10281 
10282         if (singleLine) {
10283             setLines(1);
10284             setHorizontallyScrolling(true);
10285             if (applyTransformation) {
10286                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
10287             }
10288 
10289             if (!changeMaxLength) return;
10290 
10291             // Single line length filter is only applicable editable text.
10292             if (mBufferType != BufferType.EDITABLE) return;
10293 
10294             final InputFilter[] prevFilters = getFilters();
10295             for (InputFilter filter: getFilters()) {
10296                 // We don't add LengthFilter if already there.
10297                 if (filter instanceof InputFilter.LengthFilter) return;
10298             }
10299 
10300             if (mSingleLineLengthFilter == null) {
10301                 mSingleLineLengthFilter = new InputFilter.LengthFilter(
10302                     MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
10303             }
10304 
10305             final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1];
10306             System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length);
10307             newFilters[prevFilters.length] = mSingleLineLengthFilter;
10308 
10309             setFilters(newFilters);
10310 
10311             // Since filter doesn't apply to existing text, trigger filter by setting text.
10312             setText(getText());
10313         } else {
10314             if (changeMaxLines) {
10315                 setMaxLines(Integer.MAX_VALUE);
10316             }
10317             setHorizontallyScrolling(false);
10318             if (applyTransformation) {
10319                 setTransformationMethod(null);
10320             }
10321 
10322             if (!changeMaxLength) return;
10323 
10324             // Single line length filter is only applicable editable text.
10325             if (mBufferType != BufferType.EDITABLE) return;
10326 
10327             final InputFilter[] prevFilters = getFilters();
10328             if (prevFilters.length == 0) return;
10329 
10330             // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated
10331             // single line char limit filter.
10332             if (mSingleLineLengthFilter == null) return;
10333 
10334             // If we need to remove mSingleLineLengthFilter, we need to allocate another array.
10335             // Since filter list is expected to be small and want to avoid unnecessary array
10336             // allocation, check if there is mSingleLengthFilter first.
10337             int targetIndex = -1;
10338             for (int i = 0; i < prevFilters.length; ++i) {
10339                 if (prevFilters[i] == mSingleLineLengthFilter) {
10340                     targetIndex = i;
10341                     break;
10342                 }
10343             }
10344             if (targetIndex == -1) return;  // not found. Do nothing.
10345 
10346             if (prevFilters.length == 1) {
10347                 setFilters(NO_FILTERS);
10348                 return;
10349             }
10350 
10351             // Create new array which doesn't include mSingleLengthFilter.
10352             final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1];
10353             System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex);
10354             System.arraycopy(
10355                     prevFilters,
10356                     targetIndex + 1,
10357                     newFilters,
10358                     targetIndex,
10359                     prevFilters.length - targetIndex - 1);
10360             setFilters(newFilters);
10361             mSingleLineLengthFilter = null;
10362         }
10363     }
10364 
10365     /**
10366      * Causes words in the text that are longer than the view's width
10367      * to be ellipsized instead of broken in the middle.  You may also
10368      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
10369      * to constrain the text to a single line.  Use <code>null</code>
10370      * to turn off ellipsizing.
10371      *
10372      * If {@link #setMaxLines} has been used to set two or more lines,
10373      * only {@link android.text.TextUtils.TruncateAt#END} and
10374      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
10375      * (other ellipsizing types will not do anything).
10376      *
10377      * @attr ref android.R.styleable#TextView_ellipsize
10378      */
setEllipsize(TextUtils.TruncateAt where)10379     public void setEllipsize(TextUtils.TruncateAt where) {
10380         // TruncateAt is an enum. != comparison is ok between these singleton objects.
10381         if (mEllipsize != where) {
10382             mEllipsize = where;
10383 
10384             if (mLayout != null) {
10385                 nullLayouts();
10386                 requestLayout();
10387                 invalidate();
10388             }
10389         }
10390     }
10391 
10392     /**
10393      * Sets how many times to repeat the marquee animation. Only applied if the
10394      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
10395      *
10396      * @see #getMarqueeRepeatLimit()
10397      *
10398      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10399      */
setMarqueeRepeatLimit(int marqueeLimit)10400     public void setMarqueeRepeatLimit(int marqueeLimit) {
10401         mMarqueeRepeatLimit = marqueeLimit;
10402     }
10403 
10404     /**
10405      * Gets the number of times the marquee animation is repeated. Only meaningful if the
10406      * TextView has marquee enabled.
10407      *
10408      * @return the number of times the marquee animation is repeated. -1 if the animation
10409      * repeats indefinitely
10410      *
10411      * @see #setMarqueeRepeatLimit(int)
10412      *
10413      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10414      */
10415     @InspectableProperty
getMarqueeRepeatLimit()10416     public int getMarqueeRepeatLimit() {
10417         return mMarqueeRepeatLimit;
10418     }
10419 
10420     /**
10421      * Returns where, if anywhere, words that are longer than the view
10422      * is wide should be ellipsized.
10423      */
10424     @InspectableProperty
10425     @ViewDebug.ExportedProperty
getEllipsize()10426     public TextUtils.TruncateAt getEllipsize() {
10427         return mEllipsize;
10428     }
10429 
10430     /**
10431      * Set the TextView so that when it takes focus, all the text is
10432      * selected.
10433      *
10434      * @attr ref android.R.styleable#TextView_selectAllOnFocus
10435      */
10436     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)10437     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
10438         createEditorIfNeeded();
10439         mEditor.mSelectAllOnFocus = selectAllOnFocus;
10440 
10441         if (selectAllOnFocus && !(mText instanceof Spannable)) {
10442             setText(mText, BufferType.SPANNABLE);
10443         }
10444     }
10445 
10446     /**
10447      * Set whether the cursor is visible. The default is true. Note that this property only
10448      * makes sense for editable TextView.
10449      *
10450      * @see #isCursorVisible()
10451      *
10452      * @attr ref android.R.styleable#TextView_cursorVisible
10453      */
10454     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)10455     public void setCursorVisible(boolean visible) {
10456         if (visible && mEditor == null) return; // visible is the default value with no edit data
10457         createEditorIfNeeded();
10458         if (mEditor.mCursorVisible != visible) {
10459             mEditor.mCursorVisible = visible;
10460             invalidate();
10461 
10462             mEditor.makeBlink();
10463 
10464             // InsertionPointCursorController depends on mCursorVisible
10465             mEditor.prepareCursorControllers();
10466         }
10467     }
10468 
10469     /**
10470      * @return whether or not the cursor is visible (assuming this TextView is editable)
10471      *
10472      * @see #setCursorVisible(boolean)
10473      *
10474      * @attr ref android.R.styleable#TextView_cursorVisible
10475      */
10476     @InspectableProperty
isCursorVisible()10477     public boolean isCursorVisible() {
10478         // true is the default value
10479         return mEditor == null ? true : mEditor.mCursorVisible;
10480     }
10481 
canMarquee()10482     private boolean canMarquee() {
10483         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10484         return width > 0 && (mLayout.getLineWidth(0) > width
10485                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
10486                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
10487     }
10488 
10489     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()10490     private void startMarquee() {
10491         // Do not ellipsize EditText
10492         if (getKeyListener() != null) return;
10493 
10494         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
10495             return;
10496         }
10497 
10498         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
10499                 && getLineCount() == 1 && canMarquee()) {
10500 
10501             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10502                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
10503                 final Layout tmp = mLayout;
10504                 mLayout = mSavedMarqueeModeLayout;
10505                 mSavedMarqueeModeLayout = tmp;
10506                 setHorizontalFadingEdgeEnabled(true);
10507                 requestLayout();
10508                 invalidate();
10509             }
10510 
10511             if (mMarquee == null) mMarquee = new Marquee(this);
10512             mMarquee.start(mMarqueeRepeatLimit);
10513         }
10514     }
10515 
stopMarquee()10516     private void stopMarquee() {
10517         if (mMarquee != null && !mMarquee.isStopped()) {
10518             mMarquee.stop();
10519         }
10520 
10521         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
10522             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10523             final Layout tmp = mSavedMarqueeModeLayout;
10524             mSavedMarqueeModeLayout = mLayout;
10525             mLayout = tmp;
10526             setHorizontalFadingEdgeEnabled(false);
10527             requestLayout();
10528             invalidate();
10529         }
10530     }
10531 
10532     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)10533     private void startStopMarquee(boolean start) {
10534         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10535             if (start) {
10536                 startMarquee();
10537             } else {
10538                 stopMarquee();
10539             }
10540         }
10541     }
10542 
10543     /**
10544      * This method is called when the text is changed, in case any subclasses
10545      * would like to know.
10546      *
10547      * Within <code>text</code>, the <code>lengthAfter</code> characters
10548      * beginning at <code>start</code> have just replaced old text that had
10549      * length <code>lengthBefore</code>. It is an error to attempt to make
10550      * changes to <code>text</code> from this callback.
10551      *
10552      * @param text The text the TextView is displaying
10553      * @param start The offset of the start of the range of the text that was
10554      * modified
10555      * @param lengthBefore The length of the former text that has been replaced
10556      * @param lengthAfter The length of the replacement modified text
10557      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10558     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
10559         // intentionally empty, template pattern method can be overridden by subclasses
10560     }
10561 
10562     /**
10563      * This method is called when the selection has changed, in case any
10564      * subclasses would like to know.
10565      * </p>
10566      * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs
10567      * the accessibility subsystem about the selection change.
10568      * </p>
10569      *
10570      * @param selStart The new selection start location.
10571      * @param selEnd The new selection end location.
10572      */
10573     @CallSuper
onSelectionChanged(int selStart, int selEnd)10574     protected void onSelectionChanged(int selStart, int selEnd) {
10575         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
10576     }
10577 
10578     /**
10579      * Adds a TextWatcher to the list of those whose methods are called
10580      * whenever this TextView's text changes.
10581      * <p>
10582      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
10583      * not called after {@link #setText} calls.  Now, doing {@link #setText}
10584      * if there are any text changed listeners forces the buffer type to
10585      * Editable if it would not otherwise be and does call this method.
10586      */
addTextChangedListener(TextWatcher watcher)10587     public void addTextChangedListener(TextWatcher watcher) {
10588         if (mListeners == null) {
10589             mListeners = new ArrayList<TextWatcher>();
10590         }
10591 
10592         mListeners.add(watcher);
10593     }
10594 
10595     /**
10596      * Removes the specified TextWatcher from the list of those whose
10597      * methods are called
10598      * whenever this TextView's text changes.
10599      */
removeTextChangedListener(TextWatcher watcher)10600     public void removeTextChangedListener(TextWatcher watcher) {
10601         if (mListeners != null) {
10602             int i = mListeners.indexOf(watcher);
10603 
10604             if (i >= 0) {
10605                 mListeners.remove(i);
10606             }
10607         }
10608     }
10609 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)10610     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
10611         if (mListeners != null) {
10612             final ArrayList<TextWatcher> list = mListeners;
10613             final int count = list.size();
10614             for (int i = 0; i < count; i++) {
10615                 list.get(i).beforeTextChanged(text, start, before, after);
10616             }
10617         }
10618 
10619         // The spans that are inside or intersect the modified region no longer make sense
10620         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
10621         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
10622     }
10623 
10624     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10625     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
10626         if (!(mText instanceof Editable)) return;
10627         Editable text = (Editable) mText;
10628 
10629         T[] spans = text.getSpans(start, end, type);
10630         final int length = spans.length;
10631         for (int i = 0; i < length; i++) {
10632             final int spanStart = text.getSpanStart(spans[i]);
10633             final int spanEnd = text.getSpanEnd(spans[i]);
10634             if (spanEnd == start || spanStart == end) break;
10635             text.removeSpan(spans[i]);
10636         }
10637     }
10638 
removeAdjacentSuggestionSpans(final int pos)10639     void removeAdjacentSuggestionSpans(final int pos) {
10640         if (!(mText instanceof Editable)) return;
10641         final Editable text = (Editable) mText;
10642 
10643         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
10644         final int length = spans.length;
10645         for (int i = 0; i < length; i++) {
10646             final int spanStart = text.getSpanStart(spans[i]);
10647             final int spanEnd = text.getSpanEnd(spans[i]);
10648             if (spanEnd == pos || spanStart == pos) {
10649                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
10650                     text.removeSpan(spans[i]);
10651                 }
10652             }
10653         }
10654     }
10655 
10656     /**
10657      * Not private so it can be called from an inner class without going
10658      * through a thunk.
10659      */
sendOnTextChanged(CharSequence text, int start, int before, int after)10660     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
10661         if (mListeners != null) {
10662             final ArrayList<TextWatcher> list = mListeners;
10663             final int count = list.size();
10664             for (int i = 0; i < count; i++) {
10665                 list.get(i).onTextChanged(text, start, before, after);
10666             }
10667         }
10668 
10669         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
10670     }
10671 
10672     /**
10673      * Not private so it can be called from an inner class without going
10674      * through a thunk.
10675      */
sendAfterTextChanged(Editable text)10676     void sendAfterTextChanged(Editable text) {
10677         if (mListeners != null) {
10678             final ArrayList<TextWatcher> list = mListeners;
10679             final int count = list.size();
10680             for (int i = 0; i < count; i++) {
10681                 list.get(i).afterTextChanged(text);
10682             }
10683         }
10684 
10685         notifyListeningManagersAfterTextChanged();
10686 
10687         hideErrorIfUnchanged();
10688     }
10689 
10690     /**
10691      * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
10692      * interested on text changes.
10693      */
notifyListeningManagersAfterTextChanged()10694     private void notifyListeningManagersAfterTextChanged() {
10695 
10696         // Autofill
10697         if (isAutofillable()) {
10698             // It is important to not check whether the view is important for autofill
10699             // since the user can trigger autofill manually on not important views.
10700             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10701             if (afm != null) {
10702                 if (android.view.autofill.Helper.sVerbose) {
10703                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
10704                 }
10705                 afm.notifyValueChanged(TextView.this);
10706             }
10707         }
10708 
10709         // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
10710         // of using isLaidout(), so it's not called in cases where it's laid out but a
10711         // notifyAppeared was not sent.
10712 
10713         // ContentCapture
10714         if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) {
10715             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
10716             if (cm != null && cm.isContentCaptureEnabled()) {
10717                 final ContentCaptureSession session = getContentCaptureSession();
10718                 if (session != null) {
10719                     // TODO(b/111276913): pass flags when edited by user / add CTS test
10720                     session.notifyViewTextChanged(getAutofillId(), getText());
10721                 }
10722             }
10723         }
10724     }
10725 
isAutofillable()10726     private boolean isAutofillable() {
10727         // It is important to not check whether the view is important for autofill
10728         // since the user can trigger autofill manually on not important views.
10729         return getAutofillType() != AUTOFILL_TYPE_NONE;
10730     }
10731 
updateAfterEdit()10732     void updateAfterEdit() {
10733         invalidate();
10734         int curs = getSelectionStart();
10735 
10736         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
10737             registerForPreDraw();
10738         }
10739 
10740         checkForResize();
10741 
10742         if (curs >= 0) {
10743             mHighlightPathBogus = true;
10744             if (mEditor != null) mEditor.makeBlink();
10745             bringPointIntoView(curs);
10746         }
10747     }
10748 
10749     /**
10750      * Not private so it can be called from an inner class without going
10751      * through a thunk.
10752      */
handleTextChanged(CharSequence buffer, int start, int before, int after)10753     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
10754         sLastCutCopyOrTextChangedTime = 0;
10755 
10756         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10757         if (ims == null || ims.mBatchEditNesting == 0) {
10758             updateAfterEdit();
10759         }
10760         if (ims != null) {
10761             ims.mContentChanged = true;
10762             if (ims.mChangedStart < 0) {
10763                 ims.mChangedStart = start;
10764                 ims.mChangedEnd = start + before;
10765             } else {
10766                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
10767                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
10768             }
10769             ims.mChangedDelta += after - before;
10770         }
10771         resetErrorChangedFlag();
10772         sendOnTextChanged(buffer, start, before, after);
10773         onTextChanged(buffer, start, before, after);
10774     }
10775 
10776     /**
10777      * Not private so it can be called from an inner class without going
10778      * through a thunk.
10779      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10780     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
10781         // XXX Make the start and end move together if this ends up
10782         // spending too much time invalidating.
10783 
10784         boolean selChanged = false;
10785         int newSelStart = -1, newSelEnd = -1;
10786 
10787         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10788 
10789         if (what == Selection.SELECTION_END) {
10790             selChanged = true;
10791             newSelEnd = newStart;
10792 
10793             if (oldStart >= 0 || newStart >= 0) {
10794                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
10795                 checkForResize();
10796                 registerForPreDraw();
10797                 if (mEditor != null) mEditor.makeBlink();
10798             }
10799         }
10800 
10801         if (what == Selection.SELECTION_START) {
10802             selChanged = true;
10803             newSelStart = newStart;
10804 
10805             if (oldStart >= 0 || newStart >= 0) {
10806                 int end = Selection.getSelectionEnd(buf);
10807                 invalidateCursor(end, oldStart, newStart);
10808             }
10809         }
10810 
10811         if (selChanged) {
10812             mHighlightPathBogus = true;
10813             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
10814 
10815             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
10816                 if (newSelStart < 0) {
10817                     newSelStart = Selection.getSelectionStart(buf);
10818                 }
10819                 if (newSelEnd < 0) {
10820                     newSelEnd = Selection.getSelectionEnd(buf);
10821                 }
10822 
10823                 if (mEditor != null) {
10824                     mEditor.refreshTextActionMode();
10825                     if (!hasSelection()
10826                             && mEditor.getTextActionMode() == null && hasTransientState()) {
10827                         // User generated selection has been removed.
10828                         setHasTransientState(false);
10829                     }
10830                 }
10831                 onSelectionChanged(newSelStart, newSelEnd);
10832             }
10833         }
10834 
10835         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
10836                 || what instanceof CharacterStyle) {
10837             if (ims == null || ims.mBatchEditNesting == 0) {
10838                 invalidate();
10839                 mHighlightPathBogus = true;
10840                 checkForResize();
10841             } else {
10842                 ims.mContentChanged = true;
10843             }
10844             if (mEditor != null) {
10845                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
10846                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
10847                 mEditor.invalidateHandlesAndActionMode();
10848             }
10849         }
10850 
10851         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
10852             mHighlightPathBogus = true;
10853             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
10854                 ims.mSelectionModeChanged = true;
10855             }
10856 
10857             if (Selection.getSelectionStart(buf) >= 0) {
10858                 if (ims == null || ims.mBatchEditNesting == 0) {
10859                     invalidateCursor();
10860                 } else {
10861                     ims.mCursorChanged = true;
10862                 }
10863             }
10864         }
10865 
10866         if (what instanceof ParcelableSpan) {
10867             // If this is a span that can be sent to a remote process,
10868             // the current extract editor would be interested in it.
10869             if (ims != null && ims.mExtractedTextRequest != null) {
10870                 if (ims.mBatchEditNesting != 0) {
10871                     if (oldStart >= 0) {
10872                         if (ims.mChangedStart > oldStart) {
10873                             ims.mChangedStart = oldStart;
10874                         }
10875                         if (ims.mChangedStart > oldEnd) {
10876                             ims.mChangedStart = oldEnd;
10877                         }
10878                     }
10879                     if (newStart >= 0) {
10880                         if (ims.mChangedStart > newStart) {
10881                             ims.mChangedStart = newStart;
10882                         }
10883                         if (ims.mChangedStart > newEnd) {
10884                             ims.mChangedStart = newEnd;
10885                         }
10886                     }
10887                 } else {
10888                     if (DEBUG_EXTRACT) {
10889                         Log.v(LOG_TAG, "Span change outside of batch: "
10890                                 + oldStart + "-" + oldEnd + ","
10891                                 + newStart + "-" + newEnd + " " + what);
10892                     }
10893                     ims.mContentChanged = true;
10894                 }
10895             }
10896         }
10897 
10898         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
10899                 && what instanceof SpellCheckSpan) {
10900             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
10901         }
10902     }
10903 
10904     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10905     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
10906         if (isTemporarilyDetached()) {
10907             // If we are temporarily in the detach state, then do nothing.
10908             super.onFocusChanged(focused, direction, previouslyFocusedRect);
10909             return;
10910         }
10911 
10912         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
10913 
10914         if (focused) {
10915             if (mSpannable != null) {
10916                 MetaKeyKeyListener.resetMetaState(mSpannable);
10917             }
10918         }
10919 
10920         startStopMarquee(focused);
10921 
10922         if (mTransformation != null) {
10923             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
10924         }
10925 
10926         super.onFocusChanged(focused, direction, previouslyFocusedRect);
10927     }
10928 
10929     @Override
onWindowFocusChanged(boolean hasWindowFocus)10930     public void onWindowFocusChanged(boolean hasWindowFocus) {
10931         super.onWindowFocusChanged(hasWindowFocus);
10932 
10933         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
10934 
10935         startStopMarquee(hasWindowFocus);
10936     }
10937 
10938     @Override
onVisibilityChanged(View changedView, int visibility)10939     protected void onVisibilityChanged(View changedView, int visibility) {
10940         super.onVisibilityChanged(changedView, visibility);
10941         if (mEditor != null && visibility != VISIBLE) {
10942             mEditor.hideCursorAndSpanControllers();
10943             stopTextActionMode();
10944         }
10945     }
10946 
10947     /**
10948      * Use {@link BaseInputConnection#removeComposingSpans
10949      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
10950      * state from this text view.
10951      */
clearComposingText()10952     public void clearComposingText() {
10953         if (mText instanceof Spannable) {
10954             BaseInputConnection.removeComposingSpans(mSpannable);
10955         }
10956     }
10957 
10958     @Override
setSelected(boolean selected)10959     public void setSelected(boolean selected) {
10960         boolean wasSelected = isSelected();
10961 
10962         super.setSelected(selected);
10963 
10964         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10965             if (selected) {
10966                 startMarquee();
10967             } else {
10968                 stopMarquee();
10969             }
10970         }
10971     }
10972 
10973     /**
10974      * Called from onTouchEvent() to prevent the touches by secondary fingers.
10975      * Dragging on handles can revise cursor/selection, so can dragging on the text view.
10976      * This method is a lock to avoid processing multiple fingers on both text view and handles.
10977      * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work.
10978      *
10979      * @param event The motion event that is being handled and carries the pointer info.
10980      * @param fromHandleView true if the event is delivered to selection handle or insertion
10981      * handle; false if this event is delivered to TextView.
10982      * @return Returns true to indicate that onTouchEvent() can continue processing the motion
10983      * event, otherwise false.
10984      *  - Always returns true for the first finger.
10985      *  - For secondary fingers, if the first or current finger is from TextView, returns false.
10986      *    This is to make touch mutually exclusive between the TextView and the handles, but
10987      *    not among the handles.
10988      */
isFromPrimePointer(MotionEvent event, boolean fromHandleView)10989     boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) {
10990         boolean res = true;
10991         if (mPrimePointerId == NO_POINTER_ID)  {
10992             mPrimePointerId = event.getPointerId(0);
10993             mIsPrimePointerFromHandleView = fromHandleView;
10994         } else if (mPrimePointerId != event.getPointerId(0)) {
10995             res = mIsPrimePointerFromHandleView && fromHandleView;
10996         }
10997         if (event.getActionMasked() == MotionEvent.ACTION_UP
10998             || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
10999             mPrimePointerId = -1;
11000         }
11001         return res;
11002     }
11003 
11004     @Override
onTouchEvent(MotionEvent event)11005     public boolean onTouchEvent(MotionEvent event) {
11006         if (DEBUG_CURSOR) {
11007             logCursor("onTouchEvent", "%d: %s (%f,%f)",
11008                     event.getSequenceNumber(),
11009                     MotionEvent.actionToString(event.getActionMasked()),
11010                     event.getX(), event.getY());
11011         }
11012         final int action = event.getActionMasked();
11013         if (mEditor != null) {
11014             if (!isFromPrimePointer(event, false)) {
11015                 return true;
11016             }
11017 
11018             mEditor.onTouchEvent(event);
11019 
11020             if (mEditor.mInsertionPointCursorController != null
11021                     && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
11022                 return true;
11023             }
11024             if (mEditor.mSelectionModifierCursorController != null
11025                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
11026                 return true;
11027             }
11028         }
11029 
11030         final boolean superResult = super.onTouchEvent(event);
11031         if (DEBUG_CURSOR) {
11032             logCursor("onTouchEvent", "superResult=%s", superResult);
11033         }
11034 
11035         /*
11036          * Don't handle the release after a long press, because it will move the selection away from
11037          * whatever the menu action was trying to affect. If the long press should have triggered an
11038          * insertion action mode, we can now actually show it.
11039          */
11040         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
11041             mEditor.mDiscardNextActionUp = false;
11042             if (DEBUG_CURSOR) {
11043                 logCursor("onTouchEvent", "release after long press detected");
11044             }
11045             if (mEditor.mIsInsertionActionModeStartPending) {
11046                 mEditor.startInsertionActionMode();
11047                 mEditor.mIsInsertionActionModeStartPending = false;
11048             }
11049             return superResult;
11050         }
11051 
11052         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
11053                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
11054 
11055         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
11056                 && mText instanceof Spannable && mLayout != null) {
11057             boolean handled = false;
11058 
11059             if (mMovement != null) {
11060                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
11061             }
11062 
11063             final boolean textIsSelectable = isTextSelectable();
11064             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
11065                 // The LinkMovementMethod which should handle taps on links has not been installed
11066                 // on non editable text that support text selection.
11067                 // We reproduce its behavior here to open links for these.
11068                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
11069                     getSelectionEnd(), ClickableSpan.class);
11070 
11071                 if (links.length > 0) {
11072                     links[0].onClick(this);
11073                     handled = true;
11074                 }
11075             }
11076 
11077             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
11078                 // Show the IME, except when selecting in read-only text.
11079                 final InputMethodManager imm = getInputMethodManager();
11080                 viewClicked(imm);
11081                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
11082                     imm.showSoftInput(this, 0);
11083                 }
11084 
11085                 // The above condition ensures that the mEditor is not null
11086                 mEditor.onTouchUpEvent(event);
11087 
11088                 handled = true;
11089             }
11090 
11091             if (handled) {
11092                 return true;
11093             }
11094         }
11095 
11096         return superResult;
11097     }
11098 
11099     @Override
onGenericMotionEvent(MotionEvent event)11100     public boolean onGenericMotionEvent(MotionEvent event) {
11101         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
11102             try {
11103                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
11104                     return true;
11105                 }
11106             } catch (AbstractMethodError ex) {
11107                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
11108                 // Ignore its absence in case third party applications implemented the
11109                 // interface directly.
11110             }
11111         }
11112         return super.onGenericMotionEvent(event);
11113     }
11114 
11115     @Override
onCreateContextMenu(ContextMenu menu)11116     protected void onCreateContextMenu(ContextMenu menu) {
11117         if (mEditor != null) {
11118             mEditor.onCreateContextMenu(menu);
11119         }
11120     }
11121 
11122     @Override
showContextMenu()11123     public boolean showContextMenu() {
11124         if (mEditor != null) {
11125             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
11126         }
11127         return super.showContextMenu();
11128     }
11129 
11130     @Override
showContextMenu(float x, float y)11131     public boolean showContextMenu(float x, float y) {
11132         if (mEditor != null) {
11133             mEditor.setContextMenuAnchor(x, y);
11134         }
11135         return super.showContextMenu(x, y);
11136     }
11137 
11138     /**
11139      * @return True iff this TextView contains a text that can be edited, or if this is
11140      * a selectable TextView.
11141      */
11142     @UnsupportedAppUsage
isTextEditable()11143     boolean isTextEditable() {
11144         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
11145     }
11146 
11147     /**
11148      * Returns true, only while processing a touch gesture, if the initial
11149      * touch down event caused focus to move to the text view and as a result
11150      * its selection changed.  Only valid while processing the touch gesture
11151      * of interest, in an editable text view.
11152      */
didTouchFocusSelect()11153     public boolean didTouchFocusSelect() {
11154         return mEditor != null && mEditor.mTouchFocusSelected;
11155     }
11156 
11157     @Override
cancelLongPress()11158     public void cancelLongPress() {
11159         super.cancelLongPress();
11160         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
11161     }
11162 
11163     @Override
onTrackballEvent(MotionEvent event)11164     public boolean onTrackballEvent(MotionEvent event) {
11165         if (mMovement != null && mSpannable != null && mLayout != null) {
11166             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
11167                 return true;
11168             }
11169         }
11170 
11171         return super.onTrackballEvent(event);
11172     }
11173 
11174     /**
11175      * Sets the Scroller used for producing a scrolling animation
11176      *
11177      * @param s A Scroller instance
11178      */
setScroller(Scroller s)11179     public void setScroller(Scroller s) {
11180         mScroller = s;
11181     }
11182 
11183     @Override
getLeftFadingEdgeStrength()11184     protected float getLeftFadingEdgeStrength() {
11185         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11186             final Marquee marquee = mMarquee;
11187             if (marquee.shouldDrawLeftFade()) {
11188                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
11189             } else {
11190                 return 0.0f;
11191             }
11192         } else if (getLineCount() == 1) {
11193             final float lineLeft = getLayout().getLineLeft(0);
11194             if (lineLeft > mScrollX) return 0.0f;
11195             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
11196         }
11197         return super.getLeftFadingEdgeStrength();
11198     }
11199 
11200     @Override
getRightFadingEdgeStrength()11201     protected float getRightFadingEdgeStrength() {
11202         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11203             final Marquee marquee = mMarquee;
11204             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
11205         } else if (getLineCount() == 1) {
11206             final float rightEdge = mScrollX +
11207                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
11208             final float lineRight = getLayout().getLineRight(0);
11209             if (lineRight < rightEdge) return 0.0f;
11210             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
11211         }
11212         return super.getRightFadingEdgeStrength();
11213     }
11214 
11215     /**
11216      * Calculates the fading edge strength as the ratio of the distance between two
11217      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
11218      * value for the distance calculation.
11219      *
11220      * @param position1 A horizontal position.
11221      * @param position2 A horizontal position.
11222      * @return Fading edge strength between [0.0f, 1.0f].
11223      */
11224     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)11225     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
11226         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
11227         if (horizontalFadingEdgeLength == 0) return 0.0f;
11228         final float diff = Math.abs(position1 - position2);
11229         if (diff > horizontalFadingEdgeLength) return 1.0f;
11230         return diff / horizontalFadingEdgeLength;
11231     }
11232 
isMarqueeFadeEnabled()11233     private boolean isMarqueeFadeEnabled() {
11234         return mEllipsize == TextUtils.TruncateAt.MARQUEE
11235                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
11236     }
11237 
11238     @Override
computeHorizontalScrollRange()11239     protected int computeHorizontalScrollRange() {
11240         if (mLayout != null) {
11241             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
11242                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
11243         }
11244 
11245         return super.computeHorizontalScrollRange();
11246     }
11247 
11248     @Override
computeVerticalScrollRange()11249     protected int computeVerticalScrollRange() {
11250         if (mLayout != null) {
11251             return mLayout.getHeight();
11252         }
11253         return super.computeVerticalScrollRange();
11254     }
11255 
11256     @Override
computeVerticalScrollExtent()11257     protected int computeVerticalScrollExtent() {
11258         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
11259     }
11260 
11261     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11262     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
11263         super.findViewsWithText(outViews, searched, flags);
11264         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
11265                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
11266             String searchedLowerCase = searched.toString().toLowerCase();
11267             String textLowerCase = mText.toString().toLowerCase();
11268             if (textLowerCase.contains(searchedLowerCase)) {
11269                 outViews.add(this);
11270             }
11271         }
11272     }
11273 
11274     /**
11275      * Type of the text buffer that defines the characteristics of the text such as static,
11276      * styleable, or editable.
11277      */
11278     public enum BufferType {
11279         NORMAL, SPANNABLE, EDITABLE
11280     }
11281 
11282     /**
11283      * Returns the TextView_textColor attribute from the TypedArray, if set, or
11284      * the TextAppearance_textColor from the TextView_textAppearance attribute,
11285      * if TextView_textColor was not set directly.
11286      *
11287      * @removed
11288      */
getTextColors(Context context, TypedArray attrs)11289     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
11290         if (attrs == null) {
11291             // Preserve behavior prior to removal of this API.
11292             throw new NullPointerException();
11293         }
11294 
11295         // It's not safe to use this method from apps. The parameter 'attrs'
11296         // must have been obtained using the TextView filter array which is not
11297         // available to the SDK. As such, we grab a default TypedArray with the
11298         // right filter instead here.
11299         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
11300         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
11301         if (colors == null) {
11302             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
11303             if (ap != 0) {
11304                 final TypedArray appearance = context.obtainStyledAttributes(
11305                         ap, R.styleable.TextAppearance);
11306                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
11307                 appearance.recycle();
11308             }
11309         }
11310         a.recycle();
11311 
11312         return colors;
11313     }
11314 
11315     /**
11316      * Returns the default color from the TextView_textColor attribute from the
11317      * AttributeSet, if set, or the default color from the
11318      * TextAppearance_textColor from the TextView_textAppearance attribute, if
11319      * TextView_textColor was not set directly.
11320      *
11321      * @removed
11322      */
getTextColor(Context context, TypedArray attrs, int def)11323     public static int getTextColor(Context context, TypedArray attrs, int def) {
11324         final ColorStateList colors = getTextColors(context, attrs);
11325         if (colors == null) {
11326             return def;
11327         } else {
11328             return colors.getDefaultColor();
11329         }
11330     }
11331 
11332     @Override
onKeyShortcut(int keyCode, KeyEvent event)11333     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
11334         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
11335             // Handle Ctrl-only shortcuts.
11336             switch (keyCode) {
11337                 case KeyEvent.KEYCODE_A:
11338                     if (canSelectText()) {
11339                         return onTextContextMenuItem(ID_SELECT_ALL);
11340                     }
11341                     break;
11342                 case KeyEvent.KEYCODE_Z:
11343                     if (canUndo()) {
11344                         return onTextContextMenuItem(ID_UNDO);
11345                     }
11346                     break;
11347                 case KeyEvent.KEYCODE_X:
11348                     if (canCut()) {
11349                         return onTextContextMenuItem(ID_CUT);
11350                     }
11351                     break;
11352                 case KeyEvent.KEYCODE_C:
11353                     if (canCopy()) {
11354                         return onTextContextMenuItem(ID_COPY);
11355                     }
11356                     break;
11357                 case KeyEvent.KEYCODE_V:
11358                     if (canPaste()) {
11359                         return onTextContextMenuItem(ID_PASTE);
11360                     }
11361                     break;
11362             }
11363         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
11364             // Handle Ctrl-Shift shortcuts.
11365             switch (keyCode) {
11366                 case KeyEvent.KEYCODE_Z:
11367                     if (canRedo()) {
11368                         return onTextContextMenuItem(ID_REDO);
11369                     }
11370                     break;
11371                 case KeyEvent.KEYCODE_V:
11372                     if (canPaste()) {
11373                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
11374                     }
11375             }
11376         }
11377         return super.onKeyShortcut(keyCode, event);
11378     }
11379 
11380     /**
11381      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
11382      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
11383      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
11384      * sufficient.
11385      */
canSelectText()11386     boolean canSelectText() {
11387         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
11388     }
11389 
11390     /**
11391      * Test based on the <i>intrinsic</i> charateristics of the TextView.
11392      * The text must be spannable and the movement method must allow for arbitary selection.
11393      *
11394      * See also {@link #canSelectText()}.
11395      */
textCanBeSelected()11396     boolean textCanBeSelected() {
11397         // prepareCursorController() relies on this method.
11398         // If you change this condition, make sure prepareCursorController is called anywhere
11399         // the value of this condition might be changed.
11400         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
11401         return isTextEditable()
11402                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
11403     }
11404 
11405     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)11406     private Locale getTextServicesLocale(boolean allowNullLocale) {
11407         // Start fetching the text services locale asynchronously.
11408         updateTextServicesLocaleAsync();
11409         // If !allowNullLocale and there is no cached text services locale, just return the default
11410         // locale.
11411         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
11412                 : mCurrentSpellCheckerLocaleCache;
11413     }
11414 
11415     /**
11416      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
11417      * this {@link TextView}.
11418      *
11419      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
11420      * other apps may need to set this so that the system can user right user's resources and
11421      * services such as input methods and spell checkers.</p>
11422      *
11423      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
11424      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
11425      * @hide
11426      */
11427     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)11428     public final void setTextOperationUser(@Nullable UserHandle user) {
11429         if (Objects.equals(mTextOperationUser, user)) {
11430             return;
11431         }
11432         if (user != null && !Process.myUserHandle().equals(user)) {
11433             // Just for preventing people from accidentally using this hidden API without
11434             // the required permission.  The same permission is also checked in the system server.
11435             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
11436                     != PackageManager.PERMISSION_GRANTED) {
11437                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
11438                         + " userId=" + user.getIdentifier()
11439                         + " callingUserId" + UserHandle.myUserId());
11440             }
11441         }
11442         mTextOperationUser = user;
11443         // Invalidate some resources
11444         mCurrentSpellCheckerLocaleCache = null;
11445         if (mEditor != null) {
11446             mEditor.onTextOperationUserChanged();
11447         }
11448     }
11449 
11450     @Nullable
getTextServicesManagerForUser()11451     final TextServicesManager getTextServicesManagerForUser() {
11452         return getServiceManagerForUser("android", TextServicesManager.class);
11453     }
11454 
11455     @Nullable
getClipboardManagerForUser()11456     final ClipboardManager getClipboardManagerForUser() {
11457         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
11458     }
11459 
11460     @Nullable
getTextClassificationManagerForUser()11461     final TextClassificationManager getTextClassificationManagerForUser() {
11462         return getServiceManagerForUser(
11463                 getContext().getPackageName(), TextClassificationManager.class);
11464     }
11465 
11466     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)11467     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
11468         if (mTextOperationUser == null) {
11469             return getContext().getSystemService(managerClazz);
11470         }
11471         try {
11472             Context context = getContext().createPackageContextAsUser(
11473                     packageName, 0 /* flags */, mTextOperationUser);
11474             return context.getSystemService(managerClazz);
11475         } catch (PackageManager.NameNotFoundException e) {
11476             return null;
11477         }
11478     }
11479 
11480     /**
11481      * Starts {@link Activity} as a text-operation user if it is specified with
11482      * {@link #setTextOperationUser(UserHandle)}.
11483      *
11484      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
11485      *
11486      * @param intent The description of the activity to start.
11487      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11488     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
11489         if (mTextOperationUser != null) {
11490             getContext().startActivityAsUser(intent, mTextOperationUser);
11491         } else {
11492             getContext().startActivity(intent);
11493         }
11494     }
11495 
11496     /**
11497      * This is a temporary method. Future versions may support multi-locale text.
11498      * Caveat: This method may not return the latest text services locale, but this should be
11499      * acceptable and it's more important to make this method asynchronous.
11500      *
11501      * @return The locale that should be used for a word iterator
11502      * in this TextView, based on the current spell checker settings,
11503      * the current IME's locale, or the system default locale.
11504      * Please note that a word iterator in this TextView is different from another word iterator
11505      * used by SpellChecker.java of TextView. This method should be used for the former.
11506      * @hide
11507      */
11508     // TODO: Support multi-locale
11509     // TODO: Update the text services locale immediately after the keyboard locale is switched
11510     // by catching intent of keyboard switch event
getTextServicesLocale()11511     public Locale getTextServicesLocale() {
11512         return getTextServicesLocale(false /* allowNullLocale */);
11513     }
11514 
11515     /**
11516      * @return {@code true} if this TextView is specialized for showing and interacting with the
11517      * extracted text in a full-screen input method.
11518      * @hide
11519      */
isInExtractedMode()11520     public boolean isInExtractedMode() {
11521         return false;
11522     }
11523 
11524     /**
11525      * @return {@code true} if this widget supports auto-sizing text and has been configured to
11526      * auto-size.
11527      */
isAutoSizeEnabled()11528     private boolean isAutoSizeEnabled() {
11529         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
11530     }
11531 
11532     /**
11533      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
11534      * @hide
11535      */
supportsAutoSizeText()11536     protected boolean supportsAutoSizeText() {
11537         return true;
11538     }
11539 
11540     /**
11541      * This is a temporary method. Future versions may support multi-locale text.
11542      * Caveat: This method may not return the latest spell checker locale, but this should be
11543      * acceptable and it's more important to make this method asynchronous.
11544      *
11545      * @return The locale that should be used for a spell checker in this TextView,
11546      * based on the current spell checker settings, the current IME's locale, or the system default
11547      * locale.
11548      * @hide
11549      */
getSpellCheckerLocale()11550     public Locale getSpellCheckerLocale() {
11551         return getTextServicesLocale(true /* allowNullLocale */);
11552     }
11553 
updateTextServicesLocaleAsync()11554     private void updateTextServicesLocaleAsync() {
11555         // AsyncTask.execute() uses a serial executor which means we don't have
11556         // to lock around updateTextServicesLocaleLocked() to prevent it from
11557         // being executed n times in parallel.
11558         AsyncTask.execute(new Runnable() {
11559             @Override
11560             public void run() {
11561                 updateTextServicesLocaleLocked();
11562             }
11563         });
11564     }
11565 
11566     @UnsupportedAppUsage
updateTextServicesLocaleLocked()11567     private void updateTextServicesLocaleLocked() {
11568         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
11569         if (textServicesManager == null) {
11570             return;
11571         }
11572         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
11573         final Locale locale;
11574         if (subtype != null) {
11575             locale = subtype.getLocaleObject();
11576         } else {
11577             locale = null;
11578         }
11579         mCurrentSpellCheckerLocaleCache = locale;
11580     }
11581 
onLocaleChanged()11582     void onLocaleChanged() {
11583         mEditor.onLocaleChanged();
11584     }
11585 
11586     /**
11587      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
11588      * Made available to achieve a consistent behavior.
11589      * @hide
11590      */
getWordIterator()11591     public WordIterator getWordIterator() {
11592         if (mEditor != null) {
11593             return mEditor.getWordIterator();
11594         } else {
11595             return null;
11596         }
11597     }
11598 
11599     /** @hide */
11600     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)11601     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
11602         super.onPopulateAccessibilityEventInternal(event);
11603 
11604         final CharSequence text = getTextForAccessibility();
11605         if (!TextUtils.isEmpty(text)) {
11606             event.getText().add(text);
11607         }
11608     }
11609 
11610     @Override
getAccessibilityClassName()11611     public CharSequence getAccessibilityClassName() {
11612         return TextView.class.getName();
11613     }
11614 
11615     /** @hide */
11616     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11617     protected void onProvideStructure(@NonNull ViewStructure structure,
11618             @ViewStructureType int viewFor, int flags) {
11619         super.onProvideStructure(structure, viewFor, flags);
11620 
11621         final boolean isPassword = hasPasswordTransformationMethod()
11622                 || isPasswordInputType(getInputType());
11623         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11624                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11625             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11626                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
11627             }
11628             if (mTextId != Resources.ID_NULL) {
11629                 try {
11630                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
11631                 } catch (Resources.NotFoundException e) {
11632                     if (android.view.autofill.Helper.sVerbose) {
11633                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
11634                                 + mTextId + ": " + e.getMessage());
11635                     }
11636                 }
11637             }
11638         }
11639 
11640         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11641                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11642             if (mLayout == null) {
11643                 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11644                     Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
11645                 }
11646                 assumeLayout();
11647             }
11648             Layout layout = mLayout;
11649             final int lineCount = layout.getLineCount();
11650             if (lineCount <= 1) {
11651                 // Simple case: this is a single line.
11652                 final CharSequence text = getText();
11653                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11654                     structure.setText(text);
11655                 } else {
11656                     structure.setText(text, getSelectionStart(), getSelectionEnd());
11657                 }
11658             } else {
11659                 // Complex case: multi-line, could be scrolled or within a scroll container
11660                 // so some lines are not visible.
11661                 final int[] tmpCords = new int[2];
11662                 getLocationInWindow(tmpCords);
11663                 final int topWindowLocation = tmpCords[1];
11664                 View root = this;
11665                 ViewParent viewParent = getParent();
11666                 while (viewParent instanceof View) {
11667                     root = (View) viewParent;
11668                     viewParent = root.getParent();
11669                 }
11670                 final int windowHeight = root.getHeight();
11671                 final int topLine;
11672                 final int bottomLine;
11673                 if (topWindowLocation >= 0) {
11674                     // The top of the view is fully within its window; start text at line 0.
11675                     topLine = getLineAtCoordinateUnclamped(0);
11676                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
11677                 } else {
11678                     // The top of hte window has scrolled off the top of the window; figure out
11679                     // the starting line for this.
11680                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
11681                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
11682                 }
11683                 // We want to return some contextual lines above/below the lines that are
11684                 // actually visible.
11685                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
11686                 if (expandedTopLine < 0) {
11687                     expandedTopLine = 0;
11688                 }
11689                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
11690                 if (expandedBottomLine >= lineCount) {
11691                     expandedBottomLine = lineCount - 1;
11692                 }
11693 
11694                 // Convert lines into character offsets.
11695                 int expandedTopChar = layout.getLineStart(expandedTopLine);
11696                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
11697 
11698                 // Take into account selection -- if there is a selection, we need to expand
11699                 // the text we are returning to include that selection.
11700                 final int selStart = getSelectionStart();
11701                 final int selEnd = getSelectionEnd();
11702                 if (selStart < selEnd) {
11703                     if (selStart < expandedTopChar) {
11704                         expandedTopChar = selStart;
11705                     }
11706                     if (selEnd > expandedBottomChar) {
11707                         expandedBottomChar = selEnd;
11708                     }
11709                 }
11710 
11711                 // Get the text and trim it to the range we are reporting.
11712                 CharSequence text = getText();
11713                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
11714                     text = text.subSequence(expandedTopChar, expandedBottomChar);
11715                 }
11716 
11717                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11718                     structure.setText(text);
11719                 } else {
11720                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
11721 
11722                     final int[] lineOffsets = new int[bottomLine - topLine + 1];
11723                     final int[] lineBaselines = new int[bottomLine - topLine + 1];
11724                     final int baselineOffset = getBaselineOffset();
11725                     for (int i = topLine; i <= bottomLine; i++) {
11726                         lineOffsets[i - topLine] = layout.getLineStart(i);
11727                         lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
11728                     }
11729                     structure.setTextLines(lineOffsets, lineBaselines);
11730                 }
11731             }
11732 
11733             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
11734                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11735                 // Extract style information that applies to the TextView as a whole.
11736                 int style = 0;
11737                 int typefaceStyle = getTypefaceStyle();
11738                 if ((typefaceStyle & Typeface.BOLD) != 0) {
11739                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11740                 }
11741                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
11742                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
11743                 }
11744 
11745                 // Global styles can also be set via TextView.setPaintFlags().
11746                 int paintFlags = mTextPaint.getFlags();
11747                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
11748                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11749                 }
11750                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
11751                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
11752                 }
11753                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
11754                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
11755                 }
11756 
11757                 // TextView does not have its own text background color. A background is either part
11758                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
11759                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
11760                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
11761             }
11762             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
11763                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
11764                 structure.setMinTextEms(getMinEms());
11765                 structure.setMaxTextEms(getMaxEms());
11766                 int maxLength = -1;
11767                 for (InputFilter filter: getFilters()) {
11768                     if (filter instanceof InputFilter.LengthFilter) {
11769                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
11770                         break;
11771                     }
11772                 }
11773                 structure.setMaxTextLength(maxLength);
11774             }
11775         }
11776         if (mHintId != Resources.ID_NULL) {
11777             try {
11778                 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId));
11779             } catch (Resources.NotFoundException e) {
11780                 if (android.view.autofill.Helper.sVerbose) {
11781                     Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id "
11782                             + mHintId + ": " + e.getMessage());
11783                 }
11784             }
11785         }
11786         structure.setHint(getHint());
11787         structure.setInputType(getInputType());
11788     }
11789 
canRequestAutofill()11790     boolean canRequestAutofill() {
11791         if (!isAutofillable()) {
11792             return false;
11793         }
11794         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11795         if (afm != null) {
11796             return afm.isEnabled();
11797         }
11798         return false;
11799     }
11800 
requestAutofill()11801     private void requestAutofill() {
11802         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11803         if (afm != null) {
11804             afm.requestAutofill(this);
11805         }
11806     }
11807 
11808     @Override
autofill(AutofillValue value)11809     public void autofill(AutofillValue value) {
11810         if (!value.isText() || !isTextEditable()) {
11811             Log.w(LOG_TAG, value + " could not be autofilled into " + this);
11812             return;
11813         }
11814 
11815         final CharSequence autofilledValue = value.getTextValue();
11816 
11817         // First autofill it...
11818         setText(autofilledValue, mBufferType, true, 0);
11819 
11820         // ...then move cursor to the end.
11821         final CharSequence text = getText();
11822         if ((text instanceof Spannable)) {
11823             Selection.setSelection((Spannable) text, text.length());
11824         }
11825     }
11826 
11827     @Override
getAutofillType()11828     public @AutofillType int getAutofillType() {
11829         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
11830     }
11831 
11832     /**
11833      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
11834      * {@code char}s if longer.
11835      *
11836      * @return current text, {@code null} if the text is not editable
11837      *
11838      * @see View#getAutofillValue()
11839      */
11840     @Override
11841     @Nullable
getAutofillValue()11842     public AutofillValue getAutofillValue() {
11843         if (isTextEditable()) {
11844             final CharSequence text = TextUtils.trimToParcelableSize(getText());
11845             return AutofillValue.forText(text);
11846         }
11847         return null;
11848     }
11849 
11850     /** @hide */
11851     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)11852     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
11853         super.onInitializeAccessibilityEventInternal(event);
11854 
11855         final boolean isPassword = hasPasswordTransformationMethod();
11856         event.setPassword(isPassword);
11857 
11858         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
11859             event.setFromIndex(Selection.getSelectionStart(mText));
11860             event.setToIndex(Selection.getSelectionEnd(mText));
11861             event.setItemCount(mText.length());
11862         }
11863     }
11864 
11865     /** @hide */
11866     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11867     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
11868         super.onInitializeAccessibilityNodeInfoInternal(info);
11869 
11870         final boolean isPassword = hasPasswordTransformationMethod();
11871         info.setPassword(isPassword);
11872         info.setText(getTextForAccessibility());
11873         info.setHintText(mHint);
11874         info.setShowingHintText(isShowingHint());
11875 
11876         if (mBufferType == BufferType.EDITABLE) {
11877             info.setEditable(true);
11878             if (isEnabled()) {
11879                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
11880             }
11881         }
11882 
11883         if (mEditor != null) {
11884             info.setInputType(mEditor.mInputType);
11885 
11886             if (mEditor.mError != null) {
11887                 info.setContentInvalid(true);
11888                 info.setError(mEditor.mError);
11889             }
11890             // TextView will expose this action if it is editable and has focus.
11891             if (isTextEditable() && isFocused()) {
11892                 CharSequence imeActionLabel = mContext.getResources().getString(
11893                         com.android.internal.R.string.keyboardview_keycode_enter);
11894                 if (getImeActionLabel() != null) {
11895                     imeActionLabel = getImeActionLabel();
11896                 }
11897                 AccessibilityNodeInfo.AccessibilityAction action =
11898                         new AccessibilityNodeInfo.AccessibilityAction(
11899                                 R.id.accessibilityActionImeEnter, imeActionLabel);
11900                 info.addAction(action);
11901             }
11902         }
11903 
11904         if (!TextUtils.isEmpty(mText)) {
11905             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
11906             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
11907             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
11908                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
11909                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
11910                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
11911                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
11912             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
11913             info.setAvailableExtraData(Arrays.asList(
11914                     EXTRA_DATA_RENDERING_INFO_KEY,
11915                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
11916             ));
11917         } else {
11918             info.setAvailableExtraData(Arrays.asList(
11919                     EXTRA_DATA_RENDERING_INFO_KEY
11920             ));
11921         }
11922 
11923         if (isFocused()) {
11924             if (canCopy()) {
11925                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
11926             }
11927             if (canPaste()) {
11928                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
11929             }
11930             if (canCut()) {
11931                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
11932             }
11933             if (canShare()) {
11934                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
11935                         ACCESSIBILITY_ACTION_SHARE,
11936                         getResources().getString(com.android.internal.R.string.share)));
11937             }
11938             if (canProcessText()) {  // also implies mEditor is not null.
11939                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
11940             }
11941         }
11942 
11943         // Check for known input filter types.
11944         final int numFilters = mFilters.length;
11945         for (int i = 0; i < numFilters; i++) {
11946             final InputFilter filter = mFilters[i];
11947             if (filter instanceof InputFilter.LengthFilter) {
11948                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
11949             }
11950         }
11951 
11952         if (!isSingleLine()) {
11953             info.setMultiLine(true);
11954         }
11955 
11956         // A view should not be exposed as clickable/long-clickable to a service because of a
11957         // LinkMovementMethod.
11958         if ((info.isClickable() || info.isLongClickable())
11959                 && mMovement instanceof LinkMovementMethod) {
11960             if (!hasOnClickListeners()) {
11961                 info.setClickable(false);
11962                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
11963             }
11964             if (!hasOnLongClickListeners()) {
11965                 info.setLongClickable(false);
11966                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
11967             }
11968         }
11969     }
11970 
11971     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11972     public void addExtraDataToAccessibilityNodeInfo(
11973             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
11974         if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
11975             int positionInfoStartIndex = arguments.getInt(
11976                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
11977             int positionInfoLength = arguments.getInt(
11978                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
11979             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
11980                     || (positionInfoStartIndex >= mText.length())) {
11981                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
11982                 return;
11983             }
11984             RectF[] boundingRects = new RectF[positionInfoLength];
11985             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
11986             populateCharacterBounds(builder, positionInfoStartIndex,
11987                     positionInfoStartIndex + positionInfoLength,
11988                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
11989             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
11990             for (int i = 0; i < positionInfoLength; i++) {
11991                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
11992                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
11993                     RectF bounds = cursorAnchorInfo
11994                             .getCharacterBounds(positionInfoStartIndex + i);
11995                     if (bounds != null) {
11996                         mapRectFromViewToScreenCoords(bounds, true);
11997                         boundingRects[i] = bounds;
11998                     }
11999                 }
12000             }
12001             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
12002             return;
12003         }
12004         if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) {
12005             final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo =
12006                     AccessibilityNodeInfo.ExtraRenderingInfo.obtain();
12007             extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height);
12008             extraRenderingInfo.setTextSizeInPx(getTextSize());
12009             extraRenderingInfo.setTextSizeUnit(getTextSizeUnit());
12010             info.setExtraRenderingInfo(extraRenderingInfo);
12011         }
12012     }
12013 
12014     /**
12015      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
12016      *
12017      * @param builder The builder to populate
12018      * @param startIndex The starting character index to populate
12019      * @param endIndex The ending character index to populate
12020      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
12021      * content
12022      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
12023      * @hide
12024      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)12025     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
12026             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
12027             float viewportToContentVerticalOffset) {
12028         final int minLine = mLayout.getLineForOffset(startIndex);
12029         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
12030         for (int line = minLine; line <= maxLine; ++line) {
12031             final int lineStart = mLayout.getLineStart(line);
12032             final int lineEnd = mLayout.getLineEnd(line);
12033             final int offsetStart = Math.max(lineStart, startIndex);
12034             final int offsetEnd = Math.min(lineEnd, endIndex);
12035             final boolean ltrLine =
12036                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
12037             final float[] widths = new float[offsetEnd - offsetStart];
12038             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
12039             final float top = mLayout.getLineTop(line);
12040             final float bottom = mLayout.getLineBottom(line);
12041             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
12042                 final float charWidth = widths[offset - offsetStart];
12043                 final boolean isRtl = mLayout.isRtlCharAt(offset);
12044                 final float primary = mLayout.getPrimaryHorizontal(offset);
12045                 final float secondary = mLayout.getSecondaryHorizontal(offset);
12046                 // TODO: This doesn't work perfectly for text with custom styles and
12047                 // TAB chars.
12048                 final float left;
12049                 final float right;
12050                 if (ltrLine) {
12051                     if (isRtl) {
12052                         left = secondary - charWidth;
12053                         right = secondary;
12054                     } else {
12055                         left = primary;
12056                         right = primary + charWidth;
12057                     }
12058                 } else {
12059                     if (!isRtl) {
12060                         left = secondary;
12061                         right = secondary + charWidth;
12062                     } else {
12063                         left = primary - charWidth;
12064                         right = primary;
12065                     }
12066                 }
12067                 // TODO: Check top-right and bottom-left as well.
12068                 final float localLeft = left + viewportToContentHorizontalOffset;
12069                 final float localRight = right + viewportToContentHorizontalOffset;
12070                 final float localTop = top + viewportToContentVerticalOffset;
12071                 final float localBottom = bottom + viewportToContentVerticalOffset;
12072                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
12073                 final boolean isBottomRightVisible =
12074                         isPositionVisible(localRight, localBottom);
12075                 int characterBoundsFlags = 0;
12076                 if (isTopLeftVisible || isBottomRightVisible) {
12077                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
12078                 }
12079                 if (!isTopLeftVisible || !isBottomRightVisible) {
12080                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
12081                 }
12082                 if (isRtl) {
12083                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
12084                 }
12085                 // Here offset is the index in Java chars.
12086                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
12087                         localBottom, characterBoundsFlags);
12088             }
12089         }
12090     }
12091 
12092     /**
12093      * @hide
12094      */
isPositionVisible(final float positionX, final float positionY)12095     public boolean isPositionVisible(final float positionX, final float positionY) {
12096         synchronized (TEMP_POSITION) {
12097             final float[] position = TEMP_POSITION;
12098             position[0] = positionX;
12099             position[1] = positionY;
12100             View view = this;
12101 
12102             while (view != null) {
12103                 if (view != this) {
12104                     // Local scroll is already taken into account in positionX/Y
12105                     position[0] -= view.getScrollX();
12106                     position[1] -= view.getScrollY();
12107                 }
12108 
12109                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
12110                         || position[1] > view.getHeight()) {
12111                     return false;
12112                 }
12113 
12114                 if (!view.getMatrix().isIdentity()) {
12115                     view.getMatrix().mapPoints(position);
12116                 }
12117 
12118                 position[0] += view.getLeft();
12119                 position[1] += view.getTop();
12120 
12121                 final ViewParent parent = view.getParent();
12122                 if (parent instanceof View) {
12123                     view = (View) parent;
12124                 } else {
12125                     // We've reached the ViewRoot, stop iterating
12126                     view = null;
12127                 }
12128             }
12129         }
12130 
12131         // We've been able to walk up the view hierarchy and the position was never clipped
12132         return true;
12133     }
12134 
12135     /**
12136      * Performs an accessibility action after it has been offered to the
12137      * delegate.
12138      *
12139      * @hide
12140      */
12141     @Override
performAccessibilityActionInternal(int action, Bundle arguments)12142     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
12143         if (mEditor != null
12144                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
12145             return true;
12146         }
12147         switch (action) {
12148             case AccessibilityNodeInfo.ACTION_CLICK: {
12149                 return performAccessibilityActionClick(arguments);
12150             }
12151             case AccessibilityNodeInfo.ACTION_COPY: {
12152                 if (isFocused() && canCopy()) {
12153                     if (onTextContextMenuItem(ID_COPY)) {
12154                         return true;
12155                     }
12156                 }
12157             } return false;
12158             case AccessibilityNodeInfo.ACTION_PASTE: {
12159                 if (isFocused() && canPaste()) {
12160                     if (onTextContextMenuItem(ID_PASTE)) {
12161                         return true;
12162                     }
12163                 }
12164             } return false;
12165             case AccessibilityNodeInfo.ACTION_CUT: {
12166                 if (isFocused() && canCut()) {
12167                     if (onTextContextMenuItem(ID_CUT)) {
12168                         return true;
12169                     }
12170                 }
12171             } return false;
12172             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
12173                 ensureIterableTextForAccessibilitySelectable();
12174                 CharSequence text = getIterableTextForAccessibility();
12175                 if (text == null) {
12176                     return false;
12177                 }
12178                 final int start = (arguments != null) ? arguments.getInt(
12179                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
12180                 final int end = (arguments != null) ? arguments.getInt(
12181                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
12182                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
12183                     // No arguments clears the selection.
12184                     if (start == end && end == -1) {
12185                         Selection.removeSelection((Spannable) text);
12186                         return true;
12187                     }
12188                     if (start >= 0 && start <= end && end <= text.length()) {
12189                         Selection.setSelection((Spannable) text, start, end);
12190                         // Make sure selection mode is engaged.
12191                         if (mEditor != null) {
12192                             mEditor.startSelectionActionModeAsync(false);
12193                         }
12194                         return true;
12195                     }
12196                 }
12197             } return false;
12198             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
12199             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
12200                 ensureIterableTextForAccessibilitySelectable();
12201                 return super.performAccessibilityActionInternal(action, arguments);
12202             }
12203             case ACCESSIBILITY_ACTION_SHARE: {
12204                 if (isFocused() && canShare()) {
12205                     if (onTextContextMenuItem(ID_SHARE)) {
12206                         return true;
12207                     }
12208                 }
12209             } return false;
12210             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
12211                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
12212                     return false;
12213                 }
12214                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
12215                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
12216                 setText(text);
12217                 if (mText != null) {
12218                     int updatedTextLength = mText.length();
12219                     if (updatedTextLength > 0) {
12220                         Selection.setSelection(mSpannable, updatedTextLength);
12221                     }
12222                 }
12223             } return true;
12224             case R.id.accessibilityActionImeEnter: {
12225                 if (isFocused() && isTextEditable()) {
12226                     onEditorAction(getImeActionId());
12227                 }
12228             } return true;
12229             case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
12230                 if (isLongClickable()) {
12231                     boolean handled;
12232                     if (isEnabled() && (mBufferType == BufferType.EDITABLE)) {
12233                         mEditor.mIsBeingLongClickedByAccessibility = true;
12234                         try {
12235                             handled = performLongClick();
12236                         } finally {
12237                             mEditor.mIsBeingLongClickedByAccessibility = false;
12238                         }
12239                     } else {
12240                         handled = performLongClick();
12241                     }
12242                     return handled;
12243                 }
12244             }
12245             return false;
12246             default: {
12247                 return super.performAccessibilityActionInternal(action, arguments);
12248             }
12249         }
12250     }
12251 
performAccessibilityActionClick(Bundle arguments)12252     private boolean performAccessibilityActionClick(Bundle arguments) {
12253         boolean handled = false;
12254 
12255         if (!isEnabled()) {
12256             return false;
12257         }
12258 
12259         if (isClickable() || isLongClickable()) {
12260             // Simulate View.onTouchEvent for an ACTION_UP event
12261             if (isFocusable() && !isFocused()) {
12262                 requestFocus();
12263             }
12264 
12265             performClick();
12266             handled = true;
12267         }
12268 
12269         // Show the IME, except when selecting in read-only text.
12270         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
12271                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
12272             final InputMethodManager imm = getInputMethodManager();
12273             viewClicked(imm);
12274             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
12275                 handled |= imm.showSoftInput(this, 0);
12276             }
12277         }
12278 
12279         return handled;
12280     }
12281 
hasSpannableText()12282     private boolean hasSpannableText() {
12283         return mText != null && mText instanceof Spannable;
12284     }
12285 
12286     /** @hide */
12287     @Override
sendAccessibilityEventInternal(int eventType)12288     public void sendAccessibilityEventInternal(int eventType) {
12289         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
12290             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
12291         }
12292 
12293         super.sendAccessibilityEventInternal(eventType);
12294     }
12295 
12296     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)12297     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
12298         // Do not send scroll events since first they are not interesting for
12299         // accessibility and second such events a generated too frequently.
12300         // For details see the implementation of bringTextIntoView().
12301         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
12302             return;
12303         }
12304         super.sendAccessibilityEventUnchecked(event);
12305     }
12306 
12307     /**
12308      * Returns the text that should be exposed to accessibility services.
12309      * <p>
12310      * This approximates what is displayed visually. If the user has specified
12311      * that accessibility services should speak passwords, this method will
12312      * bypass any password transformation method and return unobscured text.
12313      *
12314      * @return the text that should be exposed to accessibility services, may
12315      *         be {@code null} if no text is set
12316      */
12317     @Nullable
12318     @UnsupportedAppUsage
getTextForAccessibility()12319     private CharSequence getTextForAccessibility() {
12320         // If the text is empty, we must be showing the hint text.
12321         if (TextUtils.isEmpty(mText)) {
12322             return mHint;
12323         }
12324 
12325         // Otherwise, return whatever text is being displayed.
12326         return TextUtils.trimToParcelableSize(mTransformed);
12327     }
12328 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12329     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
12330             int fromIndex, int removedCount, int addedCount) {
12331         AccessibilityEvent event =
12332                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
12333         event.setFromIndex(fromIndex);
12334         event.setRemovedCount(removedCount);
12335         event.setAddedCount(addedCount);
12336         event.setBeforeText(beforeText);
12337         sendAccessibilityEventUnchecked(event);
12338     }
12339 
getInputMethodManager()12340     private InputMethodManager getInputMethodManager() {
12341         return getContext().getSystemService(InputMethodManager.class);
12342     }
12343 
12344     /**
12345      * Returns whether this text view is a current input method target.  The
12346      * default implementation just checks with {@link InputMethodManager}.
12347      * @return True if the TextView is a current input method target; false otherwise.
12348      */
isInputMethodTarget()12349     public boolean isInputMethodTarget() {
12350         InputMethodManager imm = getInputMethodManager();
12351         return imm != null && imm.isActive(this);
12352     }
12353 
12354     static final int ID_SELECT_ALL = android.R.id.selectAll;
12355     static final int ID_UNDO = android.R.id.undo;
12356     static final int ID_REDO = android.R.id.redo;
12357     static final int ID_CUT = android.R.id.cut;
12358     static final int ID_COPY = android.R.id.copy;
12359     static final int ID_PASTE = android.R.id.paste;
12360     static final int ID_SHARE = android.R.id.shareText;
12361     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
12362     static final int ID_REPLACE = android.R.id.replaceText;
12363     static final int ID_ASSIST = android.R.id.textAssist;
12364     static final int ID_AUTOFILL = android.R.id.autofill;
12365 
12366     /**
12367      * Called when a context menu option for the text view is selected.  Currently
12368      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
12369      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
12370      *
12371      * @return true if the context menu item action was performed.
12372      */
onTextContextMenuItem(int id)12373     public boolean onTextContextMenuItem(int id) {
12374         int min = 0;
12375         int max = mText.length();
12376 
12377         if (isFocused()) {
12378             final int selStart = getSelectionStart();
12379             final int selEnd = getSelectionEnd();
12380 
12381             min = Math.max(0, Math.min(selStart, selEnd));
12382             max = Math.max(0, Math.max(selStart, selEnd));
12383         }
12384 
12385         switch (id) {
12386             case ID_SELECT_ALL:
12387                 final boolean hadSelection = hasSelection();
12388                 selectAllText();
12389                 if (mEditor != null && hadSelection) {
12390                     mEditor.invalidateActionModeAsync();
12391                 }
12392                 return true;
12393 
12394             case ID_UNDO:
12395                 if (mEditor != null) {
12396                     mEditor.undo();
12397                 }
12398                 return true;  // Returns true even if nothing was undone.
12399 
12400             case ID_REDO:
12401                 if (mEditor != null) {
12402                     mEditor.redo();
12403                 }
12404                 return true;  // Returns true even if nothing was undone.
12405 
12406             case ID_PASTE:
12407                 paste(min, max, true /* withFormatting */);
12408                 return true;
12409 
12410             case ID_PASTE_AS_PLAIN_TEXT:
12411                 paste(min, max, false /* withFormatting */);
12412                 return true;
12413 
12414             case ID_CUT:
12415                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
12416                 if (setPrimaryClip(cutData)) {
12417                     deleteText_internal(min, max);
12418                 } else {
12419                     Toast.makeText(getContext(),
12420                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12421                             Toast.LENGTH_SHORT).show();
12422                 }
12423                 return true;
12424 
12425             case ID_COPY:
12426                 // For link action mode in a non-selectable/non-focusable TextView,
12427                 // make sure that we set the appropriate min/max.
12428                 final int selStart = getSelectionStart();
12429                 final int selEnd = getSelectionEnd();
12430                 min = Math.max(0, Math.min(selStart, selEnd));
12431                 max = Math.max(0, Math.max(selStart, selEnd));
12432                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
12433                 if (setPrimaryClip(copyData)) {
12434                     stopTextActionMode();
12435                 } else {
12436                     Toast.makeText(getContext(),
12437                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12438                             Toast.LENGTH_SHORT).show();
12439                 }
12440                 return true;
12441 
12442             case ID_REPLACE:
12443                 if (mEditor != null) {
12444                     mEditor.replace();
12445                 }
12446                 return true;
12447 
12448             case ID_SHARE:
12449                 shareSelectedText();
12450                 return true;
12451 
12452             case ID_AUTOFILL:
12453                 requestAutofill();
12454                 stopTextActionMode();
12455                 return true;
12456         }
12457         return false;
12458     }
12459 
12460     @UnsupportedAppUsage
getTransformedText(int start, int end)12461     CharSequence getTransformedText(int start, int end) {
12462         return removeSuggestionSpans(mTransformed.subSequence(start, end));
12463     }
12464 
12465     @Override
performLongClick()12466     public boolean performLongClick() {
12467         if (DEBUG_CURSOR) {
12468             logCursor("performLongClick", null);
12469         }
12470 
12471         boolean handled = false;
12472         boolean performedHapticFeedback = false;
12473 
12474         if (mEditor != null) {
12475             mEditor.mIsBeingLongClicked = true;
12476         }
12477 
12478         if (super.performLongClick()) {
12479             handled = true;
12480             performedHapticFeedback = true;
12481         }
12482 
12483         if (mEditor != null) {
12484             handled |= mEditor.performLongClick(handled);
12485             mEditor.mIsBeingLongClicked = false;
12486         }
12487 
12488         if (handled) {
12489             if (!performedHapticFeedback) {
12490               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
12491             }
12492             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
12493         } else {
12494             MetricsLogger.action(
12495                     mContext,
12496                     MetricsEvent.TEXT_LONGPRESS,
12497                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
12498         }
12499 
12500         return handled;
12501     }
12502 
12503     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12504     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
12505         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
12506         if (mEditor != null) {
12507             mEditor.onScrollChanged();
12508         }
12509     }
12510 
12511     /**
12512      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
12513      * by the IME or by the spell checker as the user types. This is done by adding
12514      * {@link SuggestionSpan}s to the text.
12515      *
12516      * When suggestions are enabled (default), this list of suggestions will be displayed when the
12517      * user asks for them on these parts of the text. This value depends on the inputType of this
12518      * TextView.
12519      *
12520      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
12521      *
12522      * In addition, the type variation must be one of
12523      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
12524      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
12525      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
12526      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
12527      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
12528      *
12529      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
12530      *
12531      * @return true if the suggestions popup window is enabled, based on the inputType.
12532      */
isSuggestionsEnabled()12533     public boolean isSuggestionsEnabled() {
12534         if (mEditor == null) return false;
12535         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
12536             return false;
12537         }
12538         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
12539 
12540         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
12541         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
12542                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
12543                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
12544                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
12545                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
12546     }
12547 
12548     /**
12549      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12550      * selection is initiated in this View.
12551      *
12552      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
12553      * Paste, Replace and Share actions, depending on what this View supports.
12554      *
12555      * <p>A custom implementation can add new entries in the default menu in its
12556      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
12557      * method. The default actions can also be removed from the menu using
12558      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12559      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
12560      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
12561      *
12562      * <p>Returning false from
12563      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
12564      * will prevent the action mode from being started.
12565      *
12566      * <p>Action click events should be handled by the custom implementation of
12567      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
12568      * android.view.MenuItem)}.
12569      *
12570      * <p>Note that text selection mode is not started when a TextView receives focus and the
12571      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
12572      * that case, to allow for quick replacement.
12573      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12574     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
12575         createEditorIfNeeded();
12576         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
12577     }
12578 
12579     /**
12580      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
12581      *
12582      * @return The current custom selection callback.
12583      */
getCustomSelectionActionModeCallback()12584     public ActionMode.Callback getCustomSelectionActionModeCallback() {
12585         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
12586     }
12587 
12588     /**
12589      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12590      * insertion is initiated in this View.
12591      * The standard implementation populates the menu with a subset of Select All,
12592      * Paste and Replace actions, depending on what this View supports.
12593      *
12594      * <p>A custom implementation can add new entries in the default menu in its
12595      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
12596      * android.view.Menu)} method. The default actions can also be removed from the menu using
12597      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12598      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
12599      *
12600      * <p>Returning false from
12601      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
12602      * android.view.Menu)} will prevent the action mode from being started.</p>
12603      *
12604      * <p>Action click events should be handled by the custom implementation of
12605      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
12606      * android.view.MenuItem)}.</p>
12607      *
12608      * <p>Note that text insertion mode is not started when a TextView receives focus and the
12609      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
12610      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12611     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
12612         createEditorIfNeeded();
12613         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
12614     }
12615 
12616     /**
12617      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
12618      *
12619      * @return The current custom insertion callback.
12620      */
getCustomInsertionActionModeCallback()12621     public ActionMode.Callback getCustomInsertionActionModeCallback() {
12622         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
12623     }
12624 
12625     /**
12626      * Sets the {@link TextClassifier} for this TextView.
12627      */
setTextClassifier(@ullable TextClassifier textClassifier)12628     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
12629         mTextClassifier = textClassifier;
12630     }
12631 
12632     /**
12633      * Returns the {@link TextClassifier} used by this TextView.
12634      * If no TextClassifier has been set, this TextView uses the default set by the
12635      * {@link TextClassificationManager}.
12636      */
12637     @NonNull
getTextClassifier()12638     public TextClassifier getTextClassifier() {
12639         if (mTextClassifier == null) {
12640             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12641             if (tcm != null) {
12642                 return tcm.getTextClassifier();
12643             }
12644             return TextClassifier.NO_OP;
12645         }
12646         return mTextClassifier;
12647     }
12648 
12649     /**
12650      * Returns a session-aware text classifier.
12651      * This method creates one if none already exists or the current one is destroyed.
12652      */
12653     @NonNull
getTextClassificationSession()12654     TextClassifier getTextClassificationSession() {
12655         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
12656             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12657             if (tcm != null) {
12658                 final String widgetType;
12659                 if (isTextEditable()) {
12660                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
12661                 } else if (isTextSelectable()) {
12662                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
12663                 } else {
12664                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
12665                 }
12666                 mTextClassificationContext = new TextClassificationContext.Builder(
12667                         mContext.getPackageName(), widgetType)
12668                         .build();
12669                 if (mTextClassifier != null) {
12670                     mTextClassificationSession = tcm.createTextClassificationSession(
12671                             mTextClassificationContext, mTextClassifier);
12672                 } else {
12673                     mTextClassificationSession = tcm.createTextClassificationSession(
12674                             mTextClassificationContext);
12675                 }
12676             } else {
12677                 mTextClassificationSession = TextClassifier.NO_OP;
12678             }
12679         }
12680         return mTextClassificationSession;
12681     }
12682 
12683     /**
12684      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
12685      * @see #getTextClassificationSession()
12686      */
12687     @Nullable
getTextClassificationContext()12688     TextClassificationContext getTextClassificationContext() {
12689         return mTextClassificationContext;
12690     }
12691 
12692     /**
12693      * Returns true if this TextView uses a no-op TextClassifier.
12694      */
usesNoOpTextClassifier()12695     boolean usesNoOpTextClassifier() {
12696         return getTextClassifier() == TextClassifier.NO_OP;
12697     }
12698 
12699     /**
12700      * Starts an ActionMode for the specified TextLinkSpan.
12701      *
12702      * @return Whether or not we're attempting to start the action mode.
12703      * @hide
12704      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12705     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12706         Preconditions.checkNotNull(clickedSpan);
12707 
12708         if (!(mText instanceof Spanned)) {
12709             return false;
12710         }
12711 
12712         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
12713         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
12714 
12715         if (start < 0 || end > mText.length() || start >= end) {
12716             return false;
12717         }
12718 
12719         createEditorIfNeeded();
12720         mEditor.startLinkActionModeAsync(start, end);
12721         return true;
12722     }
12723 
12724     /**
12725      * Handles a click on the specified TextLinkSpan.
12726      *
12727      * @return Whether or not the click is being handled.
12728      * @hide
12729      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12730     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12731         Preconditions.checkNotNull(clickedSpan);
12732         if (mText instanceof Spanned) {
12733             final Spanned spanned = (Spanned) mText;
12734             final int start = spanned.getSpanStart(clickedSpan);
12735             final int end = spanned.getSpanEnd(clickedSpan);
12736             if (start >= 0 && end <= mText.length() && start < end) {
12737                 final TextClassification.Request request = new TextClassification.Request.Builder(
12738                         mText, start, end)
12739                         .setDefaultLocales(getTextLocales())
12740                         .build();
12741                 final Supplier<TextClassification> supplier = () ->
12742                         getTextClassificationSession().classifyText(request);
12743                 final Consumer<TextClassification> consumer = classification -> {
12744                     if (classification != null) {
12745                         if (!classification.getActions().isEmpty()) {
12746                             try {
12747                                 classification.getActions().get(0).getActionIntent().send();
12748                             } catch (PendingIntent.CanceledException e) {
12749                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
12750                             }
12751                         } else {
12752                             Log.d(LOG_TAG, "No link action to perform");
12753                         }
12754                     } else {
12755                         // classification == null
12756                         Log.d(LOG_TAG, "Timeout while classifying text");
12757                     }
12758                 };
12759                 CompletableFuture.supplyAsync(supplier)
12760                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
12761                         .thenAccept(consumer);
12762                 return true;
12763             }
12764         }
12765         return false;
12766     }
12767 
12768     /**
12769      * @hide
12770      */
12771     @UnsupportedAppUsage
stopTextActionMode()12772     protected void stopTextActionMode() {
12773         if (mEditor != null) {
12774             mEditor.stopTextActionMode();
12775         }
12776     }
12777 
12778     /** @hide */
hideFloatingToolbar(int durationMs)12779     public void hideFloatingToolbar(int durationMs) {
12780         if (mEditor != null) {
12781             mEditor.hideFloatingToolbar(durationMs);
12782         }
12783     }
12784 
canUndo()12785     boolean canUndo() {
12786         return mEditor != null && mEditor.canUndo();
12787     }
12788 
canRedo()12789     boolean canRedo() {
12790         return mEditor != null && mEditor.canRedo();
12791     }
12792 
canCut()12793     boolean canCut() {
12794         if (hasPasswordTransformationMethod()) {
12795             return false;
12796         }
12797 
12798         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
12799                 && mEditor.mKeyListener != null) {
12800             return true;
12801         }
12802 
12803         return false;
12804     }
12805 
canCopy()12806     boolean canCopy() {
12807         if (hasPasswordTransformationMethod()) {
12808             return false;
12809         }
12810 
12811         if (mText.length() > 0 && hasSelection() && mEditor != null) {
12812             return true;
12813         }
12814 
12815         return false;
12816     }
12817 
canShare()12818     boolean canShare() {
12819         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
12820             return false;
12821         }
12822         return canCopy();
12823     }
12824 
isDeviceProvisioned()12825     boolean isDeviceProvisioned() {
12826         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
12827             mDeviceProvisionedState = Settings.Global.getInt(
12828                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
12829                     ? DEVICE_PROVISIONED_YES
12830                     : DEVICE_PROVISIONED_NO;
12831         }
12832         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
12833     }
12834 
12835     @UnsupportedAppUsage
canPaste()12836     boolean canPaste() {
12837         return (mText instanceof Editable
12838                 && mEditor != null && mEditor.mKeyListener != null
12839                 && getSelectionStart() >= 0
12840                 && getSelectionEnd() >= 0
12841                 && getClipboardManagerForUser().hasPrimaryClip());
12842     }
12843 
canPasteAsPlainText()12844     boolean canPasteAsPlainText() {
12845         if (!canPaste()) {
12846             return false;
12847         }
12848 
12849         final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
12850         final ClipDescription description = clipData.getDescription();
12851         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
12852         final CharSequence text = clipData.getItemAt(0).getText();
12853         if (isPlainType && (text instanceof Spanned)) {
12854             Spanned spanned = (Spanned) text;
12855             if (TextUtils.hasStyleSpan(spanned)) {
12856                 return true;
12857             }
12858         }
12859         return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
12860     }
12861 
canProcessText()12862     boolean canProcessText() {
12863         if (getId() == View.NO_ID) {
12864             return false;
12865         }
12866         return canShare();
12867     }
12868 
canSelectAllText()12869     boolean canSelectAllText() {
12870         return canSelectText() && !hasPasswordTransformationMethod()
12871                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
12872     }
12873 
selectAllText()12874     boolean selectAllText() {
12875         if (mEditor != null) {
12876             // Hide the toolbar before changing the selection to avoid flickering.
12877             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
12878         }
12879         final int length = mText.length();
12880         Selection.setSelection(mSpannable, 0, length);
12881         return length > 0;
12882     }
12883 
replaceSelectionWithText(CharSequence text)12884     void replaceSelectionWithText(CharSequence text) {
12885         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
12886     }
12887 
12888     /**
12889      * Paste clipboard content between min and max positions.
12890      */
paste(int min, int max, boolean withFormatting)12891     private void paste(int min, int max, boolean withFormatting) {
12892         ClipboardManager clipboard = getClipboardManagerForUser();
12893         ClipData clip = clipboard.getPrimaryClip();
12894         if (clip != null) {
12895             boolean didFirst = false;
12896             for (int i = 0; i < clip.getItemCount(); i++) {
12897                 final CharSequence paste;
12898                 if (withFormatting) {
12899                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
12900                 } else {
12901                     // Get an item as text and remove all spans by toString().
12902                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
12903                     paste = (text instanceof Spanned) ? text.toString() : text;
12904                 }
12905                 if (paste != null) {
12906                     if (!didFirst) {
12907                         Selection.setSelection(mSpannable, max);
12908                         ((Editable) mText).replace(min, max, paste);
12909                         didFirst = true;
12910                     } else {
12911                         ((Editable) mText).insert(getSelectionEnd(), "\n");
12912                         ((Editable) mText).insert(getSelectionEnd(), paste);
12913                     }
12914                 }
12915             }
12916             sLastCutCopyOrTextChangedTime = 0;
12917         }
12918     }
12919 
shareSelectedText()12920     private void shareSelectedText() {
12921         String selectedText = getSelectedText();
12922         if (selectedText != null && !selectedText.isEmpty()) {
12923             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
12924             sharingIntent.setType("text/plain");
12925             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
12926             selectedText = TextUtils.trimToParcelableSize(selectedText);
12927             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
12928             getContext().startActivity(Intent.createChooser(sharingIntent, null));
12929             Selection.setSelection(mSpannable, getSelectionEnd());
12930         }
12931     }
12932 
12933     @CheckResult
setPrimaryClip(ClipData clip)12934     private boolean setPrimaryClip(ClipData clip) {
12935         ClipboardManager clipboard = getClipboardManagerForUser();
12936         try {
12937             clipboard.setPrimaryClip(clip);
12938         } catch (Throwable t) {
12939             return false;
12940         }
12941         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
12942         return true;
12943     }
12944 
12945     /**
12946      * Get the character offset closest to the specified absolute position. A typical use case is to
12947      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
12948      *
12949      * @param x The horizontal absolute position of a point on screen
12950      * @param y The vertical absolute position of a point on screen
12951      * @return the character offset for the character whose position is closest to the specified
12952      *  position. Returns -1 if there is no layout.
12953      */
getOffsetForPosition(float x, float y)12954     public int getOffsetForPosition(float x, float y) {
12955         if (getLayout() == null) return -1;
12956         final int line = getLineAtCoordinate(y);
12957         final int offset = getOffsetAtCoordinate(line, x);
12958         return offset;
12959     }
12960 
convertToLocalHorizontalCoordinate(float x)12961     float convertToLocalHorizontalCoordinate(float x) {
12962         x -= getTotalPaddingLeft();
12963         // Clamp the position to inside of the view.
12964         x = Math.max(0.0f, x);
12965         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
12966         x += getScrollX();
12967         return x;
12968     }
12969 
12970     @UnsupportedAppUsage
getLineAtCoordinate(float y)12971     int getLineAtCoordinate(float y) {
12972         y -= getTotalPaddingTop();
12973         // Clamp the position to inside of the view.
12974         y = Math.max(0.0f, y);
12975         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
12976         y += getScrollY();
12977         return getLayout().getLineForVertical((int) y);
12978     }
12979 
getLineAtCoordinateUnclamped(float y)12980     int getLineAtCoordinateUnclamped(float y) {
12981         y -= getTotalPaddingTop();
12982         y += getScrollY();
12983         return getLayout().getLineForVertical((int) y);
12984     }
12985 
getOffsetAtCoordinate(int line, float x)12986     int getOffsetAtCoordinate(int line, float x) {
12987         x = convertToLocalHorizontalCoordinate(x);
12988         return getLayout().getOffsetForHorizontal(line, x);
12989     }
12990 
12991     @Override
onDragEvent(DragEvent event)12992     public boolean onDragEvent(DragEvent event) {
12993         switch (event.getAction()) {
12994             case DragEvent.ACTION_DRAG_STARTED:
12995                 return mEditor != null && mEditor.hasInsertionController();
12996 
12997             case DragEvent.ACTION_DRAG_ENTERED:
12998                 TextView.this.requestFocus();
12999                 return true;
13000 
13001             case DragEvent.ACTION_DRAG_LOCATION:
13002                 if (mText instanceof Spannable) {
13003                     final int offset = getOffsetForPosition(event.getX(), event.getY());
13004                     Selection.setSelection(mSpannable, offset);
13005                 }
13006                 return true;
13007 
13008             case DragEvent.ACTION_DROP:
13009                 if (mEditor != null) mEditor.onDrop(event);
13010                 return true;
13011 
13012             case DragEvent.ACTION_DRAG_ENDED:
13013             case DragEvent.ACTION_DRAG_EXITED:
13014             default:
13015                 return true;
13016         }
13017     }
13018 
isInBatchEditMode()13019     boolean isInBatchEditMode() {
13020         if (mEditor == null) return false;
13021         final Editor.InputMethodState ims = mEditor.mInputMethodState;
13022         if (ims != null) {
13023             return ims.mBatchEditNesting > 0;
13024         }
13025         return mEditor.mInBatchEditControllers;
13026     }
13027 
13028     @Override
onRtlPropertiesChanged(int layoutDirection)13029     public void onRtlPropertiesChanged(int layoutDirection) {
13030         super.onRtlPropertiesChanged(layoutDirection);
13031 
13032         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
13033         if (mTextDir != newTextDir) {
13034             mTextDir = newTextDir;
13035             if (mLayout != null) {
13036                 checkForRelayout();
13037             }
13038         }
13039     }
13040 
13041     /**
13042      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
13043      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
13044      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
13045      * return value may not be the same as the one TextView uses if the View's layout direction is
13046      * not resolved or detached from parent root view.
13047      */
getTextDirectionHeuristic()13048     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
13049         if (hasPasswordTransformationMethod()) {
13050             // passwords fields should be LTR
13051             return TextDirectionHeuristics.LTR;
13052         }
13053 
13054         if (mEditor != null
13055                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
13056                     == EditorInfo.TYPE_CLASS_PHONE) {
13057             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
13058             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
13059             // RTL digits.
13060             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
13061             final String zero = symbols.getDigitStrings()[0];
13062             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
13063             // direction.
13064             final int firstCodepoint = zero.codePointAt(0);
13065             final byte digitDirection = Character.getDirectionality(firstCodepoint);
13066             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
13067                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
13068                 return TextDirectionHeuristics.RTL;
13069             } else {
13070                 return TextDirectionHeuristics.LTR;
13071             }
13072         }
13073 
13074         // Always need to resolve layout direction first
13075         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
13076 
13077         // Now, we can select the heuristic
13078         switch (getTextDirection()) {
13079             default:
13080             case TEXT_DIRECTION_FIRST_STRONG:
13081                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
13082                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
13083             case TEXT_DIRECTION_ANY_RTL:
13084                 return TextDirectionHeuristics.ANYRTL_LTR;
13085             case TEXT_DIRECTION_LTR:
13086                 return TextDirectionHeuristics.LTR;
13087             case TEXT_DIRECTION_RTL:
13088                 return TextDirectionHeuristics.RTL;
13089             case TEXT_DIRECTION_LOCALE:
13090                 return TextDirectionHeuristics.LOCALE;
13091             case TEXT_DIRECTION_FIRST_STRONG_LTR:
13092                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
13093             case TEXT_DIRECTION_FIRST_STRONG_RTL:
13094                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
13095         }
13096     }
13097 
13098     /**
13099      * @hide
13100      */
13101     @Override
onResolveDrawables(int layoutDirection)13102     public void onResolveDrawables(int layoutDirection) {
13103         // No need to resolve twice
13104         if (mLastLayoutDirection == layoutDirection) {
13105             return;
13106         }
13107         mLastLayoutDirection = layoutDirection;
13108 
13109         // Resolve drawables
13110         if (mDrawables != null) {
13111             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
13112                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
13113                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
13114                 applyCompoundDrawableTint();
13115             }
13116         }
13117     }
13118 
13119     /**
13120      * Prepares a drawable for display by propagating layout direction and
13121      * drawable state.
13122      *
13123      * @param dr the drawable to prepare
13124      */
prepareDrawableForDisplay(@ullable Drawable dr)13125     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
13126         if (dr == null) {
13127             return;
13128         }
13129 
13130         dr.setLayoutDirection(getLayoutDirection());
13131 
13132         if (dr.isStateful()) {
13133             dr.setState(getDrawableState());
13134             dr.jumpToCurrentState();
13135         }
13136     }
13137 
13138     /**
13139      * @hide
13140      */
resetResolvedDrawables()13141     protected void resetResolvedDrawables() {
13142         super.resetResolvedDrawables();
13143         mLastLayoutDirection = -1;
13144     }
13145 
13146     /**
13147      * @hide
13148      */
viewClicked(InputMethodManager imm)13149     protected void viewClicked(InputMethodManager imm) {
13150         if (imm != null) {
13151             imm.viewClicked(this);
13152         }
13153     }
13154 
13155     /**
13156      * Deletes the range of text [start, end[.
13157      * @hide
13158      */
13159     @UnsupportedAppUsage
deleteText_internal(int start, int end)13160     protected void deleteText_internal(int start, int end) {
13161         ((Editable) mText).delete(start, end);
13162     }
13163 
13164     /**
13165      * Replaces the range of text [start, end[ by replacement text
13166      * @hide
13167      */
replaceText_internal(int start, int end, CharSequence text)13168     protected void replaceText_internal(int start, int end, CharSequence text) {
13169         ((Editable) mText).replace(start, end, text);
13170     }
13171 
13172     /**
13173      * Sets a span on the specified range of text
13174      * @hide
13175      */
setSpan_internal(Object span, int start, int end, int flags)13176     protected void setSpan_internal(Object span, int start, int end, int flags) {
13177         ((Editable) mText).setSpan(span, start, end, flags);
13178     }
13179 
13180     /**
13181      * Moves the cursor to the specified offset position in text
13182      * @hide
13183      */
setCursorPosition_internal(int start, int end)13184     protected void setCursorPosition_internal(int start, int end) {
13185         Selection.setSelection(((Editable) mText), start, end);
13186     }
13187 
13188     /**
13189      * An Editor should be created as soon as any of the editable-specific fields (grouped
13190      * inside the Editor object) is assigned to a non-default value.
13191      * This method will create the Editor if needed.
13192      *
13193      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
13194      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
13195      * Editor for backward compatibility, as soon as one of these fields is assigned.
13196      *
13197      * Also note that for performance reasons, the mEditor is created when needed, but not
13198      * reset when no more edit-specific fields are needed.
13199      */
13200     @UnsupportedAppUsage
createEditorIfNeeded()13201     private void createEditorIfNeeded() {
13202         if (mEditor == null) {
13203             mEditor = new Editor(this);
13204         }
13205     }
13206 
13207     /**
13208      * @hide
13209      */
13210     @Override
13211     @UnsupportedAppUsage
getIterableTextForAccessibility()13212     public CharSequence getIterableTextForAccessibility() {
13213         return mText;
13214     }
13215 
ensureIterableTextForAccessibilitySelectable()13216     private void ensureIterableTextForAccessibilitySelectable() {
13217         if (!(mText instanceof Spannable)) {
13218             setText(mText, BufferType.SPANNABLE);
13219         }
13220     }
13221 
13222     /**
13223      * @hide
13224      */
13225     @Override
getIteratorForGranularity(int granularity)13226     public TextSegmentIterator getIteratorForGranularity(int granularity) {
13227         switch (granularity) {
13228             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
13229                 Spannable text = (Spannable) getIterableTextForAccessibility();
13230                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
13231                     AccessibilityIterators.LineTextSegmentIterator iterator =
13232                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
13233                     iterator.initialize(text, getLayout());
13234                     return iterator;
13235                 }
13236             } break;
13237             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
13238                 Spannable text = (Spannable) getIterableTextForAccessibility();
13239                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
13240                     AccessibilityIterators.PageTextSegmentIterator iterator =
13241                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
13242                     iterator.initialize(this);
13243                     return iterator;
13244                 }
13245             } break;
13246         }
13247         return super.getIteratorForGranularity(granularity);
13248     }
13249 
13250     /**
13251      * @hide
13252      */
13253     @Override
getAccessibilitySelectionStart()13254     public int getAccessibilitySelectionStart() {
13255         return getSelectionStart();
13256     }
13257 
13258     /**
13259      * @hide
13260      */
isAccessibilitySelectionExtendable()13261     public boolean isAccessibilitySelectionExtendable() {
13262         return true;
13263     }
13264 
13265     /**
13266      * @hide
13267      */
13268     @Override
getAccessibilitySelectionEnd()13269     public int getAccessibilitySelectionEnd() {
13270         return getSelectionEnd();
13271     }
13272 
13273     /**
13274      * @hide
13275      */
13276     @Override
setAccessibilitySelection(int start, int end)13277     public void setAccessibilitySelection(int start, int end) {
13278         if (getAccessibilitySelectionStart() == start
13279                 && getAccessibilitySelectionEnd() == end) {
13280             return;
13281         }
13282         CharSequence text = getIterableTextForAccessibility();
13283         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
13284             Selection.setSelection((Spannable) text, start, end);
13285         } else {
13286             Selection.removeSelection((Spannable) text);
13287         }
13288         // Hide all selection controllers used for adjusting selection
13289         // since we are doing so explicitlty by other means and these
13290         // controllers interact with how selection behaves.
13291         if (mEditor != null) {
13292             mEditor.hideCursorAndSpanControllers();
13293             mEditor.stopTextActionMode();
13294         }
13295     }
13296 
13297     /** @hide */
13298     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)13299     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
13300         super.encodeProperties(stream);
13301 
13302         TruncateAt ellipsize = getEllipsize();
13303         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
13304         stream.addProperty("text:textSize", getTextSize());
13305         stream.addProperty("text:scaledTextSize", getScaledTextSize());
13306         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
13307         stream.addProperty("text:selectionStart", getSelectionStart());
13308         stream.addProperty("text:selectionEnd", getSelectionEnd());
13309         stream.addProperty("text:curTextColor", mCurTextColor);
13310         stream.addUserProperty("text:text", mText == null ? null : mText.toString());
13311         stream.addProperty("text:gravity", mGravity);
13312     }
13313 
13314     /**
13315      * User interface state that is stored by TextView for implementing
13316      * {@link View#onSaveInstanceState}.
13317      */
13318     public static class SavedState extends BaseSavedState {
13319         int selStart = -1;
13320         int selEnd = -1;
13321         @UnsupportedAppUsage
13322         CharSequence text;
13323         boolean frozenWithFocus;
13324         CharSequence error;
13325         ParcelableParcel editorState;  // Optional state from Editor.
13326 
SavedState(Parcelable superState)13327         SavedState(Parcelable superState) {
13328             super(superState);
13329         }
13330 
13331         @Override
writeToParcel(Parcel out, int flags)13332         public void writeToParcel(Parcel out, int flags) {
13333             super.writeToParcel(out, flags);
13334             out.writeInt(selStart);
13335             out.writeInt(selEnd);
13336             out.writeInt(frozenWithFocus ? 1 : 0);
13337             TextUtils.writeToParcel(text, out, flags);
13338 
13339             if (error == null) {
13340                 out.writeInt(0);
13341             } else {
13342                 out.writeInt(1);
13343                 TextUtils.writeToParcel(error, out, flags);
13344             }
13345 
13346             if (editorState == null) {
13347                 out.writeInt(0);
13348             } else {
13349                 out.writeInt(1);
13350                 editorState.writeToParcel(out, flags);
13351             }
13352         }
13353 
13354         @Override
toString()13355         public String toString() {
13356             String str = "TextView.SavedState{"
13357                     + Integer.toHexString(System.identityHashCode(this))
13358                     + " start=" + selStart + " end=" + selEnd;
13359             if (text != null) {
13360                 str += " text=" + text;
13361             }
13362             return str + "}";
13363         }
13364 
13365         @SuppressWarnings("hiding")
13366         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
13367                 new Parcelable.Creator<SavedState>() {
13368                     public SavedState createFromParcel(Parcel in) {
13369                         return new SavedState(in);
13370                     }
13371 
13372                     public SavedState[] newArray(int size) {
13373                         return new SavedState[size];
13374                     }
13375                 };
13376 
SavedState(Parcel in)13377         private SavedState(Parcel in) {
13378             super(in);
13379             selStart = in.readInt();
13380             selEnd = in.readInt();
13381             frozenWithFocus = (in.readInt() != 0);
13382             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13383 
13384             if (in.readInt() != 0) {
13385                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13386             }
13387 
13388             if (in.readInt() != 0) {
13389                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
13390             }
13391         }
13392     }
13393 
13394     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
13395         private char[] mChars;
13396         private int mStart, mLength;
13397 
CharWrapper(char[] chars, int start, int len)13398         public CharWrapper(char[] chars, int start, int len) {
13399             mChars = chars;
13400             mStart = start;
13401             mLength = len;
13402         }
13403 
set(char[] chars, int start, int len)13404         /* package */ void set(char[] chars, int start, int len) {
13405             mChars = chars;
13406             mStart = start;
13407             mLength = len;
13408         }
13409 
length()13410         public int length() {
13411             return mLength;
13412         }
13413 
charAt(int off)13414         public char charAt(int off) {
13415             return mChars[off + mStart];
13416         }
13417 
13418         @Override
toString()13419         public String toString() {
13420             return new String(mChars, mStart, mLength);
13421         }
13422 
subSequence(int start, int end)13423         public CharSequence subSequence(int start, int end) {
13424             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13425                 throw new IndexOutOfBoundsException(start + ", " + end);
13426             }
13427 
13428             return new String(mChars, start + mStart, end - start);
13429         }
13430 
getChars(int start, int end, char[] buf, int off)13431         public void getChars(int start, int end, char[] buf, int off) {
13432             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13433                 throw new IndexOutOfBoundsException(start + ", " + end);
13434             }
13435 
13436             System.arraycopy(mChars, start + mStart, buf, off, end - start);
13437         }
13438 
13439         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13440         public void drawText(BaseCanvas c, int start, int end,
13441                              float x, float y, Paint p) {
13442             c.drawText(mChars, start + mStart, end - start, x, y, p);
13443         }
13444 
13445         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13446         public void drawTextRun(BaseCanvas c, int start, int end,
13447                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
13448             int count = end - start;
13449             int contextCount = contextEnd - contextStart;
13450             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
13451                     contextCount, x, y, isRtl, p);
13452         }
13453 
measureText(int start, int end, Paint p)13454         public float measureText(int start, int end, Paint p) {
13455             return p.measureText(mChars, start + mStart, end - start);
13456         }
13457 
getTextWidths(int start, int end, float[] widths, Paint p)13458         public int getTextWidths(int start, int end, float[] widths, Paint p) {
13459             return p.getTextWidths(mChars, start + mStart, end - start, widths);
13460         }
13461 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13462         public float getTextRunAdvances(int start, int end, int contextStart,
13463                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
13464                 Paint p) {
13465             int count = end - start;
13466             int contextCount = contextEnd - contextStart;
13467             return p.getTextRunAdvances(mChars, start + mStart, count,
13468                     contextStart + mStart, contextCount, isRtl, advances,
13469                     advancesIndex);
13470         }
13471 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13472         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
13473                 int offset, int cursorOpt, Paint p) {
13474             int contextCount = contextEnd - contextStart;
13475             return p.getTextRunCursor(mChars, contextStart + mStart,
13476                     contextCount, isRtl, offset + mStart, cursorOpt);
13477         }
13478     }
13479 
13480     private static final class Marquee {
13481         // TODO: Add an option to configure this
13482         private static final float MARQUEE_DELTA_MAX = 0.07f;
13483         private static final int MARQUEE_DELAY = 1200;
13484         private static final int MARQUEE_DP_PER_SECOND = 30;
13485 
13486         private static final byte MARQUEE_STOPPED = 0x0;
13487         private static final byte MARQUEE_STARTING = 0x1;
13488         private static final byte MARQUEE_RUNNING = 0x2;
13489 
13490         private final WeakReference<TextView> mView;
13491         private final Choreographer mChoreographer;
13492 
13493         private byte mStatus = MARQUEE_STOPPED;
13494         private final float mPixelsPerMs;
13495         private float mMaxScroll;
13496         private float mMaxFadeScroll;
13497         private float mGhostStart;
13498         private float mGhostOffset;
13499         private float mFadeStop;
13500         private int mRepeatLimit;
13501 
13502         private float mScroll;
13503         private long mLastAnimationMs;
13504 
Marquee(TextView v)13505         Marquee(TextView v) {
13506             final float density = v.getContext().getResources().getDisplayMetrics().density;
13507             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
13508             mView = new WeakReference<TextView>(v);
13509             mChoreographer = Choreographer.getInstance();
13510         }
13511 
13512         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
13513             @Override
13514             public void doFrame(long frameTimeNanos) {
13515                 tick();
13516             }
13517         };
13518 
13519         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
13520             @Override
13521             public void doFrame(long frameTimeNanos) {
13522                 mStatus = MARQUEE_RUNNING;
13523                 mLastAnimationMs = mChoreographer.getFrameTime();
13524                 tick();
13525             }
13526         };
13527 
13528         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
13529             @Override
13530             public void doFrame(long frameTimeNanos) {
13531                 if (mStatus == MARQUEE_RUNNING) {
13532                     if (mRepeatLimit >= 0) {
13533                         mRepeatLimit--;
13534                     }
13535                     start(mRepeatLimit);
13536                 }
13537             }
13538         };
13539 
tick()13540         void tick() {
13541             if (mStatus != MARQUEE_RUNNING) {
13542                 return;
13543             }
13544 
13545             mChoreographer.removeFrameCallback(mTickCallback);
13546 
13547             final TextView textView = mView.get();
13548             if (textView != null && (textView.isFocused() || textView.isSelected())) {
13549                 long currentMs = mChoreographer.getFrameTime();
13550                 long deltaMs = currentMs - mLastAnimationMs;
13551                 mLastAnimationMs = currentMs;
13552                 float deltaPx = deltaMs * mPixelsPerMs;
13553                 mScroll += deltaPx;
13554                 if (mScroll > mMaxScroll) {
13555                     mScroll = mMaxScroll;
13556                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
13557                 } else {
13558                     mChoreographer.postFrameCallback(mTickCallback);
13559                 }
13560                 textView.invalidate();
13561             }
13562         }
13563 
stop()13564         void stop() {
13565             mStatus = MARQUEE_STOPPED;
13566             mChoreographer.removeFrameCallback(mStartCallback);
13567             mChoreographer.removeFrameCallback(mRestartCallback);
13568             mChoreographer.removeFrameCallback(mTickCallback);
13569             resetScroll();
13570         }
13571 
resetScroll()13572         private void resetScroll() {
13573             mScroll = 0.0f;
13574             final TextView textView = mView.get();
13575             if (textView != null) textView.invalidate();
13576         }
13577 
start(int repeatLimit)13578         void start(int repeatLimit) {
13579             if (repeatLimit == 0) {
13580                 stop();
13581                 return;
13582             }
13583             mRepeatLimit = repeatLimit;
13584             final TextView textView = mView.get();
13585             if (textView != null && textView.mLayout != null) {
13586                 mStatus = MARQUEE_STARTING;
13587                 mScroll = 0.0f;
13588                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
13589                         - textView.getCompoundPaddingRight();
13590                 final float lineWidth = textView.mLayout.getLineWidth(0);
13591                 final float gap = textWidth / 3.0f;
13592                 mGhostStart = lineWidth - textWidth + gap;
13593                 mMaxScroll = mGhostStart + textWidth;
13594                 mGhostOffset = lineWidth + gap;
13595                 mFadeStop = lineWidth + textWidth / 6.0f;
13596                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
13597 
13598                 textView.invalidate();
13599                 mChoreographer.postFrameCallback(mStartCallback);
13600             }
13601         }
13602 
getGhostOffset()13603         float getGhostOffset() {
13604             return mGhostOffset;
13605         }
13606 
getScroll()13607         float getScroll() {
13608             return mScroll;
13609         }
13610 
getMaxFadeScroll()13611         float getMaxFadeScroll() {
13612             return mMaxFadeScroll;
13613         }
13614 
shouldDrawLeftFade()13615         boolean shouldDrawLeftFade() {
13616             return mScroll <= mFadeStop;
13617         }
13618 
shouldDrawGhost()13619         boolean shouldDrawGhost() {
13620             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
13621         }
13622 
isRunning()13623         boolean isRunning() {
13624             return mStatus == MARQUEE_RUNNING;
13625         }
13626 
isStopped()13627         boolean isStopped() {
13628             return mStatus == MARQUEE_STOPPED;
13629         }
13630     }
13631 
13632     private class ChangeWatcher implements TextWatcher, SpanWatcher {
13633 
13634         private CharSequence mBeforeText;
13635 
beforeTextChanged(CharSequence buffer, int start, int before, int after)13636         public void beforeTextChanged(CharSequence buffer, int start,
13637                                       int before, int after) {
13638             if (DEBUG_EXTRACT) {
13639                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
13640                         + " before=" + before + " after=" + after + ": " + buffer);
13641             }
13642 
13643             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
13644                 mBeforeText = mTransformed.toString();
13645             }
13646 
13647             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
13648         }
13649 
onTextChanged(CharSequence buffer, int start, int before, int after)13650         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
13651             if (DEBUG_EXTRACT) {
13652                 Log.v(LOG_TAG, "onTextChanged start=" + start
13653                         + " before=" + before + " after=" + after + ": " + buffer);
13654             }
13655             TextView.this.handleTextChanged(buffer, start, before, after);
13656 
13657             if (AccessibilityManager.getInstance(mContext).isEnabled()
13658                     && (isFocused() || isSelected() && isShown())) {
13659                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
13660                 mBeforeText = null;
13661             }
13662         }
13663 
afterTextChanged(Editable buffer)13664         public void afterTextChanged(Editable buffer) {
13665             if (DEBUG_EXTRACT) {
13666                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
13667             }
13668             TextView.this.sendAfterTextChanged(buffer);
13669 
13670             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
13671                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
13672             }
13673         }
13674 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13675         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
13676             if (DEBUG_EXTRACT) {
13677                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
13678                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
13679             }
13680             TextView.this.spanChange(buf, what, s, st, e, en);
13681         }
13682 
onSpanAdded(Spannable buf, Object what, int s, int e)13683         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
13684             if (DEBUG_EXTRACT) {
13685                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
13686             }
13687             TextView.this.spanChange(buf, what, -1, s, -1, e);
13688         }
13689 
onSpanRemoved(Spannable buf, Object what, int s, int e)13690         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
13691             if (DEBUG_EXTRACT) {
13692                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
13693             }
13694             TextView.this.spanChange(buf, what, s, -1, e, -1);
13695         }
13696     }
13697 
logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)13698     private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
13699         if (msgFormat == null) {
13700             Log.d(LOG_TAG, location);
13701         } else {
13702             Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs));
13703         }
13704     }
13705 }
13706