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.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
22 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
23 
24 import android.R;
25 import android.annotation.CheckResult;
26 import android.annotation.ColorInt;
27 import android.annotation.DrawableRes;
28 import android.annotation.FloatRange;
29 import android.annotation.IntDef;
30 import android.annotation.IntRange;
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.annotation.Px;
34 import android.annotation.Size;
35 import android.annotation.StringRes;
36 import android.annotation.StyleRes;
37 import android.annotation.XmlRes;
38 import android.app.Activity;
39 import android.app.PendingIntent;
40 import android.app.assist.AssistStructure;
41 import android.content.ClipData;
42 import android.content.ClipDescription;
43 import android.content.ClipboardManager;
44 import android.content.Context;
45 import android.content.Intent;
46 import android.content.UndoManager;
47 import android.content.res.ColorStateList;
48 import android.content.res.CompatibilityInfo;
49 import android.content.res.Configuration;
50 import android.content.res.ResourceId;
51 import android.content.res.Resources;
52 import android.content.res.TypedArray;
53 import android.content.res.XmlResourceParser;
54 import android.graphics.BaseCanvas;
55 import android.graphics.Canvas;
56 import android.graphics.Insets;
57 import android.graphics.Paint;
58 import android.graphics.Paint.FontMetricsInt;
59 import android.graphics.Path;
60 import android.graphics.PorterDuff;
61 import android.graphics.Rect;
62 import android.graphics.RectF;
63 import android.graphics.Typeface;
64 import android.graphics.drawable.Drawable;
65 import android.graphics.fonts.FontVariationAxis;
66 import android.icu.text.DecimalFormatSymbols;
67 import android.os.AsyncTask;
68 import android.os.Build.VERSION_CODES;
69 import android.os.Bundle;
70 import android.os.LocaleList;
71 import android.os.Parcel;
72 import android.os.Parcelable;
73 import android.os.ParcelableParcel;
74 import android.os.SystemClock;
75 import android.provider.Settings;
76 import android.text.BoringLayout;
77 import android.text.DynamicLayout;
78 import android.text.Editable;
79 import android.text.GetChars;
80 import android.text.GraphicsOperations;
81 import android.text.InputFilter;
82 import android.text.InputType;
83 import android.text.Layout;
84 import android.text.ParcelableSpan;
85 import android.text.PrecomputedText;
86 import android.text.Selection;
87 import android.text.SpanWatcher;
88 import android.text.Spannable;
89 import android.text.SpannableStringBuilder;
90 import android.text.Spanned;
91 import android.text.SpannedString;
92 import android.text.StaticLayout;
93 import android.text.TextDirectionHeuristic;
94 import android.text.TextDirectionHeuristics;
95 import android.text.TextPaint;
96 import android.text.TextUtils;
97 import android.text.TextUtils.TruncateAt;
98 import android.text.TextWatcher;
99 import android.text.method.AllCapsTransformationMethod;
100 import android.text.method.ArrowKeyMovementMethod;
101 import android.text.method.DateKeyListener;
102 import android.text.method.DateTimeKeyListener;
103 import android.text.method.DialerKeyListener;
104 import android.text.method.DigitsKeyListener;
105 import android.text.method.KeyListener;
106 import android.text.method.LinkMovementMethod;
107 import android.text.method.MetaKeyKeyListener;
108 import android.text.method.MovementMethod;
109 import android.text.method.PasswordTransformationMethod;
110 import android.text.method.SingleLineTransformationMethod;
111 import android.text.method.TextKeyListener;
112 import android.text.method.TimeKeyListener;
113 import android.text.method.TransformationMethod;
114 import android.text.method.TransformationMethod2;
115 import android.text.method.WordIterator;
116 import android.text.style.CharacterStyle;
117 import android.text.style.ClickableSpan;
118 import android.text.style.ParagraphStyle;
119 import android.text.style.SpellCheckSpan;
120 import android.text.style.SuggestionSpan;
121 import android.text.style.URLSpan;
122 import android.text.style.UpdateAppearance;
123 import android.text.util.Linkify;
124 import android.util.AttributeSet;
125 import android.util.DisplayMetrics;
126 import android.util.IntArray;
127 import android.util.Log;
128 import android.util.SparseIntArray;
129 import android.util.TypedValue;
130 import android.view.AccessibilityIterators.TextSegmentIterator;
131 import android.view.ActionMode;
132 import android.view.Choreographer;
133 import android.view.ContextMenu;
134 import android.view.DragEvent;
135 import android.view.Gravity;
136 import android.view.HapticFeedbackConstants;
137 import android.view.InputDevice;
138 import android.view.KeyCharacterMap;
139 import android.view.KeyEvent;
140 import android.view.MotionEvent;
141 import android.view.PointerIcon;
142 import android.view.View;
143 import android.view.ViewConfiguration;
144 import android.view.ViewDebug;
145 import android.view.ViewGroup.LayoutParams;
146 import android.view.ViewHierarchyEncoder;
147 import android.view.ViewParent;
148 import android.view.ViewRootImpl;
149 import android.view.ViewStructure;
150 import android.view.ViewTreeObserver;
151 import android.view.accessibility.AccessibilityEvent;
152 import android.view.accessibility.AccessibilityManager;
153 import android.view.accessibility.AccessibilityNodeInfo;
154 import android.view.animation.AnimationUtils;
155 import android.view.autofill.AutofillManager;
156 import android.view.autofill.AutofillValue;
157 import android.view.inputmethod.BaseInputConnection;
158 import android.view.inputmethod.CompletionInfo;
159 import android.view.inputmethod.CorrectionInfo;
160 import android.view.inputmethod.CursorAnchorInfo;
161 import android.view.inputmethod.EditorInfo;
162 import android.view.inputmethod.ExtractedText;
163 import android.view.inputmethod.ExtractedTextRequest;
164 import android.view.inputmethod.InputConnection;
165 import android.view.inputmethod.InputMethodManager;
166 import android.view.textclassifier.TextClassification;
167 import android.view.textclassifier.TextClassificationContext;
168 import android.view.textclassifier.TextClassificationManager;
169 import android.view.textclassifier.TextClassifier;
170 import android.view.textclassifier.TextLinks;
171 import android.view.textservice.SpellCheckerSubtype;
172 import android.view.textservice.TextServicesManager;
173 import android.widget.RemoteViews.RemoteView;
174 
175 import com.android.internal.annotations.VisibleForTesting;
176 import com.android.internal.logging.MetricsLogger;
177 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
178 import com.android.internal.util.FastMath;
179 import com.android.internal.util.Preconditions;
180 import com.android.internal.widget.EditableInputConnection;
181 
182 import libcore.util.EmptyArray;
183 
184 import org.xmlpull.v1.XmlPullParserException;
185 
186 import java.io.IOException;
187 import java.lang.annotation.Retention;
188 import java.lang.annotation.RetentionPolicy;
189 import java.lang.ref.WeakReference;
190 import java.util.ArrayList;
191 import java.util.Arrays;
192 import java.util.Locale;
193 import java.util.concurrent.CompletableFuture;
194 import java.util.concurrent.TimeUnit;
195 import java.util.function.Consumer;
196 import java.util.function.Supplier;
197 
198 /**
199  * A user interface element that displays text to the user.
200  * To provide user-editable text, see {@link EditText}.
201  * <p>
202  * The following code sample shows a typical use, with an XML layout
203  * and code to modify the contents of the text view:
204  * </p>
205 
206  * <pre>
207  * &lt;LinearLayout
208        xmlns:android="http://schemas.android.com/apk/res/android"
209        android:layout_width="match_parent"
210        android:layout_height="match_parent"&gt;
211  *    &lt;TextView
212  *        android:id="@+id/text_view_id"
213  *        android:layout_height="wrap_content"
214  *        android:layout_width="wrap_content"
215  *        android:text="@string/hello" /&gt;
216  * &lt;/LinearLayout&gt;
217  * </pre>
218  * <p>
219  * This code sample demonstrates how to modify the contents of the text view
220  * defined in the previous XML layout:
221  * </p>
222  * <pre>
223  * public class MainActivity extends Activity {
224  *
225  *    protected void onCreate(Bundle savedInstanceState) {
226  *         super.onCreate(savedInstanceState);
227  *         setContentView(R.layout.activity_main);
228  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
229  *         helloTextView.setText(R.string.user_greeting);
230  *     }
231  * }
232  * </pre>
233  * <p>
234  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
235  * </p>
236  * <p>
237  * <b>XML attributes</b>
238  * <p>
239  * See {@link android.R.styleable#TextView TextView Attributes},
240  * {@link android.R.styleable#View View Attributes}
241  *
242  * @attr ref android.R.styleable#TextView_text
243  * @attr ref android.R.styleable#TextView_bufferType
244  * @attr ref android.R.styleable#TextView_hint
245  * @attr ref android.R.styleable#TextView_textColor
246  * @attr ref android.R.styleable#TextView_textColorHighlight
247  * @attr ref android.R.styleable#TextView_textColorHint
248  * @attr ref android.R.styleable#TextView_textAppearance
249  * @attr ref android.R.styleable#TextView_textColorLink
250  * @attr ref android.R.styleable#TextView_textSize
251  * @attr ref android.R.styleable#TextView_textScaleX
252  * @attr ref android.R.styleable#TextView_fontFamily
253  * @attr ref android.R.styleable#TextView_typeface
254  * @attr ref android.R.styleable#TextView_textStyle
255  * @attr ref android.R.styleable#TextView_cursorVisible
256  * @attr ref android.R.styleable#TextView_maxLines
257  * @attr ref android.R.styleable#TextView_maxHeight
258  * @attr ref android.R.styleable#TextView_lines
259  * @attr ref android.R.styleable#TextView_height
260  * @attr ref android.R.styleable#TextView_minLines
261  * @attr ref android.R.styleable#TextView_minHeight
262  * @attr ref android.R.styleable#TextView_maxEms
263  * @attr ref android.R.styleable#TextView_maxWidth
264  * @attr ref android.R.styleable#TextView_ems
265  * @attr ref android.R.styleable#TextView_width
266  * @attr ref android.R.styleable#TextView_minEms
267  * @attr ref android.R.styleable#TextView_minWidth
268  * @attr ref android.R.styleable#TextView_gravity
269  * @attr ref android.R.styleable#TextView_scrollHorizontally
270  * @attr ref android.R.styleable#TextView_password
271  * @attr ref android.R.styleable#TextView_singleLine
272  * @attr ref android.R.styleable#TextView_selectAllOnFocus
273  * @attr ref android.R.styleable#TextView_includeFontPadding
274  * @attr ref android.R.styleable#TextView_maxLength
275  * @attr ref android.R.styleable#TextView_shadowColor
276  * @attr ref android.R.styleable#TextView_shadowDx
277  * @attr ref android.R.styleable#TextView_shadowDy
278  * @attr ref android.R.styleable#TextView_shadowRadius
279  * @attr ref android.R.styleable#TextView_autoLink
280  * @attr ref android.R.styleable#TextView_linksClickable
281  * @attr ref android.R.styleable#TextView_numeric
282  * @attr ref android.R.styleable#TextView_digits
283  * @attr ref android.R.styleable#TextView_phoneNumber
284  * @attr ref android.R.styleable#TextView_inputMethod
285  * @attr ref android.R.styleable#TextView_capitalize
286  * @attr ref android.R.styleable#TextView_autoText
287  * @attr ref android.R.styleable#TextView_editable
288  * @attr ref android.R.styleable#TextView_freezesText
289  * @attr ref android.R.styleable#TextView_ellipsize
290  * @attr ref android.R.styleable#TextView_drawableTop
291  * @attr ref android.R.styleable#TextView_drawableBottom
292  * @attr ref android.R.styleable#TextView_drawableRight
293  * @attr ref android.R.styleable#TextView_drawableLeft
294  * @attr ref android.R.styleable#TextView_drawableStart
295  * @attr ref android.R.styleable#TextView_drawableEnd
296  * @attr ref android.R.styleable#TextView_drawablePadding
297  * @attr ref android.R.styleable#TextView_drawableTint
298  * @attr ref android.R.styleable#TextView_drawableTintMode
299  * @attr ref android.R.styleable#TextView_lineSpacingExtra
300  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
301  * @attr ref android.R.styleable#TextView_justificationMode
302  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
303  * @attr ref android.R.styleable#TextView_inputType
304  * @attr ref android.R.styleable#TextView_imeOptions
305  * @attr ref android.R.styleable#TextView_privateImeOptions
306  * @attr ref android.R.styleable#TextView_imeActionLabel
307  * @attr ref android.R.styleable#TextView_imeActionId
308  * @attr ref android.R.styleable#TextView_editorExtras
309  * @attr ref android.R.styleable#TextView_elegantTextHeight
310  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
311  * @attr ref android.R.styleable#TextView_letterSpacing
312  * @attr ref android.R.styleable#TextView_fontFeatureSettings
313  * @attr ref android.R.styleable#TextView_breakStrategy
314  * @attr ref android.R.styleable#TextView_hyphenationFrequency
315  * @attr ref android.R.styleable#TextView_autoSizeTextType
316  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
317  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
318  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
319  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
320  */
321 @RemoteView
322 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
323     static final String LOG_TAG = "TextView";
324     static final boolean DEBUG_EXTRACT = false;
325     private static final float[] TEMP_POSITION = new float[2];
326 
327     // Enum for the "typeface" XML parameter.
328     // TODO: How can we get this from the XML instead of hardcoding it here?
329     /** @hide */
330     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
331     @Retention(RetentionPolicy.SOURCE)
332     public @interface XMLTypefaceAttr{}
333     private static final int DEFAULT_TYPEFACE = -1;
334     private static final int SANS = 1;
335     private static final int SERIF = 2;
336     private static final int MONOSPACE = 3;
337 
338     // Enum for the "ellipsize" XML parameter.
339     private static final int ELLIPSIZE_NOT_SET = -1;
340     private static final int ELLIPSIZE_NONE = 0;
341     private static final int ELLIPSIZE_START = 1;
342     private static final int ELLIPSIZE_MIDDLE = 2;
343     private static final int ELLIPSIZE_END = 3;
344     private static final int ELLIPSIZE_MARQUEE = 4;
345 
346     // Bitfield for the "numeric" XML parameter.
347     // TODO: How can we get this from the XML instead of hardcoding it here?
348     private static final int SIGNED = 2;
349     private static final int DECIMAL = 4;
350 
351     /**
352      * Draw marquee text with fading edges as usual
353      */
354     private static final int MARQUEE_FADE_NORMAL = 0;
355 
356     /**
357      * Draw marquee text as ellipsize end while inactive instead of with the fade.
358      * (Useful for devices where the fade can be expensive if overdone)
359      */
360     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
361 
362     /**
363      * Draw marquee text with fading edges because it is currently active/animating.
364      */
365     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
366 
367     private static final int LINES = 1;
368     private static final int EMS = LINES;
369     private static final int PIXELS = 2;
370 
371     private static final RectF TEMP_RECTF = new RectF();
372 
373     /** @hide */
374     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
375     private static final int ANIMATED_SCROLL_GAP = 250;
376 
377     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
378     private static final Spanned EMPTY_SPANNED = new SpannedString("");
379 
380     private static final int CHANGE_WATCHER_PRIORITY = 100;
381 
382     // New state used to change background based on whether this TextView is multiline.
383     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
384 
385     // Accessibility action to share selected text.
386     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
387 
388     /**
389      * @hide
390      */
391     // Accessibility action start id for "process text" actions.
392     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
393 
394     /**
395      * @hide
396      */
397     static final int PROCESS_TEXT_REQUEST_CODE = 100;
398 
399     /**
400      *  Return code of {@link #doKeyDown}.
401      */
402     private static final int KEY_EVENT_NOT_HANDLED = 0;
403     private static final int KEY_EVENT_HANDLED = -1;
404     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
405     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
406 
407     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
408 
409     // System wide time for last cut, copy or text changed action.
410     static long sLastCutCopyOrTextChangedTime;
411 
412     private ColorStateList mTextColor;
413     private ColorStateList mHintTextColor;
414     private ColorStateList mLinkTextColor;
415     @ViewDebug.ExportedProperty(category = "text")
416     private int mCurTextColor;
417     private int mCurHintTextColor;
418     private boolean mFreezesText;
419 
420     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
421     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
422 
423     private float mShadowRadius, mShadowDx, mShadowDy;
424     private int mShadowColor;
425 
426     private boolean mPreDrawRegistered;
427     private boolean mPreDrawListenerDetached;
428 
429     private TextClassifier mTextClassifier;
430     private TextClassifier mTextClassificationSession;
431 
432     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
433     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
434     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
435     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
436     // into by the user holding the movement key down) then we shouldn't prevent the focus from
437     // changing.
438     private boolean mPreventDefaultMovement;
439 
440     private TextUtils.TruncateAt mEllipsize;
441 
442     static class Drawables {
443         static final int LEFT = 0;
444         static final int TOP = 1;
445         static final int RIGHT = 2;
446         static final int BOTTOM = 3;
447 
448         static final int DRAWABLE_NONE = -1;
449         static final int DRAWABLE_RIGHT = 0;
450         static final int DRAWABLE_LEFT = 1;
451 
452         final Rect mCompoundRect = new Rect();
453 
454         final Drawable[] mShowing = new Drawable[4];
455 
456         ColorStateList mTintList;
457         PorterDuff.Mode mTintMode;
458         boolean mHasTint;
459         boolean mHasTintMode;
460 
461         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
462         Drawable mDrawableLeftInitial, mDrawableRightInitial;
463 
464         boolean mIsRtlCompatibilityMode;
465         boolean mOverride;
466 
467         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
468                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
469 
470         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
471                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
472 
473         int mDrawablePadding;
474 
475         int mDrawableSaved = DRAWABLE_NONE;
476 
Drawables(Context context)477         public Drawables(Context context) {
478             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
479             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
480                     || !context.getApplicationInfo().hasRtlSupport();
481             mOverride = false;
482         }
483 
484         /**
485          * @return {@code true} if this object contains metadata that needs to
486          *         be retained, {@code false} otherwise
487          */
488         public boolean hasMetadata() {
489             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
490         }
491 
492         /**
493          * Updates the list of displayed drawables to account for the current
494          * layout direction.
495          *
496          * @param layoutDirection the current layout direction
497          * @return {@code true} if the displayed drawables changed
498          */
499         public boolean resolveWithLayoutDirection(int layoutDirection) {
500             final Drawable previousLeft = mShowing[Drawables.LEFT];
501             final Drawable previousRight = mShowing[Drawables.RIGHT];
502 
503             // First reset "left" and "right" drawables to their initial values
504             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
505             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
506 
507             if (mIsRtlCompatibilityMode) {
508                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
509                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
510                     mShowing[Drawables.LEFT] = mDrawableStart;
511                     mDrawableSizeLeft = mDrawableSizeStart;
512                     mDrawableHeightLeft = mDrawableHeightStart;
513                 }
514                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
515                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
516                     mShowing[Drawables.RIGHT] = mDrawableEnd;
517                     mDrawableSizeRight = mDrawableSizeEnd;
518                     mDrawableHeightRight = mDrawableHeightEnd;
519                 }
520             } else {
521                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
522                 // drawable if and only if they have been defined
523                 switch(layoutDirection) {
524                     case LAYOUT_DIRECTION_RTL:
525                         if (mOverride) {
526                             mShowing[Drawables.RIGHT] = mDrawableStart;
527                             mDrawableSizeRight = mDrawableSizeStart;
528                             mDrawableHeightRight = mDrawableHeightStart;
529 
530                             mShowing[Drawables.LEFT] = mDrawableEnd;
531                             mDrawableSizeLeft = mDrawableSizeEnd;
532                             mDrawableHeightLeft = mDrawableHeightEnd;
533                         }
534                         break;
535 
536                     case LAYOUT_DIRECTION_LTR:
537                     default:
538                         if (mOverride) {
539                             mShowing[Drawables.LEFT] = mDrawableStart;
540                             mDrawableSizeLeft = mDrawableSizeStart;
541                             mDrawableHeightLeft = mDrawableHeightStart;
542 
543                             mShowing[Drawables.RIGHT] = mDrawableEnd;
544                             mDrawableSizeRight = mDrawableSizeEnd;
545                             mDrawableHeightRight = mDrawableHeightEnd;
546                         }
547                         break;
548                 }
549             }
550 
551             applyErrorDrawableIfNeeded(layoutDirection);
552 
553             return mShowing[Drawables.LEFT] != previousLeft
554                     || mShowing[Drawables.RIGHT] != previousRight;
555         }
556 
557         public void setErrorDrawable(Drawable dr, TextView tv) {
558             if (mDrawableError != dr && mDrawableError != null) {
559                 mDrawableError.setCallback(null);
560             }
561             mDrawableError = dr;
562 
563             if (mDrawableError != null) {
564                 final Rect compoundRect = mCompoundRect;
565                 final int[] state = tv.getDrawableState();
566 
567                 mDrawableError.setState(state);
568                 mDrawableError.copyBounds(compoundRect);
569                 mDrawableError.setCallback(tv);
570                 mDrawableSizeError = compoundRect.width();
571                 mDrawableHeightError = compoundRect.height();
572             } else {
573                 mDrawableSizeError = mDrawableHeightError = 0;
574             }
575         }
576 
577         private void applyErrorDrawableIfNeeded(int layoutDirection) {
578             // first restore the initial state if needed
579             switch (mDrawableSaved) {
580                 case DRAWABLE_LEFT:
581                     mShowing[Drawables.LEFT] = mDrawableTemp;
582                     mDrawableSizeLeft = mDrawableSizeTemp;
583                     mDrawableHeightLeft = mDrawableHeightTemp;
584                     break;
585                 case DRAWABLE_RIGHT:
586                     mShowing[Drawables.RIGHT] = mDrawableTemp;
587                     mDrawableSizeRight = mDrawableSizeTemp;
588                     mDrawableHeightRight = mDrawableHeightTemp;
589                     break;
590                 case DRAWABLE_NONE:
591                 default:
592             }
593             // then, if needed, assign the Error drawable to the correct location
594             if (mDrawableError != null) {
595                 switch(layoutDirection) {
596                     case LAYOUT_DIRECTION_RTL:
597                         mDrawableSaved = DRAWABLE_LEFT;
598 
599                         mDrawableTemp = mShowing[Drawables.LEFT];
600                         mDrawableSizeTemp = mDrawableSizeLeft;
601                         mDrawableHeightTemp = mDrawableHeightLeft;
602 
603                         mShowing[Drawables.LEFT] = mDrawableError;
604                         mDrawableSizeLeft = mDrawableSizeError;
605                         mDrawableHeightLeft = mDrawableHeightError;
606                         break;
607                     case LAYOUT_DIRECTION_LTR:
608                     default:
609                         mDrawableSaved = DRAWABLE_RIGHT;
610 
611                         mDrawableTemp = mShowing[Drawables.RIGHT];
612                         mDrawableSizeTemp = mDrawableSizeRight;
613                         mDrawableHeightTemp = mDrawableHeightRight;
614 
615                         mShowing[Drawables.RIGHT] = mDrawableError;
616                         mDrawableSizeRight = mDrawableSizeError;
617                         mDrawableHeightRight = mDrawableHeightError;
618                         break;
619                 }
620             }
621         }
622     }
623 
624     Drawables mDrawables;
625 
626     private CharWrapper mCharWrapper;
627 
628     private Marquee mMarquee;
629     private boolean mRestartMarquee;
630 
631     private int mMarqueeRepeatLimit = 3;
632 
633     private int mLastLayoutDirection = -1;
634 
635     /**
636      * On some devices the fading edges add a performance penalty if used
637      * extensively in the same layout. This mode indicates how the marquee
638      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
639      */
640     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
641 
642     /**
643      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
644      * the layout that should be used when the mode switches.
645      */
646     private Layout mSavedMarqueeModeLayout;
647 
648     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
649     @ViewDebug.ExportedProperty(category = "text")
650     private @Nullable CharSequence mText;
651     private @Nullable Spannable mSpannable;
652     private @Nullable PrecomputedText mPrecomputed;
653 
654     private CharSequence mTransformed;
655     private BufferType mBufferType = BufferType.NORMAL;
656 
657     private CharSequence mHint;
658     private Layout mHintLayout;
659 
660     private MovementMethod mMovement;
661 
662     private TransformationMethod mTransformation;
663     private boolean mAllowTransformationLengthChange;
664     private ChangeWatcher mChangeWatcher;
665 
666     private ArrayList<TextWatcher> mListeners;
667 
668     // display attributes
669     private final TextPaint mTextPaint;
670     private boolean mUserSetTextScaleX;
671     private Layout mLayout;
672     private boolean mLocalesChanged = false;
673 
674     // True if setKeyListener() has been explicitly called
675     private boolean mListenerChanged = false;
676     // True if internationalized input should be used for numbers and date and time.
677     private final boolean mUseInternationalizedInput;
678     // True if fallback fonts that end up getting used should be allowed to affect line spacing.
679     /* package */ boolean mUseFallbackLineSpacing;
680 
681     @ViewDebug.ExportedProperty(category = "text")
682     private int mGravity = Gravity.TOP | Gravity.START;
683     private boolean mHorizontallyScrolling;
684 
685     private int mAutoLinkMask;
686     private boolean mLinksClickable = true;
687 
688     private float mSpacingMult = 1.0f;
689     private float mSpacingAdd = 0.0f;
690 
691     private int mBreakStrategy;
692     private int mHyphenationFrequency;
693     private int mJustificationMode;
694 
695     private int mMaximum = Integer.MAX_VALUE;
696     private int mMaxMode = LINES;
697     private int mMinimum = 0;
698     private int mMinMode = LINES;
699 
700     private int mOldMaximum = mMaximum;
701     private int mOldMaxMode = mMaxMode;
702 
703     private int mMaxWidth = Integer.MAX_VALUE;
704     private int mMaxWidthMode = PIXELS;
705     private int mMinWidth = 0;
706     private int mMinWidthMode = PIXELS;
707 
708     private boolean mSingleLine;
709     private int mDesiredHeightAtMeasure = -1;
710     private boolean mIncludePad = true;
711     private int mDeferScroll = -1;
712 
713     // tmp primitives, so we don't alloc them on each draw
714     private Rect mTempRect;
715     private long mLastScroll;
716     private Scroller mScroller;
717     private TextPaint mTempTextPaint;
718 
719     private BoringLayout.Metrics mBoring, mHintBoring;
720     private BoringLayout mSavedLayout, mSavedHintLayout;
721 
722     private TextDirectionHeuristic mTextDir;
723 
724     private InputFilter[] mFilters = NO_FILTERS;
725 
726     private volatile Locale mCurrentSpellCheckerLocaleCache;
727 
728     // It is possible to have a selection even when mEditor is null (programmatically set, like when
729     // a link is pressed). These highlight-related fields do not go in mEditor.
730     int mHighlightColor = 0x6633B5E5;
731     private Path mHighlightPath;
732     private final Paint mHighlightPaint;
733     private boolean mHighlightPathBogus = true;
734 
735     // Although these fields are specific to editable text, they are not added to Editor because
736     // they are defined by the TextView's style and are theme-dependent.
737     int mCursorDrawableRes;
738     // These six fields, could be moved to Editor, since we know their default values and we
739     // could condition the creation of the Editor to a non standard value. This is however
740     // brittle since the hardcoded values here (such as
741     // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
742     // default style is modified.
743     int mTextSelectHandleLeftRes;
744     int mTextSelectHandleRightRes;
745     int mTextSelectHandleRes;
746     int mTextEditSuggestionItemLayout;
747     int mTextEditSuggestionContainerLayout;
748     int mTextEditSuggestionHighlightStyle;
749 
750     /**
751      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
752      * See {@link #createEditorIfNeeded()}.
753      */
754     private Editor mEditor;
755 
756     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
757     private static final int DEVICE_PROVISIONED_NO = 1;
758     private static final int DEVICE_PROVISIONED_YES = 2;
759 
760     /**
761      * Some special options such as sharing selected text should only be shown if the device
762      * is provisioned. Only check the provisioned state once for a given view instance.
763      */
764     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
765 
766     /**
767      * The TextView does not auto-size text (default).
768      */
769     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
770 
771     /**
772      * The TextView scales text size both horizontally and vertically to fit within the
773      * container.
774      */
775     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
776 
777     /** @hide */
778     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
779             AUTO_SIZE_TEXT_TYPE_NONE,
780             AUTO_SIZE_TEXT_TYPE_UNIFORM
781     })
782     @Retention(RetentionPolicy.SOURCE)
783     public @interface AutoSizeTextType {}
784     // Default minimum size for auto-sizing text in scaled pixels.
785     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
786     // Default maximum size for auto-sizing text in scaled pixels.
787     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
788     // Default value for the step size in pixels.
789     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
790     // Use this to specify that any of the auto-size configuration int values have not been set.
791     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
792     // Auto-size text type.
793     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
794     // Specify if auto-size text is needed.
795     private boolean mNeedsAutoSizeText = false;
796     // Step size for auto-sizing in pixels.
797     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
798     // Minimum text size for auto-sizing in pixels.
799     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
800     // Maximum text size for auto-sizing in pixels.
801     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
802     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
803     // when auto-sizing text.
804     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
805     // Specifies whether auto-size should use the provided auto size steps set or if it should
806     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
807     // mAutoSizeStepGranularityInPx.
808     private boolean mHasPresetAutoSizeValues = false;
809 
810     // Autofill-related attributes
811     //
812     // Indicates whether the text was set statically or dynamically, so it can be used to
813     // sanitize autofill requests.
814     private boolean mTextSetFromXmlOrResourceId = false;
815     // Resource id used to set the text.
816     private @StringRes int mTextId = ResourceId.ID_NULL;
817     // Last value used on AFM.notifyValueChanged(), used to optimize autofill workflow by avoiding
818     // calls when the value did not change
819     private CharSequence mLastValueSentToAutofillManager;
820     //
821     // End of autofill-related attributes
822 
823     /**
824      * Kick-start the font cache for the zygote process (to pay the cost of
825      * initializing freetype for our default font only once).
826      * @hide
827      */
828     public static void preloadFontCache() {
829         Paint p = new Paint();
830         p.setAntiAlias(true);
831         // Ensure that the Typeface is loaded here.
832         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
833         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
834         // since Paint.measureText can not be called without Typeface static initializer.
835         p.setTypeface(Typeface.DEFAULT);
836         // We don't care about the result, just the side-effect of measuring.
837         p.measureText("H");
838     }
839 
840     /**
841      * Interface definition for a callback to be invoked when an action is
842      * performed on the editor.
843      */
844     public interface OnEditorActionListener {
845         /**
846          * Called when an action is being performed.
847          *
848          * @param v The view that was clicked.
849          * @param actionId Identifier of the action.  This will be either the
850          * identifier you supplied, or {@link EditorInfo#IME_NULL
851          * EditorInfo.IME_NULL} if being called due to the enter key
852          * being pressed.
853          * @param event If triggered by an enter key, this is the event;
854          * otherwise, this is null.
855          * @return Return true if you have consumed the action, else false.
856          */
857         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
858     }
859 
860     public TextView(Context context) {
861         this(context, null);
862     }
863 
864     public TextView(Context context, @Nullable AttributeSet attrs) {
865         this(context, attrs, com.android.internal.R.attr.textViewStyle);
866     }
867 
868     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
869         this(context, attrs, defStyleAttr, 0);
870     }
871 
872     @SuppressWarnings("deprecation")
873     public TextView(
874             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
875         super(context, attrs, defStyleAttr, defStyleRes);
876 
877         // TextView is important by default, unless app developer overrode attribute.
878         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
879             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
880         }
881 
882         setTextInternal("");
883 
884         final Resources res = getResources();
885         final CompatibilityInfo compat = res.getCompatibilityInfo();
886 
887         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
888         mTextPaint.density = res.getDisplayMetrics().density;
889         mTextPaint.setCompatibilityScaling(compat.applicationScale);
890 
891         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
892         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
893 
894         mMovement = getDefaultMovementMethod();
895 
896         mTransformation = null;
897 
898         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
899         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
900         attributes.mTextSize = 15;
901         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
902         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
903         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
904 
905         final Resources.Theme theme = context.getTheme();
906 
907         /*
908          * Look the appearance up without checking first if it exists because
909          * almost every TextView has one and it greatly simplifies the logic
910          * to be able to parse the appearance first and then let specific tags
911          * for this View override it.
912          */
913         TypedArray a = theme.obtainStyledAttributes(attrs,
914                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
915         TypedArray appearance = null;
916         int ap = a.getResourceId(
917                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
918         a.recycle();
919         if (ap != -1) {
920             appearance = theme.obtainStyledAttributes(
921                     ap, com.android.internal.R.styleable.TextAppearance);
922         }
923         if (appearance != null) {
924             readTextAppearance(context, appearance, attributes, false /* styleArray */);
925             attributes.mFontFamilyExplicit = false;
926             appearance.recycle();
927         }
928 
929         boolean editable = getDefaultEditable();
930         CharSequence inputMethod = null;
931         int numeric = 0;
932         CharSequence digits = null;
933         boolean phone = false;
934         boolean autotext = false;
935         int autocap = -1;
936         int buffertype = 0;
937         boolean selectallonfocus = false;
938         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
939                 drawableBottom = null, drawableStart = null, drawableEnd = null;
940         ColorStateList drawableTint = null;
941         PorterDuff.Mode drawableTintMode = null;
942         int drawablePadding = 0;
943         int ellipsize = ELLIPSIZE_NOT_SET;
944         boolean singleLine = false;
945         int maxlength = -1;
946         CharSequence text = "";
947         CharSequence hint = null;
948         boolean password = false;
949         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
950         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
951         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
952         int inputType = EditorInfo.TYPE_NULL;
953         a = theme.obtainStyledAttributes(
954                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
955         int firstBaselineToTopHeight = -1;
956         int lastBaselineToBottomHeight = -1;
957         int lineHeight = -1;
958 
959         readTextAppearance(context, a, attributes, true /* styleArray */);
960 
961         int n = a.getIndexCount();
962 
963         // Must set id in a temporary variable because it will be reset by setText()
964         boolean textIsSetFromXml = false;
965         for (int i = 0; i < n; i++) {
966             int attr = a.getIndex(i);
967 
968             switch (attr) {
969                 case com.android.internal.R.styleable.TextView_editable:
970                     editable = a.getBoolean(attr, editable);
971                     break;
972 
973                 case com.android.internal.R.styleable.TextView_inputMethod:
974                     inputMethod = a.getText(attr);
975                     break;
976 
977                 case com.android.internal.R.styleable.TextView_numeric:
978                     numeric = a.getInt(attr, numeric);
979                     break;
980 
981                 case com.android.internal.R.styleable.TextView_digits:
982                     digits = a.getText(attr);
983                     break;
984 
985                 case com.android.internal.R.styleable.TextView_phoneNumber:
986                     phone = a.getBoolean(attr, phone);
987                     break;
988 
989                 case com.android.internal.R.styleable.TextView_autoText:
990                     autotext = a.getBoolean(attr, autotext);
991                     break;
992 
993                 case com.android.internal.R.styleable.TextView_capitalize:
994                     autocap = a.getInt(attr, autocap);
995                     break;
996 
997                 case com.android.internal.R.styleable.TextView_bufferType:
998                     buffertype = a.getInt(attr, buffertype);
999                     break;
1000 
1001                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1002                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1003                     break;
1004 
1005                 case com.android.internal.R.styleable.TextView_autoLink:
1006                     mAutoLinkMask = a.getInt(attr, 0);
1007                     break;
1008 
1009                 case com.android.internal.R.styleable.TextView_linksClickable:
1010                     mLinksClickable = a.getBoolean(attr, true);
1011                     break;
1012 
1013                 case com.android.internal.R.styleable.TextView_drawableLeft:
1014                     drawableLeft = a.getDrawable(attr);
1015                     break;
1016 
1017                 case com.android.internal.R.styleable.TextView_drawableTop:
1018                     drawableTop = a.getDrawable(attr);
1019                     break;
1020 
1021                 case com.android.internal.R.styleable.TextView_drawableRight:
1022                     drawableRight = a.getDrawable(attr);
1023                     break;
1024 
1025                 case com.android.internal.R.styleable.TextView_drawableBottom:
1026                     drawableBottom = a.getDrawable(attr);
1027                     break;
1028 
1029                 case com.android.internal.R.styleable.TextView_drawableStart:
1030                     drawableStart = a.getDrawable(attr);
1031                     break;
1032 
1033                 case com.android.internal.R.styleable.TextView_drawableEnd:
1034                     drawableEnd = a.getDrawable(attr);
1035                     break;
1036 
1037                 case com.android.internal.R.styleable.TextView_drawableTint:
1038                     drawableTint = a.getColorStateList(attr);
1039                     break;
1040 
1041                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1042                     drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
1043                     break;
1044 
1045                 case com.android.internal.R.styleable.TextView_drawablePadding:
1046                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1047                     break;
1048 
1049                 case com.android.internal.R.styleable.TextView_maxLines:
1050                     setMaxLines(a.getInt(attr, -1));
1051                     break;
1052 
1053                 case com.android.internal.R.styleable.TextView_maxHeight:
1054                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1055                     break;
1056 
1057                 case com.android.internal.R.styleable.TextView_lines:
1058                     setLines(a.getInt(attr, -1));
1059                     break;
1060 
1061                 case com.android.internal.R.styleable.TextView_height:
1062                     setHeight(a.getDimensionPixelSize(attr, -1));
1063                     break;
1064 
1065                 case com.android.internal.R.styleable.TextView_minLines:
1066                     setMinLines(a.getInt(attr, -1));
1067                     break;
1068 
1069                 case com.android.internal.R.styleable.TextView_minHeight:
1070                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1071                     break;
1072 
1073                 case com.android.internal.R.styleable.TextView_maxEms:
1074                     setMaxEms(a.getInt(attr, -1));
1075                     break;
1076 
1077                 case com.android.internal.R.styleable.TextView_maxWidth:
1078                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1079                     break;
1080 
1081                 case com.android.internal.R.styleable.TextView_ems:
1082                     setEms(a.getInt(attr, -1));
1083                     break;
1084 
1085                 case com.android.internal.R.styleable.TextView_width:
1086                     setWidth(a.getDimensionPixelSize(attr, -1));
1087                     break;
1088 
1089                 case com.android.internal.R.styleable.TextView_minEms:
1090                     setMinEms(a.getInt(attr, -1));
1091                     break;
1092 
1093                 case com.android.internal.R.styleable.TextView_minWidth:
1094                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1095                     break;
1096 
1097                 case com.android.internal.R.styleable.TextView_gravity:
1098                     setGravity(a.getInt(attr, -1));
1099                     break;
1100 
1101                 case com.android.internal.R.styleable.TextView_hint:
1102                     hint = a.getText(attr);
1103                     break;
1104 
1105                 case com.android.internal.R.styleable.TextView_text:
1106                     textIsSetFromXml = true;
1107                     mTextId = a.getResourceId(attr, ResourceId.ID_NULL);
1108                     text = a.getText(attr);
1109                     break;
1110 
1111                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1112                     if (a.getBoolean(attr, false)) {
1113                         setHorizontallyScrolling(true);
1114                     }
1115                     break;
1116 
1117                 case com.android.internal.R.styleable.TextView_singleLine:
1118                     singleLine = a.getBoolean(attr, singleLine);
1119                     break;
1120 
1121                 case com.android.internal.R.styleable.TextView_ellipsize:
1122                     ellipsize = a.getInt(attr, ellipsize);
1123                     break;
1124 
1125                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1126                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1127                     break;
1128 
1129                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1130                     if (!a.getBoolean(attr, true)) {
1131                         setIncludeFontPadding(false);
1132                     }
1133                     break;
1134 
1135                 case com.android.internal.R.styleable.TextView_cursorVisible:
1136                     if (!a.getBoolean(attr, true)) {
1137                         setCursorVisible(false);
1138                     }
1139                     break;
1140 
1141                 case com.android.internal.R.styleable.TextView_maxLength:
1142                     maxlength = a.getInt(attr, -1);
1143                     break;
1144 
1145                 case com.android.internal.R.styleable.TextView_textScaleX:
1146                     setTextScaleX(a.getFloat(attr, 1.0f));
1147                     break;
1148 
1149                 case com.android.internal.R.styleable.TextView_freezesText:
1150                     mFreezesText = a.getBoolean(attr, false);
1151                     break;
1152 
1153                 case com.android.internal.R.styleable.TextView_enabled:
1154                     setEnabled(a.getBoolean(attr, isEnabled()));
1155                     break;
1156 
1157                 case com.android.internal.R.styleable.TextView_password:
1158                     password = a.getBoolean(attr, password);
1159                     break;
1160 
1161                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1162                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1163                     break;
1164 
1165                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1166                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1167                     break;
1168 
1169                 case com.android.internal.R.styleable.TextView_inputType:
1170                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1171                     break;
1172 
1173                 case com.android.internal.R.styleable.TextView_allowUndo:
1174                     createEditorIfNeeded();
1175                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1176                     break;
1177 
1178                 case com.android.internal.R.styleable.TextView_imeOptions:
1179                     createEditorIfNeeded();
1180                     mEditor.createInputContentTypeIfNeeded();
1181                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1182                             mEditor.mInputContentType.imeOptions);
1183                     break;
1184 
1185                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1186                     createEditorIfNeeded();
1187                     mEditor.createInputContentTypeIfNeeded();
1188                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1189                     break;
1190 
1191                 case com.android.internal.R.styleable.TextView_imeActionId:
1192                     createEditorIfNeeded();
1193                     mEditor.createInputContentTypeIfNeeded();
1194                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1195                             mEditor.mInputContentType.imeActionId);
1196                     break;
1197 
1198                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1199                     setPrivateImeOptions(a.getString(attr));
1200                     break;
1201 
1202                 case com.android.internal.R.styleable.TextView_editorExtras:
1203                     try {
1204                         setInputExtras(a.getResourceId(attr, 0));
1205                     } catch (XmlPullParserException e) {
1206                         Log.w(LOG_TAG, "Failure reading input extras", e);
1207                     } catch (IOException e) {
1208                         Log.w(LOG_TAG, "Failure reading input extras", e);
1209                     }
1210                     break;
1211 
1212                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1213                     mCursorDrawableRes = a.getResourceId(attr, 0);
1214                     break;
1215 
1216                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1217                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1218                     break;
1219 
1220                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1221                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1222                     break;
1223 
1224                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1225                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1226                     break;
1227 
1228                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1229                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1230                     break;
1231 
1232                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1233                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1234                     break;
1235 
1236                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1237                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1238                     break;
1239 
1240                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1241                     setTextIsSelectable(a.getBoolean(attr, false));
1242                     break;
1243 
1244                 case com.android.internal.R.styleable.TextView_breakStrategy:
1245                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1246                     break;
1247 
1248                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1249                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1250                     break;
1251 
1252                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1253                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1254                     break;
1255 
1256                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1257                     autoSizeStepGranularityInPx = a.getDimension(attr,
1258                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1259                     break;
1260 
1261                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1262                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1263                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1264                     break;
1265 
1266                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1267                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1268                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1269                     break;
1270 
1271                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1272                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1273                     if (autoSizeStepSizeArrayResId > 0) {
1274                         final TypedArray autoSizePresetTextSizes = a.getResources()
1275                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1276                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1277                         autoSizePresetTextSizes.recycle();
1278                     }
1279                     break;
1280                 case com.android.internal.R.styleable.TextView_justificationMode:
1281                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1282                     break;
1283 
1284                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1285                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1286                     break;
1287 
1288                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1289                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1290                     break;
1291 
1292                 case com.android.internal.R.styleable.TextView_lineHeight:
1293                     lineHeight = a.getDimensionPixelSize(attr, -1);
1294                     break;
1295             }
1296         }
1297 
1298         a.recycle();
1299 
1300         BufferType bufferType = BufferType.EDITABLE;
1301 
1302         final int variation =
1303                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1304         final boolean passwordInputType = variation
1305                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1306         final boolean webPasswordInputType = variation
1307                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1308         final boolean numberPasswordInputType = variation
1309                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1310 
1311         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1312         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1313         mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
1314 
1315         if (inputMethod != null) {
1316             Class<?> c;
1317 
1318             try {
1319                 c = Class.forName(inputMethod.toString());
1320             } catch (ClassNotFoundException ex) {
1321                 throw new RuntimeException(ex);
1322             }
1323 
1324             try {
1325                 createEditorIfNeeded();
1326                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1327             } catch (InstantiationException ex) {
1328                 throw new RuntimeException(ex);
1329             } catch (IllegalAccessException ex) {
1330                 throw new RuntimeException(ex);
1331             }
1332             try {
1333                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1334                         ? inputType
1335                         : mEditor.mKeyListener.getInputType();
1336             } catch (IncompatibleClassChangeError e) {
1337                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1338             }
1339         } else if (digits != null) {
1340             createEditorIfNeeded();
1341             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1342             // If no input type was specified, we will default to generic
1343             // text, since we can't tell the IME about the set of digits
1344             // that was selected.
1345             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1346                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1347         } else if (inputType != EditorInfo.TYPE_NULL) {
1348             setInputType(inputType, true);
1349             // If set, the input type overrides what was set using the deprecated singleLine flag.
1350             singleLine = !isMultilineInputType(inputType);
1351         } else if (phone) {
1352             createEditorIfNeeded();
1353             mEditor.mKeyListener = DialerKeyListener.getInstance();
1354             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1355         } else if (numeric != 0) {
1356             createEditorIfNeeded();
1357             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1358                     null,  // locale
1359                     (numeric & SIGNED) != 0,
1360                     (numeric & DECIMAL) != 0);
1361             inputType = mEditor.mKeyListener.getInputType();
1362             mEditor.mInputType = inputType;
1363         } else if (autotext || autocap != -1) {
1364             TextKeyListener.Capitalize cap;
1365 
1366             inputType = EditorInfo.TYPE_CLASS_TEXT;
1367 
1368             switch (autocap) {
1369                 case 1:
1370                     cap = TextKeyListener.Capitalize.SENTENCES;
1371                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1372                     break;
1373 
1374                 case 2:
1375                     cap = TextKeyListener.Capitalize.WORDS;
1376                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1377                     break;
1378 
1379                 case 3:
1380                     cap = TextKeyListener.Capitalize.CHARACTERS;
1381                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1382                     break;
1383 
1384                 default:
1385                     cap = TextKeyListener.Capitalize.NONE;
1386                     break;
1387             }
1388 
1389             createEditorIfNeeded();
1390             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1391             mEditor.mInputType = inputType;
1392         } else if (editable) {
1393             createEditorIfNeeded();
1394             mEditor.mKeyListener = TextKeyListener.getInstance();
1395             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1396         } else if (isTextSelectable()) {
1397             // Prevent text changes from keyboard.
1398             if (mEditor != null) {
1399                 mEditor.mKeyListener = null;
1400                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1401             }
1402             bufferType = BufferType.SPANNABLE;
1403             // So that selection can be changed using arrow keys and touch is handled.
1404             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1405         } else {
1406             if (mEditor != null) mEditor.mKeyListener = null;
1407 
1408             switch (buffertype) {
1409                 case 0:
1410                     bufferType = BufferType.NORMAL;
1411                     break;
1412                 case 1:
1413                     bufferType = BufferType.SPANNABLE;
1414                     break;
1415                 case 2:
1416                     bufferType = BufferType.EDITABLE;
1417                     break;
1418             }
1419         }
1420 
1421         if (mEditor != null) {
1422             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1423                     numberPasswordInputType);
1424         }
1425 
1426         if (selectallonfocus) {
1427             createEditorIfNeeded();
1428             mEditor.mSelectAllOnFocus = true;
1429 
1430             if (bufferType == BufferType.NORMAL) {
1431                 bufferType = BufferType.SPANNABLE;
1432             }
1433         }
1434 
1435         // Set up the tint (if needed) before setting the drawables so that it
1436         // gets applied correctly.
1437         if (drawableTint != null || drawableTintMode != null) {
1438             if (mDrawables == null) {
1439                 mDrawables = new Drawables(context);
1440             }
1441             if (drawableTint != null) {
1442                 mDrawables.mTintList = drawableTint;
1443                 mDrawables.mHasTint = true;
1444             }
1445             if (drawableTintMode != null) {
1446                 mDrawables.mTintMode = drawableTintMode;
1447                 mDrawables.mHasTintMode = true;
1448             }
1449         }
1450 
1451         // This call will save the initial left/right drawables
1452         setCompoundDrawablesWithIntrinsicBounds(
1453                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1454         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1455         setCompoundDrawablePadding(drawablePadding);
1456 
1457         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1458         // of lines of height are unchanged for multi-line TextViews.
1459         setInputTypeSingleLine(singleLine);
1460         applySingleLine(singleLine, singleLine, singleLine);
1461 
1462         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1463             ellipsize = ELLIPSIZE_END;
1464         }
1465 
1466         switch (ellipsize) {
1467             case ELLIPSIZE_START:
1468                 setEllipsize(TextUtils.TruncateAt.START);
1469                 break;
1470             case ELLIPSIZE_MIDDLE:
1471                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1472                 break;
1473             case ELLIPSIZE_END:
1474                 setEllipsize(TextUtils.TruncateAt.END);
1475                 break;
1476             case ELLIPSIZE_MARQUEE:
1477                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1478                     setHorizontalFadingEdgeEnabled(true);
1479                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1480                 } else {
1481                     setHorizontalFadingEdgeEnabled(false);
1482                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1483                 }
1484                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1485                 break;
1486         }
1487 
1488         final boolean isPassword = password || passwordInputType || webPasswordInputType
1489                 || numberPasswordInputType;
1490         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1491                 && (mEditor.mInputType
1492                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1493                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1494         if (isMonospaceEnforced) {
1495             attributes.mTypefaceIndex = MONOSPACE;
1496         }
1497 
1498         applyTextAppearance(attributes);
1499 
1500         if (isPassword) {
1501             setTransformationMethod(PasswordTransformationMethod.getInstance());
1502         }
1503 
1504         if (maxlength >= 0) {
1505             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1506         } else {
1507             setFilters(NO_FILTERS);
1508         }
1509 
1510         setText(text, bufferType);
1511         if (textIsSetFromXml) {
1512             mTextSetFromXmlOrResourceId = true;
1513         }
1514 
1515         if (hint != null) setHint(hint);
1516 
1517         /*
1518          * Views are not normally clickable unless specified to be.
1519          * However, TextViews that have input or movement methods *are*
1520          * clickable by default. By setting clickable here, we implicitly set focusable as well
1521          * if not overridden by the developer.
1522          */
1523         a = context.obtainStyledAttributes(
1524                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1525         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1526         boolean clickable = canInputOrMove || isClickable();
1527         boolean longClickable = canInputOrMove || isLongClickable();
1528         int focusable = getFocusable();
1529 
1530         n = a.getIndexCount();
1531         for (int i = 0; i < n; i++) {
1532             int attr = a.getIndex(i);
1533 
1534             switch (attr) {
1535                 case com.android.internal.R.styleable.View_focusable:
1536                     TypedValue val = new TypedValue();
1537                     if (a.getValue(attr, val)) {
1538                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1539                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1540                                 : val.data;
1541                     }
1542                     break;
1543 
1544                 case com.android.internal.R.styleable.View_clickable:
1545                     clickable = a.getBoolean(attr, clickable);
1546                     break;
1547 
1548                 case com.android.internal.R.styleable.View_longClickable:
1549                     longClickable = a.getBoolean(attr, longClickable);
1550                     break;
1551             }
1552         }
1553         a.recycle();
1554 
1555         // Some apps were relying on the undefined behavior of focusable winning over
1556         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1557         // when starting with EditText and setting only focusable=false). To keep those apps from
1558         // breaking, re-apply the focusable attribute here.
1559         if (focusable != getFocusable()) {
1560             setFocusable(focusable);
1561         }
1562         setClickable(clickable);
1563         setLongClickable(longClickable);
1564 
1565         if (mEditor != null) mEditor.prepareCursorControllers();
1566 
1567         // If not explicitly specified this view is important for accessibility.
1568         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1569             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1570         }
1571 
1572         if (supportsAutoSizeText()) {
1573             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1574                 // If uniform auto-size has been specified but preset values have not been set then
1575                 // replace the auto-size configuration values that have not been specified with the
1576                 // defaults.
1577                 if (!mHasPresetAutoSizeValues) {
1578                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1579 
1580                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1581                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1582                                 TypedValue.COMPLEX_UNIT_SP,
1583                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1584                                 displayMetrics);
1585                     }
1586 
1587                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1588                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1589                                 TypedValue.COMPLEX_UNIT_SP,
1590                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1591                                 displayMetrics);
1592                     }
1593 
1594                     if (autoSizeStepGranularityInPx
1595                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1596                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1597                     }
1598 
1599                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1600                             autoSizeMaxTextSizeInPx,
1601                             autoSizeStepGranularityInPx);
1602                 }
1603 
1604                 setupAutoSizeText();
1605             }
1606         } else {
1607             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1608         }
1609 
1610         if (firstBaselineToTopHeight >= 0) {
1611             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1612         }
1613         if (lastBaselineToBottomHeight >= 0) {
1614             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1615         }
1616         if (lineHeight >= 0) {
1617             setLineHeight(lineHeight);
1618         }
1619     }
1620 
1621     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)1622     private void setTextInternal(@Nullable CharSequence text) {
1623         mText = text;
1624         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
1625         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
1626     }
1627 
1628     /**
1629      * Specify whether this widget should automatically scale the text to try to perfectly fit
1630      * within the layout bounds by using the default auto-size configuration.
1631      *
1632      * @param autoSizeTextType the type of auto-size. Must be one of
1633      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1634      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1635      *
1636      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1637      *
1638      * @attr ref android.R.styleable#TextView_autoSizeTextType
1639      *
1640      * @see #getAutoSizeTextType()
1641      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1642     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1643         if (supportsAutoSizeText()) {
1644             switch (autoSizeTextType) {
1645                 case AUTO_SIZE_TEXT_TYPE_NONE:
1646                     clearAutoSizeConfiguration();
1647                     break;
1648                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1649                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1650                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1651                             TypedValue.COMPLEX_UNIT_SP,
1652                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1653                             displayMetrics);
1654                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1655                             TypedValue.COMPLEX_UNIT_SP,
1656                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1657                             displayMetrics);
1658 
1659                     validateAndSetAutoSizeTextTypeUniformConfiguration(
1660                             autoSizeMinTextSizeInPx,
1661                             autoSizeMaxTextSizeInPx,
1662                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1663                     if (setupAutoSizeText()) {
1664                         autoSizeText();
1665                         invalidate();
1666                     }
1667                     break;
1668                 default:
1669                     throw new IllegalArgumentException(
1670                             "Unknown auto-size text type: " + autoSizeTextType);
1671             }
1672         }
1673     }
1674 
1675     /**
1676      * Specify whether this widget should automatically scale the text to try to perfectly fit
1677      * within the layout bounds. If all the configuration params are valid the type of auto-size is
1678      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1679      *
1680      * @param autoSizeMinTextSize the minimum text size available for auto-size
1681      * @param autoSizeMaxTextSize the maximum text size available for auto-size
1682      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1683      *                                the minimum and maximum text size in order to build the set of
1684      *                                text sizes the system uses to choose from when auto-sizing
1685      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1686      *             possible dimension units
1687      *
1688      * @throws IllegalArgumentException if any of the configuration params are invalid.
1689      *
1690      * @attr ref android.R.styleable#TextView_autoSizeTextType
1691      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1692      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1693      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1694      *
1695      * @see #setAutoSizeTextTypeWithDefaults(int)
1696      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1697      * @see #getAutoSizeMinTextSize()
1698      * @see #getAutoSizeMaxTextSize()
1699      * @see #getAutoSizeStepGranularity()
1700      * @see #getAutoSizeTextAvailableSizes()
1701      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1702     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1703             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1704         if (supportsAutoSizeText()) {
1705             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1706             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1707                     unit, autoSizeMinTextSize, displayMetrics);
1708             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1709                     unit, autoSizeMaxTextSize, displayMetrics);
1710             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1711                     unit, autoSizeStepGranularity, displayMetrics);
1712 
1713             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1714                     autoSizeMaxTextSizeInPx,
1715                     autoSizeStepGranularityInPx);
1716 
1717             if (setupAutoSizeText()) {
1718                 autoSizeText();
1719                 invalidate();
1720             }
1721         }
1722     }
1723 
1724     /**
1725      * Specify whether this widget should automatically scale the text to try to perfectly fit
1726      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1727      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1728      *
1729      * @param presetSizes an {@code int} array of sizes in pixels
1730      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1731      *             the possible dimension units
1732      *
1733      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1734      *
1735      * @attr ref android.R.styleable#TextView_autoSizeTextType
1736      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1737      *
1738      * @see #setAutoSizeTextTypeWithDefaults(int)
1739      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1740      * @see #getAutoSizeMinTextSize()
1741      * @see #getAutoSizeMaxTextSize()
1742      * @see #getAutoSizeTextAvailableSizes()
1743      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1744     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1745         if (supportsAutoSizeText()) {
1746             final int presetSizesLength = presetSizes.length;
1747             if (presetSizesLength > 0) {
1748                 int[] presetSizesInPx = new int[presetSizesLength];
1749 
1750                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1751                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1752                 } else {
1753                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1754                     // Convert all to sizes to pixels.
1755                     for (int i = 0; i < presetSizesLength; i++) {
1756                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
1757                             presetSizes[i], displayMetrics));
1758                     }
1759                 }
1760 
1761                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1762                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
1763                     throw new IllegalArgumentException("None of the preset sizes is valid: "
1764                             + Arrays.toString(presetSizes));
1765                 }
1766             } else {
1767                 mHasPresetAutoSizeValues = false;
1768             }
1769 
1770             if (setupAutoSizeText()) {
1771                 autoSizeText();
1772                 invalidate();
1773             }
1774         }
1775     }
1776 
1777     /**
1778      * Returns the type of auto-size set for this widget.
1779      *
1780      * @return an {@code int} corresponding to one of the auto-size types:
1781      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1782      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1783      *
1784      * @attr ref android.R.styleable#TextView_autoSizeTextType
1785      *
1786      * @see #setAutoSizeTextTypeWithDefaults(int)
1787      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1788      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1789      */
1790     @AutoSizeTextType
getAutoSizeTextType()1791     public int getAutoSizeTextType() {
1792         return mAutoSizeTextType;
1793     }
1794 
1795     /**
1796      * @return the current auto-size step granularity in pixels.
1797      *
1798      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1799      *
1800      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1801      */
getAutoSizeStepGranularity()1802     public int getAutoSizeStepGranularity() {
1803         return Math.round(mAutoSizeStepGranularityInPx);
1804     }
1805 
1806     /**
1807      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
1808      *         if auto-size has not been configured this function returns {@code -1}.
1809      *
1810      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1811      *
1812      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1813      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1814      */
getAutoSizeMinTextSize()1815     public int getAutoSizeMinTextSize() {
1816         return Math.round(mAutoSizeMinTextSizeInPx);
1817     }
1818 
1819     /**
1820      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
1821      *         if auto-size has not been configured this function returns {@code -1}.
1822      *
1823      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1824      *
1825      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1826      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1827      */
getAutoSizeMaxTextSize()1828     public int getAutoSizeMaxTextSize() {
1829         return Math.round(mAutoSizeMaxTextSizeInPx);
1830     }
1831 
1832     /**
1833      * @return the current auto-size {@code int} sizes array (in pixels).
1834      *
1835      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1836      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1837      */
getAutoSizeTextAvailableSizes()1838     public int[] getAutoSizeTextAvailableSizes() {
1839         return mAutoSizeTextSizesInPx;
1840     }
1841 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)1842     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
1843         final int textSizesLength = textSizes.length();
1844         final int[] parsedSizes = new int[textSizesLength];
1845 
1846         if (textSizesLength > 0) {
1847             for (int i = 0; i < textSizesLength; i++) {
1848                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
1849             }
1850             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
1851             setupAutoSizeUniformPresetSizesConfiguration();
1852         }
1853     }
1854 
setupAutoSizeUniformPresetSizesConfiguration()1855     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
1856         final int sizesLength = mAutoSizeTextSizesInPx.length;
1857         mHasPresetAutoSizeValues = sizesLength > 0;
1858         if (mHasPresetAutoSizeValues) {
1859             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1860             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
1861             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
1862             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1863         }
1864         return mHasPresetAutoSizeValues;
1865     }
1866 
1867     /**
1868      * If all params are valid then save the auto-size configuration.
1869      *
1870      * @throws IllegalArgumentException if any of the params are invalid
1871      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)1872     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
1873             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
1874         // First validate.
1875         if (autoSizeMinTextSizeInPx <= 0) {
1876             throw new IllegalArgumentException("Minimum auto-size text size ("
1877                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
1878         }
1879 
1880         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
1881             throw new IllegalArgumentException("Maximum auto-size text size ("
1882                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
1883                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
1884         }
1885 
1886         if (autoSizeStepGranularityInPx <= 0) {
1887             throw new IllegalArgumentException("The auto-size step granularity ("
1888                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
1889         }
1890 
1891         // All good, persist the configuration.
1892         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1893         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
1894         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
1895         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
1896         mHasPresetAutoSizeValues = false;
1897     }
1898 
clearAutoSizeConfiguration()1899     private void clearAutoSizeConfiguration() {
1900         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1901         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1902         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1903         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1904         mAutoSizeTextSizesInPx = EmptyArray.INT;
1905         mNeedsAutoSizeText = false;
1906     }
1907 
1908     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)1909     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
1910         final int presetValuesLength = presetValues.length;
1911         if (presetValuesLength == 0) {
1912             return presetValues;
1913         }
1914         Arrays.sort(presetValues);
1915 
1916         final IntArray uniqueValidSizes = new IntArray();
1917         for (int i = 0; i < presetValuesLength; i++) {
1918             final int currentPresetValue = presetValues[i];
1919 
1920             if (currentPresetValue > 0
1921                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
1922                 uniqueValidSizes.add(currentPresetValue);
1923             }
1924         }
1925 
1926         return presetValuesLength == uniqueValidSizes.size()
1927             ? presetValues
1928             : uniqueValidSizes.toArray();
1929     }
1930 
setupAutoSizeText()1931     private boolean setupAutoSizeText() {
1932         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1933             // Calculate the sizes set based on minimum size, maximum size and step size if we do
1934             // not have a predefined set of sizes or if the current sizes array is empty.
1935             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
1936                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
1937                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
1938                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
1939                 for (int i = 0; i < autoSizeValuesLength; i++) {
1940                     autoSizeTextSizesInPx[i] = Math.round(
1941                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
1942                 }
1943                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
1944             }
1945 
1946             mNeedsAutoSizeText = true;
1947         } else {
1948             mNeedsAutoSizeText = false;
1949         }
1950 
1951         return mNeedsAutoSizeText;
1952     }
1953 
parseDimensionArray(TypedArray dimens)1954     private int[] parseDimensionArray(TypedArray dimens) {
1955         if (dimens == null) {
1956             return null;
1957         }
1958         int[] result = new int[dimens.length()];
1959         for (int i = 0; i < result.length; i++) {
1960             result[i] = dimens.getDimensionPixelSize(i, 0);
1961         }
1962         return result;
1963     }
1964 
1965     /**
1966      * @hide
1967      */
1968     @Override
onActivityResult(int requestCode, int resultCode, Intent data)1969     public void onActivityResult(int requestCode, int resultCode, Intent data) {
1970         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
1971             if (resultCode == Activity.RESULT_OK && data != null) {
1972                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
1973                 if (result != null) {
1974                     if (isTextEditable()) {
1975                         replaceSelectionWithText(result);
1976                         if (mEditor != null) {
1977                             mEditor.refreshTextActionMode();
1978                         }
1979                     } else {
1980                         if (result.length() > 0) {
1981                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
1982                                 .show();
1983                         }
1984                     }
1985                 }
1986             } else if (mSpannable != null) {
1987                 // Reset the selection.
1988                 Selection.setSelection(mSpannable, getSelectionEnd());
1989             }
1990         }
1991     }
1992 
1993     /**
1994      * Sets the Typeface taking into account the given attributes.
1995      *
1996      * @param typeface a typeface
1997      * @param familyName family name string, e.g. "serif"
1998      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
1999      * @param style a typeface style
2000      * @param weight a weight value for the Typeface or -1 if not specified.
2001      */
setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = Typeface.MAX_WEIGHT) int weight)2002     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2003             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2004             @IntRange(from = -1, to = Typeface.MAX_WEIGHT) int weight) {
2005         if (typeface == null && familyName != null) {
2006             // Lookup normal Typeface from system font map.
2007             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2008             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2009         } else if (typeface != null) {
2010             resolveStyleAndSetTypeface(typeface, style, weight);
2011         } else {  // both typeface and familyName is null.
2012             switch (typefaceIndex) {
2013                 case SANS:
2014                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2015                     break;
2016                 case SERIF:
2017                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2018                     break;
2019                 case MONOSPACE:
2020                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2021                     break;
2022                 case DEFAULT_TYPEFACE:
2023                 default:
2024                     resolveStyleAndSetTypeface(null, style, weight);
2025                     break;
2026             }
2027         }
2028     }
2029 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = Typeface.MAX_WEIGHT) int weight)2030     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2031             @IntRange(from = -1, to = Typeface.MAX_WEIGHT) int weight) {
2032         if (weight >= 0) {
2033             weight = Math.min(Typeface.MAX_WEIGHT, weight);
2034             final boolean italic = (style & Typeface.ITALIC) != 0;
2035             setTypeface(Typeface.create(typeface, weight, italic));
2036         } else {
2037             setTypeface(typeface, style);
2038         }
2039     }
2040 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2041     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2042         boolean hasRelativeDrawables = (start != null) || (end != null);
2043         if (hasRelativeDrawables) {
2044             Drawables dr = mDrawables;
2045             if (dr == null) {
2046                 mDrawables = dr = new Drawables(getContext());
2047             }
2048             mDrawables.mOverride = true;
2049             final Rect compoundRect = dr.mCompoundRect;
2050             int[] state = getDrawableState();
2051             if (start != null) {
2052                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2053                 start.setState(state);
2054                 start.copyBounds(compoundRect);
2055                 start.setCallback(this);
2056 
2057                 dr.mDrawableStart = start;
2058                 dr.mDrawableSizeStart = compoundRect.width();
2059                 dr.mDrawableHeightStart = compoundRect.height();
2060             } else {
2061                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2062             }
2063             if (end != null) {
2064                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2065                 end.setState(state);
2066                 end.copyBounds(compoundRect);
2067                 end.setCallback(this);
2068 
2069                 dr.mDrawableEnd = end;
2070                 dr.mDrawableSizeEnd = compoundRect.width();
2071                 dr.mDrawableHeightEnd = compoundRect.height();
2072             } else {
2073                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2074             }
2075             resetResolvedDrawables();
2076             resolveDrawables();
2077             applyCompoundDrawableTint();
2078         }
2079     }
2080 
2081     @android.view.RemotableViewMethod
2082     @Override
setEnabled(boolean enabled)2083     public void setEnabled(boolean enabled) {
2084         if (enabled == isEnabled()) {
2085             return;
2086         }
2087 
2088         if (!enabled) {
2089             // Hide the soft input if the currently active TextView is disabled
2090             InputMethodManager imm = InputMethodManager.peekInstance();
2091             if (imm != null && imm.isActive(this)) {
2092                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2093             }
2094         }
2095 
2096         super.setEnabled(enabled);
2097 
2098         if (enabled) {
2099             // Make sure IME is updated with current editor info.
2100             InputMethodManager imm = InputMethodManager.peekInstance();
2101             if (imm != null) imm.restartInput(this);
2102         }
2103 
2104         // Will change text color
2105         if (mEditor != null) {
2106             mEditor.invalidateTextDisplayList();
2107             mEditor.prepareCursorControllers();
2108 
2109             // start or stop the cursor blinking as appropriate
2110             mEditor.makeBlink();
2111         }
2112     }
2113 
2114     /**
2115      * Sets the typeface and style in which the text should be displayed,
2116      * and turns on the fake bold and italic bits in the Paint if the
2117      * Typeface that you provided does not have all the bits in the
2118      * style that you specified.
2119      *
2120      * @attr ref android.R.styleable#TextView_typeface
2121      * @attr ref android.R.styleable#TextView_textStyle
2122      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2123     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2124         if (style > 0) {
2125             if (tf == null) {
2126                 tf = Typeface.defaultFromStyle(style);
2127             } else {
2128                 tf = Typeface.create(tf, style);
2129             }
2130 
2131             setTypeface(tf);
2132             // now compute what (if any) algorithmic styling is needed
2133             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2134             int need = style & ~typefaceStyle;
2135             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2136             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2137         } else {
2138             mTextPaint.setFakeBoldText(false);
2139             mTextPaint.setTextSkewX(0);
2140             setTypeface(tf);
2141         }
2142     }
2143 
2144     /**
2145      * Subclasses override this to specify that they have a KeyListener
2146      * by default even if not specifically called for in the XML options.
2147      */
getDefaultEditable()2148     protected boolean getDefaultEditable() {
2149         return false;
2150     }
2151 
2152     /**
2153      * Subclasses override this to specify a default movement method.
2154      */
getDefaultMovementMethod()2155     protected MovementMethod getDefaultMovementMethod() {
2156         return null;
2157     }
2158 
2159     /**
2160      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2161      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2162      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2163      * the return value from this method to Spannable or Editable, respectively.
2164      *
2165      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2166      * should make your own copy first.</p>
2167      *
2168      * @return The text displayed by the text view.
2169      * @attr ref android.R.styleable#TextView_text
2170      */
2171     @ViewDebug.CapturedViewProperty
getText()2172     public CharSequence getText() {
2173         return mText;
2174     }
2175 
2176     /**
2177      * Returns the length, in characters, of the text managed by this TextView
2178      * @return The length of the text managed by the TextView in characters.
2179      */
length()2180     public int length() {
2181         return mText.length();
2182     }
2183 
2184     /**
2185      * Return the text that TextView is displaying as an Editable object. If the text is not
2186      * editable, null is returned.
2187      *
2188      * @see #getText
2189      */
getEditableText()2190     public Editable getEditableText() {
2191         return (mText instanceof Editable) ? (Editable) mText : null;
2192     }
2193 
2194     /**
2195      * Gets the vertical distance between lines of text, in pixels.
2196      * Note that markup within the text can cause individual lines
2197      * to be taller or shorter than this height, and the layout may
2198      * contain additional first-or last-line padding.
2199      * @return The height of one standard line in pixels.
2200      */
getLineHeight()2201     public int getLineHeight() {
2202         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2203     }
2204 
2205     /**
2206      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2207      * This value can be null if the text or width has recently changed.
2208      * @return The Layout that is currently being used to display the text.
2209      */
getLayout()2210     public final Layout getLayout() {
2211         return mLayout;
2212     }
2213 
2214     /**
2215      * @return the {@link android.text.Layout} that is currently being used to
2216      * display the hint text. This can be null.
2217      */
getHintLayout()2218     final Layout getHintLayout() {
2219         return mHintLayout;
2220     }
2221 
2222     /**
2223      * Retrieve the {@link android.content.UndoManager} that is currently associated
2224      * with this TextView.  By default there is no associated UndoManager, so null
2225      * is returned.  One can be associated with the TextView through
2226      * {@link #setUndoManager(android.content.UndoManager, String)}
2227      *
2228      * @hide
2229      */
getUndoManager()2230     public final UndoManager getUndoManager() {
2231         // TODO: Consider supporting a global undo manager.
2232         throw new UnsupportedOperationException("not implemented");
2233     }
2234 
2235 
2236     /**
2237      * @hide
2238      */
2239     @VisibleForTesting
getEditorForTesting()2240     public final Editor getEditorForTesting() {
2241         return mEditor;
2242     }
2243 
2244     /**
2245      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2246      * done, all edit operations on the TextView will result in appropriate
2247      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2248      * stack.
2249      *
2250      * @param undoManager The {@link android.content.UndoManager} to associate with
2251      * this TextView, or null to clear any existing association.
2252      * @param tag String tag identifying this particular TextView owner in the
2253      * UndoManager.  This is used to keep the correct association with the
2254      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2255      *
2256      * @hide
2257      */
setUndoManager(UndoManager undoManager, String tag)2258     public final void setUndoManager(UndoManager undoManager, String tag) {
2259         // TODO: Consider supporting a global undo manager. An implementation will need to:
2260         // * createEditorIfNeeded()
2261         // * Promote to BufferType.EDITABLE if needed.
2262         // * Update the UndoManager and UndoOwner.
2263         // Likewise it will need to be able to restore the default UndoManager.
2264         throw new UnsupportedOperationException("not implemented");
2265     }
2266 
2267     /**
2268      * Gets the current {@link KeyListener} for the TextView.
2269      * This will frequently be null for non-EditText TextViews.
2270      * @return the current key listener for this TextView.
2271      *
2272      * @attr ref android.R.styleable#TextView_numeric
2273      * @attr ref android.R.styleable#TextView_digits
2274      * @attr ref android.R.styleable#TextView_phoneNumber
2275      * @attr ref android.R.styleable#TextView_inputMethod
2276      * @attr ref android.R.styleable#TextView_capitalize
2277      * @attr ref android.R.styleable#TextView_autoText
2278      */
getKeyListener()2279     public final KeyListener getKeyListener() {
2280         return mEditor == null ? null : mEditor.mKeyListener;
2281     }
2282 
2283     /**
2284      * Sets the key listener to be used with this TextView.  This can be null
2285      * to disallow user input.  Note that this method has significant and
2286      * subtle interactions with soft keyboards and other input method:
2287      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2288      * for important details.  Calling this method will replace the current
2289      * content type of the text view with the content type returned by the
2290      * key listener.
2291      * <p>
2292      * Be warned that if you want a TextView with a key listener or movement
2293      * method not to be focusable, or if you want a TextView without a
2294      * key listener or movement method to be focusable, you must call
2295      * {@link #setFocusable} again after calling this to get the focusability
2296      * back the way you want it.
2297      *
2298      * @attr ref android.R.styleable#TextView_numeric
2299      * @attr ref android.R.styleable#TextView_digits
2300      * @attr ref android.R.styleable#TextView_phoneNumber
2301      * @attr ref android.R.styleable#TextView_inputMethod
2302      * @attr ref android.R.styleable#TextView_capitalize
2303      * @attr ref android.R.styleable#TextView_autoText
2304      */
setKeyListener(KeyListener input)2305     public void setKeyListener(KeyListener input) {
2306         mListenerChanged = true;
2307         setKeyListenerOnly(input);
2308         fixFocusableAndClickableSettings();
2309 
2310         if (input != null) {
2311             createEditorIfNeeded();
2312             setInputTypeFromEditor();
2313         } else {
2314             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2315         }
2316 
2317         InputMethodManager imm = InputMethodManager.peekInstance();
2318         if (imm != null) imm.restartInput(this);
2319     }
2320 
setInputTypeFromEditor()2321     private void setInputTypeFromEditor() {
2322         try {
2323             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2324         } catch (IncompatibleClassChangeError e) {
2325             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2326         }
2327         // Change inputType, without affecting transformation.
2328         // No need to applySingleLine since mSingleLine is unchanged.
2329         setInputTypeSingleLine(mSingleLine);
2330     }
2331 
setKeyListenerOnly(KeyListener input)2332     private void setKeyListenerOnly(KeyListener input) {
2333         if (mEditor == null && input == null) return; // null is the default value
2334 
2335         createEditorIfNeeded();
2336         if (mEditor.mKeyListener != input) {
2337             mEditor.mKeyListener = input;
2338             if (input != null && !(mText instanceof Editable)) {
2339                 setText(mText);
2340             }
2341 
2342             setFilters((Editable) mText, mFilters);
2343         }
2344     }
2345 
2346     /**
2347      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2348      * which provides positioning, scrolling, and text selection functionality.
2349      * This will frequently be null for non-EditText TextViews.
2350      * @return the movement method being used for this TextView.
2351      * @see android.text.method.MovementMethod
2352      */
getMovementMethod()2353     public final MovementMethod getMovementMethod() {
2354         return mMovement;
2355     }
2356 
2357     /**
2358      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2359      * for this TextView. This can be null to disallow using the arrow keys to move the
2360      * cursor or scroll the view.
2361      * <p>
2362      * Be warned that if you want a TextView with a key listener or movement
2363      * method not to be focusable, or if you want a TextView without a
2364      * key listener or movement method to be focusable, you must call
2365      * {@link #setFocusable} again after calling this to get the focusability
2366      * back the way you want it.
2367      */
setMovementMethod(MovementMethod movement)2368     public final void setMovementMethod(MovementMethod movement) {
2369         if (mMovement != movement) {
2370             mMovement = movement;
2371 
2372             if (movement != null && mSpannable == null) {
2373                 setText(mText);
2374             }
2375 
2376             fixFocusableAndClickableSettings();
2377 
2378             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2379             // mMovement
2380             if (mEditor != null) mEditor.prepareCursorControllers();
2381         }
2382     }
2383 
fixFocusableAndClickableSettings()2384     private void fixFocusableAndClickableSettings() {
2385         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2386             setFocusable(FOCUSABLE);
2387             setClickable(true);
2388             setLongClickable(true);
2389         } else {
2390             setFocusable(FOCUSABLE_AUTO);
2391             setClickable(false);
2392             setLongClickable(false);
2393         }
2394     }
2395 
2396     /**
2397      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2398      * This is frequently null, except for single-line and password fields.
2399      * @return the current transformation method for this TextView.
2400      *
2401      * @attr ref android.R.styleable#TextView_password
2402      * @attr ref android.R.styleable#TextView_singleLine
2403      */
getTransformationMethod()2404     public final TransformationMethod getTransformationMethod() {
2405         return mTransformation;
2406     }
2407 
2408     /**
2409      * Sets the transformation that is applied to the text that this
2410      * TextView is displaying.
2411      *
2412      * @attr ref android.R.styleable#TextView_password
2413      * @attr ref android.R.styleable#TextView_singleLine
2414      */
setTransformationMethod(TransformationMethod method)2415     public final void setTransformationMethod(TransformationMethod method) {
2416         if (method == mTransformation) {
2417             // Avoid the setText() below if the transformation is
2418             // the same.
2419             return;
2420         }
2421         if (mTransformation != null) {
2422             if (mSpannable != null) {
2423                 mSpannable.removeSpan(mTransformation);
2424             }
2425         }
2426 
2427         mTransformation = method;
2428 
2429         if (method instanceof TransformationMethod2) {
2430             TransformationMethod2 method2 = (TransformationMethod2) method;
2431             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2432             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2433         } else {
2434             mAllowTransformationLengthChange = false;
2435         }
2436 
2437         setText(mText);
2438 
2439         if (hasPasswordTransformationMethod()) {
2440             notifyViewAccessibilityStateChangedIfNeeded(
2441                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2442         }
2443 
2444         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2445         // getTextDirectionHeuristic, needs reset
2446         mTextDir = getTextDirectionHeuristic();
2447     }
2448 
2449     /**
2450      * Returns the top padding of the view, plus space for the top
2451      * Drawable if any.
2452      */
getCompoundPaddingTop()2453     public int getCompoundPaddingTop() {
2454         final Drawables dr = mDrawables;
2455         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2456             return mPaddingTop;
2457         } else {
2458             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2459         }
2460     }
2461 
2462     /**
2463      * Returns the bottom padding of the view, plus space for the bottom
2464      * Drawable if any.
2465      */
getCompoundPaddingBottom()2466     public int getCompoundPaddingBottom() {
2467         final Drawables dr = mDrawables;
2468         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2469             return mPaddingBottom;
2470         } else {
2471             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2472         }
2473     }
2474 
2475     /**
2476      * Returns the left padding of the view, plus space for the left
2477      * Drawable if any.
2478      */
getCompoundPaddingLeft()2479     public int getCompoundPaddingLeft() {
2480         final Drawables dr = mDrawables;
2481         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2482             return mPaddingLeft;
2483         } else {
2484             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2485         }
2486     }
2487 
2488     /**
2489      * Returns the right padding of the view, plus space for the right
2490      * Drawable if any.
2491      */
getCompoundPaddingRight()2492     public int getCompoundPaddingRight() {
2493         final Drawables dr = mDrawables;
2494         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2495             return mPaddingRight;
2496         } else {
2497             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2498         }
2499     }
2500 
2501     /**
2502      * Returns the start padding of the view, plus space for the start
2503      * Drawable if any.
2504      */
getCompoundPaddingStart()2505     public int getCompoundPaddingStart() {
2506         resolveDrawables();
2507         switch(getLayoutDirection()) {
2508             default:
2509             case LAYOUT_DIRECTION_LTR:
2510                 return getCompoundPaddingLeft();
2511             case LAYOUT_DIRECTION_RTL:
2512                 return getCompoundPaddingRight();
2513         }
2514     }
2515 
2516     /**
2517      * Returns the end padding of the view, plus space for the end
2518      * Drawable if any.
2519      */
getCompoundPaddingEnd()2520     public int getCompoundPaddingEnd() {
2521         resolveDrawables();
2522         switch(getLayoutDirection()) {
2523             default:
2524             case LAYOUT_DIRECTION_LTR:
2525                 return getCompoundPaddingRight();
2526             case LAYOUT_DIRECTION_RTL:
2527                 return getCompoundPaddingLeft();
2528         }
2529     }
2530 
2531     /**
2532      * Returns the extended top padding of the view, including both the
2533      * top Drawable if any and any extra space to keep more than maxLines
2534      * of text from showing.  It is only valid to call this after measuring.
2535      */
getExtendedPaddingTop()2536     public int getExtendedPaddingTop() {
2537         if (mMaxMode != LINES) {
2538             return getCompoundPaddingTop();
2539         }
2540 
2541         if (mLayout == null) {
2542             assumeLayout();
2543         }
2544 
2545         if (mLayout.getLineCount() <= mMaximum) {
2546             return getCompoundPaddingTop();
2547         }
2548 
2549         int top = getCompoundPaddingTop();
2550         int bottom = getCompoundPaddingBottom();
2551         int viewht = getHeight() - top - bottom;
2552         int layoutht = mLayout.getLineTop(mMaximum);
2553 
2554         if (layoutht >= viewht) {
2555             return top;
2556         }
2557 
2558         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2559         if (gravity == Gravity.TOP) {
2560             return top;
2561         } else if (gravity == Gravity.BOTTOM) {
2562             return top + viewht - layoutht;
2563         } else { // (gravity == Gravity.CENTER_VERTICAL)
2564             return top + (viewht - layoutht) / 2;
2565         }
2566     }
2567 
2568     /**
2569      * Returns the extended bottom padding of the view, including both the
2570      * bottom Drawable if any and any extra space to keep more than maxLines
2571      * of text from showing.  It is only valid to call this after measuring.
2572      */
getExtendedPaddingBottom()2573     public int getExtendedPaddingBottom() {
2574         if (mMaxMode != LINES) {
2575             return getCompoundPaddingBottom();
2576         }
2577 
2578         if (mLayout == null) {
2579             assumeLayout();
2580         }
2581 
2582         if (mLayout.getLineCount() <= mMaximum) {
2583             return getCompoundPaddingBottom();
2584         }
2585 
2586         int top = getCompoundPaddingTop();
2587         int bottom = getCompoundPaddingBottom();
2588         int viewht = getHeight() - top - bottom;
2589         int layoutht = mLayout.getLineTop(mMaximum);
2590 
2591         if (layoutht >= viewht) {
2592             return bottom;
2593         }
2594 
2595         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2596         if (gravity == Gravity.TOP) {
2597             return bottom + viewht - layoutht;
2598         } else if (gravity == Gravity.BOTTOM) {
2599             return bottom;
2600         } else { // (gravity == Gravity.CENTER_VERTICAL)
2601             return bottom + (viewht - layoutht) / 2;
2602         }
2603     }
2604 
2605     /**
2606      * Returns the total left padding of the view, including the left
2607      * Drawable if any.
2608      */
getTotalPaddingLeft()2609     public int getTotalPaddingLeft() {
2610         return getCompoundPaddingLeft();
2611     }
2612 
2613     /**
2614      * Returns the total right padding of the view, including the right
2615      * Drawable if any.
2616      */
getTotalPaddingRight()2617     public int getTotalPaddingRight() {
2618         return getCompoundPaddingRight();
2619     }
2620 
2621     /**
2622      * Returns the total start padding of the view, including the start
2623      * Drawable if any.
2624      */
getTotalPaddingStart()2625     public int getTotalPaddingStart() {
2626         return getCompoundPaddingStart();
2627     }
2628 
2629     /**
2630      * Returns the total end padding of the view, including the end
2631      * Drawable if any.
2632      */
getTotalPaddingEnd()2633     public int getTotalPaddingEnd() {
2634         return getCompoundPaddingEnd();
2635     }
2636 
2637     /**
2638      * Returns the total top padding of the view, including the top
2639      * Drawable if any, the extra space to keep more than maxLines
2640      * from showing, and the vertical offset for gravity, if any.
2641      */
getTotalPaddingTop()2642     public int getTotalPaddingTop() {
2643         return getExtendedPaddingTop() + getVerticalOffset(true);
2644     }
2645 
2646     /**
2647      * Returns the total bottom padding of the view, including the bottom
2648      * Drawable if any, the extra space to keep more than maxLines
2649      * from showing, and the vertical offset for gravity, if any.
2650      */
getTotalPaddingBottom()2651     public int getTotalPaddingBottom() {
2652         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2653     }
2654 
2655     /**
2656      * Sets the Drawables (if any) to appear to the left of, above, to the
2657      * right of, and below the text. Use {@code null} if you do not want a
2658      * Drawable there. The Drawables must already have had
2659      * {@link Drawable#setBounds} called.
2660      * <p>
2661      * Calling this method will overwrite any Drawables previously set using
2662      * {@link #setCompoundDrawablesRelative} or related methods.
2663      *
2664      * @attr ref android.R.styleable#TextView_drawableLeft
2665      * @attr ref android.R.styleable#TextView_drawableTop
2666      * @attr ref android.R.styleable#TextView_drawableRight
2667      * @attr ref android.R.styleable#TextView_drawableBottom
2668      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2669     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2670             @Nullable Drawable right, @Nullable Drawable bottom) {
2671         Drawables dr = mDrawables;
2672 
2673         // We're switching to absolute, discard relative.
2674         if (dr != null) {
2675             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2676             dr.mDrawableStart = null;
2677             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2678             dr.mDrawableEnd = null;
2679             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2680             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2681         }
2682 
2683         final boolean drawables = left != null || top != null || right != null || bottom != null;
2684         if (!drawables) {
2685             // Clearing drawables...  can we free the data structure?
2686             if (dr != null) {
2687                 if (!dr.hasMetadata()) {
2688                     mDrawables = null;
2689                 } else {
2690                     // We need to retain the last set padding, so just clear
2691                     // out all of the fields in the existing structure.
2692                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2693                         if (dr.mShowing[i] != null) {
2694                             dr.mShowing[i].setCallback(null);
2695                         }
2696                         dr.mShowing[i] = null;
2697                     }
2698                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2699                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2700                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2701                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2702                 }
2703             }
2704         } else {
2705             if (dr == null) {
2706                 mDrawables = dr = new Drawables(getContext());
2707             }
2708 
2709             mDrawables.mOverride = false;
2710 
2711             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2712                 dr.mShowing[Drawables.LEFT].setCallback(null);
2713             }
2714             dr.mShowing[Drawables.LEFT] = left;
2715 
2716             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2717                 dr.mShowing[Drawables.TOP].setCallback(null);
2718             }
2719             dr.mShowing[Drawables.TOP] = top;
2720 
2721             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2722                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2723             }
2724             dr.mShowing[Drawables.RIGHT] = right;
2725 
2726             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2727                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2728             }
2729             dr.mShowing[Drawables.BOTTOM] = bottom;
2730 
2731             final Rect compoundRect = dr.mCompoundRect;
2732             int[] state;
2733 
2734             state = getDrawableState();
2735 
2736             if (left != null) {
2737                 left.setState(state);
2738                 left.copyBounds(compoundRect);
2739                 left.setCallback(this);
2740                 dr.mDrawableSizeLeft = compoundRect.width();
2741                 dr.mDrawableHeightLeft = compoundRect.height();
2742             } else {
2743                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2744             }
2745 
2746             if (right != null) {
2747                 right.setState(state);
2748                 right.copyBounds(compoundRect);
2749                 right.setCallback(this);
2750                 dr.mDrawableSizeRight = compoundRect.width();
2751                 dr.mDrawableHeightRight = compoundRect.height();
2752             } else {
2753                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2754             }
2755 
2756             if (top != null) {
2757                 top.setState(state);
2758                 top.copyBounds(compoundRect);
2759                 top.setCallback(this);
2760                 dr.mDrawableSizeTop = compoundRect.height();
2761                 dr.mDrawableWidthTop = compoundRect.width();
2762             } else {
2763                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2764             }
2765 
2766             if (bottom != null) {
2767                 bottom.setState(state);
2768                 bottom.copyBounds(compoundRect);
2769                 bottom.setCallback(this);
2770                 dr.mDrawableSizeBottom = compoundRect.height();
2771                 dr.mDrawableWidthBottom = compoundRect.width();
2772             } else {
2773                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2774             }
2775         }
2776 
2777         // Save initial left/right drawables
2778         if (dr != null) {
2779             dr.mDrawableLeftInitial = left;
2780             dr.mDrawableRightInitial = right;
2781         }
2782 
2783         resetResolvedDrawables();
2784         resolveDrawables();
2785         applyCompoundDrawableTint();
2786         invalidate();
2787         requestLayout();
2788     }
2789 
2790     /**
2791      * Sets the Drawables (if any) to appear to the left of, above, to the
2792      * right of, and below the text. Use 0 if you do not want a Drawable there.
2793      * The Drawables' bounds will be set to their intrinsic bounds.
2794      * <p>
2795      * Calling this method will overwrite any Drawables previously set using
2796      * {@link #setCompoundDrawablesRelative} or related methods.
2797      *
2798      * @param left Resource identifier of the left Drawable.
2799      * @param top Resource identifier of the top Drawable.
2800      * @param right Resource identifier of the right Drawable.
2801      * @param bottom Resource identifier of the bottom Drawable.
2802      *
2803      * @attr ref android.R.styleable#TextView_drawableLeft
2804      * @attr ref android.R.styleable#TextView_drawableTop
2805      * @attr ref android.R.styleable#TextView_drawableRight
2806      * @attr ref android.R.styleable#TextView_drawableBottom
2807      */
2808     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)2809     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2810             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2811         final Context context = getContext();
2812         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2813                 top != 0 ? context.getDrawable(top) : null,
2814                 right != 0 ? context.getDrawable(right) : null,
2815                 bottom != 0 ? context.getDrawable(bottom) : null);
2816     }
2817 
2818     /**
2819      * Sets the Drawables (if any) to appear to the left of, above, to the
2820      * right of, and below the text. Use {@code null} if you do not want a
2821      * Drawable there. The Drawables' bounds will be set to their intrinsic
2822      * bounds.
2823      * <p>
2824      * Calling this method will overwrite any Drawables previously set using
2825      * {@link #setCompoundDrawablesRelative} or related methods.
2826      *
2827      * @attr ref android.R.styleable#TextView_drawableLeft
2828      * @attr ref android.R.styleable#TextView_drawableTop
2829      * @attr ref android.R.styleable#TextView_drawableRight
2830      * @attr ref android.R.styleable#TextView_drawableBottom
2831      */
2832     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2833     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2834             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2835 
2836         if (left != null) {
2837             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2838         }
2839         if (right != null) {
2840             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2841         }
2842         if (top != null) {
2843             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2844         }
2845         if (bottom != null) {
2846             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2847         }
2848         setCompoundDrawables(left, top, right, bottom);
2849     }
2850 
2851     /**
2852      * Sets the Drawables (if any) to appear to the start of, above, to the end
2853      * of, and below the text. Use {@code null} if you do not want a Drawable
2854      * there. The Drawables must already have had {@link Drawable#setBounds}
2855      * called.
2856      * <p>
2857      * Calling this method will overwrite any Drawables previously set using
2858      * {@link #setCompoundDrawables} or related methods.
2859      *
2860      * @attr ref android.R.styleable#TextView_drawableStart
2861      * @attr ref android.R.styleable#TextView_drawableTop
2862      * @attr ref android.R.styleable#TextView_drawableEnd
2863      * @attr ref android.R.styleable#TextView_drawableBottom
2864      */
2865     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)2866     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2867             @Nullable Drawable end, @Nullable Drawable bottom) {
2868         Drawables dr = mDrawables;
2869 
2870         // We're switching to relative, discard absolute.
2871         if (dr != null) {
2872             if (dr.mShowing[Drawables.LEFT] != null) {
2873                 dr.mShowing[Drawables.LEFT].setCallback(null);
2874             }
2875             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2876             if (dr.mShowing[Drawables.RIGHT] != null) {
2877                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2878             }
2879             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
2880             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2881             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2882         }
2883 
2884         final boolean drawables = start != null || top != null
2885                 || end != null || bottom != null;
2886 
2887         if (!drawables) {
2888             // Clearing drawables...  can we free the data structure?
2889             if (dr != null) {
2890                 if (!dr.hasMetadata()) {
2891                     mDrawables = null;
2892                 } else {
2893                     // We need to retain the last set padding, so just clear
2894                     // out all of the fields in the existing structure.
2895                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2896                     dr.mDrawableStart = null;
2897                     if (dr.mShowing[Drawables.TOP] != null) {
2898                         dr.mShowing[Drawables.TOP].setCallback(null);
2899                     }
2900                     dr.mShowing[Drawables.TOP] = null;
2901                     if (dr.mDrawableEnd != null) {
2902                         dr.mDrawableEnd.setCallback(null);
2903                     }
2904                     dr.mDrawableEnd = null;
2905                     if (dr.mShowing[Drawables.BOTTOM] != null) {
2906                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
2907                     }
2908                     dr.mShowing[Drawables.BOTTOM] = null;
2909                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2910                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2911                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2912                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2913                 }
2914             }
2915         } else {
2916             if (dr == null) {
2917                 mDrawables = dr = new Drawables(getContext());
2918             }
2919 
2920             mDrawables.mOverride = true;
2921 
2922             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2923                 dr.mDrawableStart.setCallback(null);
2924             }
2925             dr.mDrawableStart = start;
2926 
2927             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2928                 dr.mShowing[Drawables.TOP].setCallback(null);
2929             }
2930             dr.mShowing[Drawables.TOP] = top;
2931 
2932             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2933                 dr.mDrawableEnd.setCallback(null);
2934             }
2935             dr.mDrawableEnd = end;
2936 
2937             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2938                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2939             }
2940             dr.mShowing[Drawables.BOTTOM] = bottom;
2941 
2942             final Rect compoundRect = dr.mCompoundRect;
2943             int[] state;
2944 
2945             state = getDrawableState();
2946 
2947             if (start != null) {
2948                 start.setState(state);
2949                 start.copyBounds(compoundRect);
2950                 start.setCallback(this);
2951                 dr.mDrawableSizeStart = compoundRect.width();
2952                 dr.mDrawableHeightStart = compoundRect.height();
2953             } else {
2954                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2955             }
2956 
2957             if (end != null) {
2958                 end.setState(state);
2959                 end.copyBounds(compoundRect);
2960                 end.setCallback(this);
2961                 dr.mDrawableSizeEnd = compoundRect.width();
2962                 dr.mDrawableHeightEnd = compoundRect.height();
2963             } else {
2964                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2965             }
2966 
2967             if (top != null) {
2968                 top.setState(state);
2969                 top.copyBounds(compoundRect);
2970                 top.setCallback(this);
2971                 dr.mDrawableSizeTop = compoundRect.height();
2972                 dr.mDrawableWidthTop = compoundRect.width();
2973             } else {
2974                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2975             }
2976 
2977             if (bottom != null) {
2978                 bottom.setState(state);
2979                 bottom.copyBounds(compoundRect);
2980                 bottom.setCallback(this);
2981                 dr.mDrawableSizeBottom = compoundRect.height();
2982                 dr.mDrawableWidthBottom = compoundRect.width();
2983             } else {
2984                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2985             }
2986         }
2987 
2988         resetResolvedDrawables();
2989         resolveDrawables();
2990         invalidate();
2991         requestLayout();
2992     }
2993 
2994     /**
2995      * Sets the Drawables (if any) to appear to the start of, above, to the end
2996      * of, and below the text. Use 0 if you do not want a Drawable there. The
2997      * Drawables' bounds will be set to their intrinsic bounds.
2998      * <p>
2999      * Calling this method will overwrite any Drawables previously set using
3000      * {@link #setCompoundDrawables} or related methods.
3001      *
3002      * @param start Resource identifier of the start Drawable.
3003      * @param top Resource identifier of the top Drawable.
3004      * @param end Resource identifier of the end Drawable.
3005      * @param bottom Resource identifier of the bottom Drawable.
3006      *
3007      * @attr ref android.R.styleable#TextView_drawableStart
3008      * @attr ref android.R.styleable#TextView_drawableTop
3009      * @attr ref android.R.styleable#TextView_drawableEnd
3010      * @attr ref android.R.styleable#TextView_drawableBottom
3011      */
3012     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3013     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3014             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3015         final Context context = getContext();
3016         setCompoundDrawablesRelativeWithIntrinsicBounds(
3017                 start != 0 ? context.getDrawable(start) : null,
3018                 top != 0 ? context.getDrawable(top) : null,
3019                 end != 0 ? context.getDrawable(end) : null,
3020                 bottom != 0 ? context.getDrawable(bottom) : null);
3021     }
3022 
3023     /**
3024      * Sets the Drawables (if any) to appear to the start of, above, to the end
3025      * of, and below the text. Use {@code null} if you do not want a Drawable
3026      * there. The Drawables' bounds will be set to their intrinsic bounds.
3027      * <p>
3028      * Calling this method will overwrite any Drawables previously set using
3029      * {@link #setCompoundDrawables} or related methods.
3030      *
3031      * @attr ref android.R.styleable#TextView_drawableStart
3032      * @attr ref android.R.styleable#TextView_drawableTop
3033      * @attr ref android.R.styleable#TextView_drawableEnd
3034      * @attr ref android.R.styleable#TextView_drawableBottom
3035      */
3036     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3037     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3038             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3039 
3040         if (start != null) {
3041             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3042         }
3043         if (end != null) {
3044             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3045         }
3046         if (top != null) {
3047             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3048         }
3049         if (bottom != null) {
3050             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3051         }
3052         setCompoundDrawablesRelative(start, top, end, bottom);
3053     }
3054 
3055     /**
3056      * Returns drawables for the left, top, right, and bottom borders.
3057      *
3058      * @attr ref android.R.styleable#TextView_drawableLeft
3059      * @attr ref android.R.styleable#TextView_drawableTop
3060      * @attr ref android.R.styleable#TextView_drawableRight
3061      * @attr ref android.R.styleable#TextView_drawableBottom
3062      */
3063     @NonNull
getCompoundDrawables()3064     public Drawable[] getCompoundDrawables() {
3065         final Drawables dr = mDrawables;
3066         if (dr != null) {
3067             return dr.mShowing.clone();
3068         } else {
3069             return new Drawable[] { null, null, null, null };
3070         }
3071     }
3072 
3073     /**
3074      * Returns drawables for the start, top, end, and bottom borders.
3075      *
3076      * @attr ref android.R.styleable#TextView_drawableStart
3077      * @attr ref android.R.styleable#TextView_drawableTop
3078      * @attr ref android.R.styleable#TextView_drawableEnd
3079      * @attr ref android.R.styleable#TextView_drawableBottom
3080      */
3081     @NonNull
getCompoundDrawablesRelative()3082     public Drawable[] getCompoundDrawablesRelative() {
3083         final Drawables dr = mDrawables;
3084         if (dr != null) {
3085             return new Drawable[] {
3086                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3087                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3088             };
3089         } else {
3090             return new Drawable[] { null, null, null, null };
3091         }
3092     }
3093 
3094     /**
3095      * Sets the size of the padding between the compound drawables and
3096      * the text.
3097      *
3098      * @attr ref android.R.styleable#TextView_drawablePadding
3099      */
3100     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3101     public void setCompoundDrawablePadding(int pad) {
3102         Drawables dr = mDrawables;
3103         if (pad == 0) {
3104             if (dr != null) {
3105                 dr.mDrawablePadding = pad;
3106             }
3107         } else {
3108             if (dr == null) {
3109                 mDrawables = dr = new Drawables(getContext());
3110             }
3111             dr.mDrawablePadding = pad;
3112         }
3113 
3114         invalidate();
3115         requestLayout();
3116     }
3117 
3118     /**
3119      * Returns the padding between the compound drawables and the text.
3120      *
3121      * @attr ref android.R.styleable#TextView_drawablePadding
3122      */
getCompoundDrawablePadding()3123     public int getCompoundDrawablePadding() {
3124         final Drawables dr = mDrawables;
3125         return dr != null ? dr.mDrawablePadding : 0;
3126     }
3127 
3128     /**
3129      * Applies a tint to the compound drawables. Does not modify the
3130      * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
3131      * <p>
3132      * Subsequent calls to
3133      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3134      * and related methods will automatically mutate the drawables and apply
3135      * the specified tint and tint mode using
3136      * {@link Drawable#setTintList(ColorStateList)}.
3137      *
3138      * @param tint the tint to apply, may be {@code null} to clear tint
3139      *
3140      * @attr ref android.R.styleable#TextView_drawableTint
3141      * @see #getCompoundDrawableTintList()
3142      * @see Drawable#setTintList(ColorStateList)
3143      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3144     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3145         if (mDrawables == null) {
3146             mDrawables = new Drawables(getContext());
3147         }
3148         mDrawables.mTintList = tint;
3149         mDrawables.mHasTint = true;
3150 
3151         applyCompoundDrawableTint();
3152     }
3153 
3154     /**
3155      * @return the tint applied to the compound drawables
3156      * @attr ref android.R.styleable#TextView_drawableTint
3157      * @see #setCompoundDrawableTintList(ColorStateList)
3158      */
getCompoundDrawableTintList()3159     public ColorStateList getCompoundDrawableTintList() {
3160         return mDrawables != null ? mDrawables.mTintList : null;
3161     }
3162 
3163     /**
3164      * Specifies the blending mode used to apply the tint specified by
3165      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3166      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3167      *
3168      * @param tintMode the blending mode used to apply the tint, may be
3169      *                 {@code null} to clear tint
3170      * @attr ref android.R.styleable#TextView_drawableTintMode
3171      * @see #setCompoundDrawableTintList(ColorStateList)
3172      * @see Drawable#setTintMode(PorterDuff.Mode)
3173      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3174     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3175         if (mDrawables == null) {
3176             mDrawables = new Drawables(getContext());
3177         }
3178         mDrawables.mTintMode = tintMode;
3179         mDrawables.mHasTintMode = true;
3180 
3181         applyCompoundDrawableTint();
3182     }
3183 
3184     /**
3185      * Returns the blending mode used to apply the tint to the compound
3186      * drawables, if specified.
3187      *
3188      * @return the blending mode used to apply the tint to the compound
3189      *         drawables
3190      * @attr ref android.R.styleable#TextView_drawableTintMode
3191      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3192      */
getCompoundDrawableTintMode()3193     public PorterDuff.Mode getCompoundDrawableTintMode() {
3194         return mDrawables != null ? mDrawables.mTintMode : null;
3195     }
3196 
applyCompoundDrawableTint()3197     private void applyCompoundDrawableTint() {
3198         if (mDrawables == null) {
3199             return;
3200         }
3201 
3202         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3203             final ColorStateList tintList = mDrawables.mTintList;
3204             final PorterDuff.Mode tintMode = mDrawables.mTintMode;
3205             final boolean hasTint = mDrawables.mHasTint;
3206             final boolean hasTintMode = mDrawables.mHasTintMode;
3207             final int[] state = getDrawableState();
3208 
3209             for (Drawable dr : mDrawables.mShowing) {
3210                 if (dr == null) {
3211                     continue;
3212                 }
3213 
3214                 if (dr == mDrawables.mDrawableError) {
3215                     // From a developer's perspective, the error drawable isn't
3216                     // a compound drawable. Don't apply the generic compound
3217                     // drawable tint to it.
3218                     continue;
3219                 }
3220 
3221                 dr.mutate();
3222 
3223                 if (hasTint) {
3224                     dr.setTintList(tintList);
3225                 }
3226 
3227                 if (hasTintMode) {
3228                     dr.setTintMode(tintMode);
3229                 }
3230 
3231                 // The drawable (or one of its children) may not have been
3232                 // stateful before applying the tint, so let's try again.
3233                 if (dr.isStateful()) {
3234                     dr.setState(state);
3235                 }
3236             }
3237         }
3238     }
3239 
3240     /**
3241      * @inheritDoc
3242      *
3243      * @see #setFirstBaselineToTopHeight(int)
3244      * @see #setLastBaselineToBottomHeight(int)
3245      */
3246     @Override
setPadding(int left, int top, int right, int bottom)3247     public void setPadding(int left, int top, int right, int bottom) {
3248         if (left != mPaddingLeft
3249                 || right != mPaddingRight
3250                 || top != mPaddingTop
3251                 ||  bottom != mPaddingBottom) {
3252             nullLayouts();
3253         }
3254 
3255         // the super call will requestLayout()
3256         super.setPadding(left, top, right, bottom);
3257         invalidate();
3258     }
3259 
3260     /**
3261      * @inheritDoc
3262      *
3263      * @see #setFirstBaselineToTopHeight(int)
3264      * @see #setLastBaselineToBottomHeight(int)
3265      */
3266     @Override
setPaddingRelative(int start, int top, int end, int bottom)3267     public void setPaddingRelative(int start, int top, int end, int bottom) {
3268         if (start != getPaddingStart()
3269                 || end != getPaddingEnd()
3270                 || top != mPaddingTop
3271                 || bottom != mPaddingBottom) {
3272             nullLayouts();
3273         }
3274 
3275         // the super call will requestLayout()
3276         super.setPaddingRelative(start, top, end, bottom);
3277         invalidate();
3278     }
3279 
3280     /**
3281      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3282      * equal to the distance between the firt text baseline and the top of this TextView.
3283      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3284      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3285      *
3286      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3287      *      in pixels
3288      *
3289      * @see #getFirstBaselineToTopHeight()
3290      * @see #setPadding(int, int, int, int)
3291      * @see #setPaddingRelative(int, int, int, int)
3292      *
3293      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3294      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3295     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3296         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3297 
3298         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3299         final int fontMetricsTop;
3300         if (getIncludeFontPadding()) {
3301             fontMetricsTop = fontMetrics.top;
3302         } else {
3303             fontMetricsTop = fontMetrics.ascent;
3304         }
3305 
3306         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3307         // in settings). At the moment, we don't.
3308 
3309         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3310             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3311             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3312         }
3313     }
3314 
3315     /**
3316      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3317      * equal to the distance between the last text baseline and the bottom of this TextView.
3318      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3319      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3320      *
3321      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3322      *      in pixels
3323      *
3324      * @see #getLastBaselineToBottomHeight()
3325      * @see #setPadding(int, int, int, int)
3326      * @see #setPaddingRelative(int, int, int, int)
3327      *
3328      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3329      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3330     public void setLastBaselineToBottomHeight(
3331             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3332         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3333 
3334         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3335         final int fontMetricsBottom;
3336         if (getIncludeFontPadding()) {
3337             fontMetricsBottom = fontMetrics.bottom;
3338         } else {
3339             fontMetricsBottom = fontMetrics.descent;
3340         }
3341 
3342         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3343         // in settings). At the moment, we don't.
3344 
3345         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3346             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3347             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3348         }
3349     }
3350 
3351     /**
3352      * Returns the distance between the first text baseline and the top of this TextView.
3353      *
3354      * @see #setFirstBaselineToTopHeight(int)
3355      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3356      */
getFirstBaselineToTopHeight()3357     public int getFirstBaselineToTopHeight() {
3358         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3359     }
3360 
3361     /**
3362      * Returns the distance between the last text baseline and the bottom of this TextView.
3363      *
3364      * @see #setLastBaselineToBottomHeight(int)
3365      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3366      */
getLastBaselineToBottomHeight()3367     public int getLastBaselineToBottomHeight() {
3368         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3369     }
3370 
3371     /**
3372      * Gets the autolink mask of the text.  See {@link
3373      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3374      * possible values.
3375      *
3376      * @attr ref android.R.styleable#TextView_autoLink
3377      */
getAutoLinkMask()3378     public final int getAutoLinkMask() {
3379         return mAutoLinkMask;
3380     }
3381 
3382     /**
3383      * Sets the text appearance from the specified style resource.
3384      * <p>
3385      * Use a framework-defined {@code TextAppearance} style like
3386      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3387      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3388      * set of attributes that can be used in a custom style.
3389      *
3390      * @param resId the resource identifier of the style to apply
3391      * @attr ref android.R.styleable#TextView_textAppearance
3392      */
3393     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)3394     public void setTextAppearance(@StyleRes int resId) {
3395         setTextAppearance(mContext, resId);
3396     }
3397 
3398     /**
3399      * Sets the text color, size, style, hint color, and highlight color
3400      * from the specified TextAppearance resource.
3401      *
3402      * @deprecated Use {@link #setTextAppearance(int)} instead.
3403      */
3404     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)3405     public void setTextAppearance(Context context, @StyleRes int resId) {
3406         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3407         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
3408         readTextAppearance(context, ta, attributes, false /* styleArray */);
3409         ta.recycle();
3410         applyTextAppearance(attributes);
3411     }
3412 
3413     /**
3414      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
3415      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
3416      */
3417     private static class TextAppearanceAttributes {
3418         int mTextColorHighlight = 0;
3419         ColorStateList mTextColor = null;
3420         ColorStateList mTextColorHint = null;
3421         ColorStateList mTextColorLink = null;
3422         int mTextSize = 0;
3423         String mFontFamily = null;
3424         Typeface mFontTypeface = null;
3425         boolean mFontFamilyExplicit = false;
3426         int mTypefaceIndex = -1;
3427         int mStyleIndex = -1;
3428         int mFontWeight = -1;
3429         boolean mAllCaps = false;
3430         int mShadowColor = 0;
3431         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
3432         boolean mHasElegant = false;
3433         boolean mElegant = false;
3434         boolean mHasFallbackLineSpacing = false;
3435         boolean mFallbackLineSpacing = false;
3436         boolean mHasLetterSpacing = false;
3437         float mLetterSpacing = 0;
3438         String mFontFeatureSettings = null;
3439 
3440         @Override
toString()3441         public String toString() {
3442             return "TextAppearanceAttributes {\n"
3443                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
3444                     + "    mTextColor:" + mTextColor + "\n"
3445                     + "    mTextColorHint:" + mTextColorHint + "\n"
3446                     + "    mTextColorLink:" + mTextColorLink + "\n"
3447                     + "    mTextSize:" + mTextSize + "\n"
3448                     + "    mFontFamily:" + mFontFamily + "\n"
3449                     + "    mFontTypeface:" + mFontTypeface + "\n"
3450                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
3451                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
3452                     + "    mStyleIndex:" + mStyleIndex + "\n"
3453                     + "    mFontWeight:" + mFontWeight + "\n"
3454                     + "    mAllCaps:" + mAllCaps + "\n"
3455                     + "    mShadowColor:" + mShadowColor + "\n"
3456                     + "    mShadowDx:" + mShadowDx + "\n"
3457                     + "    mShadowDy:" + mShadowDy + "\n"
3458                     + "    mShadowRadius:" + mShadowRadius + "\n"
3459                     + "    mHasElegant:" + mHasElegant + "\n"
3460                     + "    mElegant:" + mElegant + "\n"
3461                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
3462                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
3463                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
3464                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
3465                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
3466                     + "}";
3467         }
3468     }
3469 
3470     // Maps styleable attributes that exist both in TextView style and TextAppearance.
3471     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
3472     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3473         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
3474                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3475         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
3476                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3477         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
3478                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3479         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
3480                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3481         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
3482                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3483         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
3484                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3485         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
3486                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3487         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
3488                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)3489         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
3490                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)3491         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
3492                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)3493         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
3494                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)3495         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
3496                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)3497         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
3498                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)3499         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
3500                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)3501         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
3502                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)3503         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
3504                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)3505         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
3506                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)3507         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
3508                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
3509     }
3510 
3511     /**
3512      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
3513      * set. If the TypedArray contains a value that was already set in the given attributes, that
3514      * will be overriden.
3515      *
3516      * @param context The Context to be used
3517      * @param appearance The TypedArray to read properties from
3518      * @param attributes the TextAppearanceAttributes to fill in
3519      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
3520      *                   what attribute indexes will be used to read the properties.
3521      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)3522     private void readTextAppearance(Context context, TypedArray appearance,
3523             TextAppearanceAttributes attributes, boolean styleArray) {
3524         final int n = appearance.getIndexCount();
3525         for (int i = 0; i < n; i++) {
3526             final int attr = appearance.getIndex(i);
3527             int index = attr;
3528             // Translate style array index ids to TextAppearance ids.
3529             if (styleArray) {
3530                 index = sAppearanceValues.get(attr, -1);
3531                 if (index == -1) {
3532                     // This value is not part of a Text Appearance and should be ignored.
3533                     continue;
3534                 }
3535             }
3536             switch (index) {
3537                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
3538                     attributes.mTextColorHighlight =
3539                             appearance.getColor(attr, attributes.mTextColorHighlight);
3540                     break;
3541                 case com.android.internal.R.styleable.TextAppearance_textColor:
3542                     attributes.mTextColor = appearance.getColorStateList(attr);
3543                     break;
3544                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
3545                     attributes.mTextColorHint = appearance.getColorStateList(attr);
3546                     break;
3547                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
3548                     attributes.mTextColorLink = appearance.getColorStateList(attr);
3549                     break;
3550                 case com.android.internal.R.styleable.TextAppearance_textSize:
3551                     attributes.mTextSize =
3552                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
3553                     break;
3554                 case com.android.internal.R.styleable.TextAppearance_typeface:
3555                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
3556                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
3557                         attributes.mFontFamily = null;
3558                     }
3559                     break;
3560                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
3561                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
3562                         try {
3563                             attributes.mFontTypeface = appearance.getFont(attr);
3564                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
3565                             // Expected if it is not a font resource.
3566                         }
3567                     }
3568                     if (attributes.mFontTypeface == null) {
3569                         attributes.mFontFamily = appearance.getString(attr);
3570                     }
3571                     attributes.mFontFamilyExplicit = true;
3572                     break;
3573                 case com.android.internal.R.styleable.TextAppearance_textStyle:
3574                     attributes.mStyleIndex = appearance.getInt(attr, attributes.mStyleIndex);
3575                     break;
3576                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
3577                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
3578                     break;
3579                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
3580                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
3581                     break;
3582                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
3583                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
3584                     break;
3585                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
3586                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
3587                     break;
3588                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
3589                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
3590                     break;
3591                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
3592                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
3593                     break;
3594                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
3595                     attributes.mHasElegant = true;
3596                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
3597                     break;
3598                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
3599                     attributes.mHasFallbackLineSpacing = true;
3600                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
3601                             attributes.mFallbackLineSpacing);
3602                     break;
3603                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
3604                     attributes.mHasLetterSpacing = true;
3605                     attributes.mLetterSpacing =
3606                             appearance.getFloat(attr, attributes.mLetterSpacing);
3607                     break;
3608                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
3609                     attributes.mFontFeatureSettings = appearance.getString(attr);
3610                     break;
3611                 default:
3612             }
3613         }
3614     }
3615 
applyTextAppearance(TextAppearanceAttributes attributes)3616     private void applyTextAppearance(TextAppearanceAttributes attributes) {
3617         if (attributes.mTextColor != null) {
3618             setTextColor(attributes.mTextColor);
3619         }
3620 
3621         if (attributes.mTextColorHint != null) {
3622             setHintTextColor(attributes.mTextColorHint);
3623         }
3624 
3625         if (attributes.mTextColorLink != null) {
3626             setLinkTextColor(attributes.mTextColorLink);
3627         }
3628 
3629         if (attributes.mTextColorHighlight != 0) {
3630             setHighlightColor(attributes.mTextColorHighlight);
3631         }
3632 
3633         if (attributes.mTextSize != 0) {
3634             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
3635         }
3636 
3637         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
3638             attributes.mFontFamily = null;
3639         }
3640         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
3641                 attributes.mTypefaceIndex, attributes.mStyleIndex, attributes.mFontWeight);
3642 
3643         if (attributes.mShadowColor != 0) {
3644             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
3645                     attributes.mShadowColor);
3646         }
3647 
3648         if (attributes.mAllCaps) {
3649             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
3650         }
3651 
3652         if (attributes.mHasElegant) {
3653             setElegantTextHeight(attributes.mElegant);
3654         }
3655 
3656         if (attributes.mHasFallbackLineSpacing) {
3657             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
3658         }
3659 
3660         if (attributes.mHasLetterSpacing) {
3661             setLetterSpacing(attributes.mLetterSpacing);
3662         }
3663 
3664         if (attributes.mFontFeatureSettings != null) {
3665             setFontFeatureSettings(attributes.mFontFeatureSettings);
3666         }
3667     }
3668 
3669     /**
3670      * Get the default primary {@link Locale} of the text in this TextView. This will always be
3671      * the first member of {@link #getTextLocales()}.
3672      * @return the default primary {@link Locale} of the text in this TextView.
3673      */
3674     @NonNull
getTextLocale()3675     public Locale getTextLocale() {
3676         return mTextPaint.getTextLocale();
3677     }
3678 
3679     /**
3680      * Get the default {@link LocaleList} of the text in this TextView.
3681      * @return the default {@link LocaleList} of the text in this TextView.
3682      */
3683     @NonNull @Size(min = 1)
getTextLocales()3684     public LocaleList getTextLocales() {
3685         return mTextPaint.getTextLocales();
3686     }
3687 
changeListenerLocaleTo(@ullable Locale locale)3688     private void changeListenerLocaleTo(@Nullable Locale locale) {
3689         if (mListenerChanged) {
3690             // If a listener has been explicitly set, don't change it. We may break something.
3691             return;
3692         }
3693         // The following null check is not absolutely necessary since all calling points of
3694         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
3695         // here in case others would want to call this method in the future.
3696         if (mEditor != null) {
3697             KeyListener listener = mEditor.mKeyListener;
3698             if (listener instanceof DigitsKeyListener) {
3699                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
3700             } else if (listener instanceof DateKeyListener) {
3701                 listener = DateKeyListener.getInstance(locale);
3702             } else if (listener instanceof TimeKeyListener) {
3703                 listener = TimeKeyListener.getInstance(locale);
3704             } else if (listener instanceof DateTimeKeyListener) {
3705                 listener = DateTimeKeyListener.getInstance(locale);
3706             } else {
3707                 return;
3708             }
3709             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
3710             setKeyListenerOnly(listener);
3711             setInputTypeFromEditor();
3712             if (wasPasswordType) {
3713                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
3714                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
3715                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
3716                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
3717                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
3718                 }
3719             }
3720         }
3721     }
3722 
3723     /**
3724      * Set the default {@link Locale} of the text in this TextView to a one-member
3725      * {@link LocaleList} containing just the given Locale.
3726      *
3727      * @param locale the {@link Locale} for drawing text, must not be null.
3728      *
3729      * @see #setTextLocales
3730      */
setTextLocale(@onNull Locale locale)3731     public void setTextLocale(@NonNull Locale locale) {
3732         mLocalesChanged = true;
3733         mTextPaint.setTextLocale(locale);
3734         if (mLayout != null) {
3735             nullLayouts();
3736             requestLayout();
3737             invalidate();
3738         }
3739     }
3740 
3741     /**
3742      * Set the default {@link LocaleList} of the text in this TextView to the given value.
3743      *
3744      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
3745      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
3746      * other aspects of text display, including line breaking.
3747      *
3748      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
3749      *
3750      * @see Paint#setTextLocales
3751      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)3752     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
3753         mLocalesChanged = true;
3754         mTextPaint.setTextLocales(locales);
3755         if (mLayout != null) {
3756             nullLayouts();
3757             requestLayout();
3758             invalidate();
3759         }
3760     }
3761 
3762     @Override
onConfigurationChanged(Configuration newConfig)3763     protected void onConfigurationChanged(Configuration newConfig) {
3764         super.onConfigurationChanged(newConfig);
3765         if (!mLocalesChanged) {
3766             mTextPaint.setTextLocales(LocaleList.getDefault());
3767             if (mLayout != null) {
3768                 nullLayouts();
3769                 requestLayout();
3770                 invalidate();
3771             }
3772         }
3773     }
3774 
3775     /**
3776      * @return the size (in pixels) of the default text size in this TextView.
3777      */
3778     @ViewDebug.ExportedProperty(category = "text")
getTextSize()3779     public float getTextSize() {
3780         return mTextPaint.getTextSize();
3781     }
3782 
3783     /**
3784      * @return the size (in scaled pixels) of the default text size in this TextView.
3785      * @hide
3786      */
3787     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()3788     public float getScaledTextSize() {
3789         return mTextPaint.getTextSize() / mTextPaint.density;
3790     }
3791 
3792     /** @hide */
3793     @ViewDebug.ExportedProperty(category = "text", mapping = {
3794             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
3795             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
3796             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
3797             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
3798     })
getTypefaceStyle()3799     public int getTypefaceStyle() {
3800         Typeface typeface = mTextPaint.getTypeface();
3801         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
3802     }
3803 
3804     /**
3805      * Set the default text size to the given value, interpreted as "scaled
3806      * pixel" units.  This size is adjusted based on the current density and
3807      * user font size preference.
3808      *
3809      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
3810      *
3811      * @param size The scaled pixel size.
3812      *
3813      * @attr ref android.R.styleable#TextView_textSize
3814      */
3815     @android.view.RemotableViewMethod
setTextSize(float size)3816     public void setTextSize(float size) {
3817         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
3818     }
3819 
3820     /**
3821      * Set the default text size to a given unit and value. See {@link
3822      * TypedValue} for the possible dimension units.
3823      *
3824      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
3825      *
3826      * @param unit The desired dimension unit.
3827      * @param size The desired size in the given units.
3828      *
3829      * @attr ref android.R.styleable#TextView_textSize
3830      */
setTextSize(int unit, float size)3831     public void setTextSize(int unit, float size) {
3832         if (!isAutoSizeEnabled()) {
3833             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
3834         }
3835     }
3836 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)3837     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
3838         Context c = getContext();
3839         Resources r;
3840 
3841         if (c == null) {
3842             r = Resources.getSystem();
3843         } else {
3844             r = c.getResources();
3845         }
3846 
3847         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
3848                 shouldRequestLayout);
3849     }
3850 
setRawTextSize(float size, boolean shouldRequestLayout)3851     private void setRawTextSize(float size, boolean shouldRequestLayout) {
3852         if (size != mTextPaint.getTextSize()) {
3853             mTextPaint.setTextSize(size);
3854 
3855             if (shouldRequestLayout && mLayout != null) {
3856                 // Do not auto-size right after setting the text size.
3857                 mNeedsAutoSizeText = false;
3858                 nullLayouts();
3859                 requestLayout();
3860                 invalidate();
3861             }
3862         }
3863     }
3864 
3865     /**
3866      * Gets the extent by which text should be stretched horizontally.
3867      * This will usually be 1.0.
3868      * @return The horizontal scale factor.
3869      */
getTextScaleX()3870     public float getTextScaleX() {
3871         return mTextPaint.getTextScaleX();
3872     }
3873 
3874     /**
3875      * Sets the horizontal scale factor for text. The default value
3876      * is 1.0. Values greater than 1.0 stretch the text wider.
3877      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
3878      * @param size The horizontal scale factor.
3879      * @attr ref android.R.styleable#TextView_textScaleX
3880      */
3881     @android.view.RemotableViewMethod
setTextScaleX(float size)3882     public void setTextScaleX(float size) {
3883         if (size != mTextPaint.getTextScaleX()) {
3884             mUserSetTextScaleX = true;
3885             mTextPaint.setTextScaleX(size);
3886 
3887             if (mLayout != null) {
3888                 nullLayouts();
3889                 requestLayout();
3890                 invalidate();
3891             }
3892         }
3893     }
3894 
3895     /**
3896      * Sets the typeface and style in which the text should be displayed.
3897      * Note that not all Typeface families actually have bold and italic
3898      * variants, so you may need to use
3899      * {@link #setTypeface(Typeface, int)} to get the appearance
3900      * that you actually want.
3901      *
3902      * @see #getTypeface()
3903      *
3904      * @attr ref android.R.styleable#TextView_fontFamily
3905      * @attr ref android.R.styleable#TextView_typeface
3906      * @attr ref android.R.styleable#TextView_textStyle
3907      */
setTypeface(@ullable Typeface tf)3908     public void setTypeface(@Nullable Typeface tf) {
3909         if (mTextPaint.getTypeface() != tf) {
3910             mTextPaint.setTypeface(tf);
3911 
3912             if (mLayout != null) {
3913                 nullLayouts();
3914                 requestLayout();
3915                 invalidate();
3916             }
3917         }
3918     }
3919 
3920     /**
3921      * Gets the current {@link Typeface} that is used to style the text.
3922      * @return The current Typeface.
3923      *
3924      * @see #setTypeface(Typeface)
3925      *
3926      * @attr ref android.R.styleable#TextView_fontFamily
3927      * @attr ref android.R.styleable#TextView_typeface
3928      * @attr ref android.R.styleable#TextView_textStyle
3929      */
getTypeface()3930     public Typeface getTypeface() {
3931         return mTextPaint.getTypeface();
3932     }
3933 
3934     /**
3935      * Set the TextView's elegant height metrics flag. This setting selects font
3936      * variants that have not been compacted to fit Latin-based vertical
3937      * metrics, and also increases top and bottom bounds to provide more space.
3938      *
3939      * @param elegant set the paint's elegant metrics flag.
3940      *
3941      * @see #isElegantTextHeight()
3942      * @see Paint#isElegantTextHeight()
3943      *
3944      * @attr ref android.R.styleable#TextView_elegantTextHeight
3945      */
setElegantTextHeight(boolean elegant)3946     public void setElegantTextHeight(boolean elegant) {
3947         if (elegant != mTextPaint.isElegantTextHeight()) {
3948             mTextPaint.setElegantTextHeight(elegant);
3949             if (mLayout != null) {
3950                 nullLayouts();
3951                 requestLayout();
3952                 invalidate();
3953             }
3954         }
3955     }
3956 
3957     /**
3958      * Set whether to respect the ascent and descent of the fallback fonts that are used in
3959      * displaying the text (which is needed to avoid text from consecutive lines running into
3960      * each other). If set, fallback fonts that end up getting used can increase the ascent
3961      * and descent of the lines that they are used on.
3962      * <p/>
3963      * It is required to be true if text could be in languages like Burmese or Tibetan where text
3964      * is typically much taller or deeper than Latin text.
3965      *
3966      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
3967      *
3968      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
3969      *
3970      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
3971      */
setFallbackLineSpacing(boolean enabled)3972     public void setFallbackLineSpacing(boolean enabled) {
3973         if (mUseFallbackLineSpacing != enabled) {
3974             mUseFallbackLineSpacing = enabled;
3975             if (mLayout != null) {
3976                 nullLayouts();
3977                 requestLayout();
3978                 invalidate();
3979             }
3980         }
3981     }
3982 
3983     /**
3984      * @return whether fallback line spacing is enabled, {@code true} by default
3985      *
3986      * @see #setFallbackLineSpacing(boolean)
3987      *
3988      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
3989      */
isFallbackLineSpacing()3990     public boolean isFallbackLineSpacing() {
3991         return mUseFallbackLineSpacing;
3992     }
3993 
3994     /**
3995      * Get the value of the TextView's elegant height metrics flag. This setting selects font
3996      * variants that have not been compacted to fit Latin-based vertical
3997      * metrics, and also increases top and bottom bounds to provide more space.
3998      * @return {@code true} if the elegant height metrics flag is set.
3999      *
4000      * @see #setElegantTextHeight(boolean)
4001      * @see Paint#setElegantTextHeight(boolean)
4002      */
isElegantTextHeight()4003     public boolean isElegantTextHeight() {
4004         return mTextPaint.isElegantTextHeight();
4005     }
4006 
4007     /**
4008      * Gets the text letter-space value, which determines the spacing between characters.
4009      * The value returned is in ems. Normally, this value is 0.0.
4010      * @return The text letter-space value in ems.
4011      *
4012      * @see #setLetterSpacing(float)
4013      * @see Paint#setLetterSpacing
4014      */
getLetterSpacing()4015     public float getLetterSpacing() {
4016         return mTextPaint.getLetterSpacing();
4017     }
4018 
4019     /**
4020      * Sets text letter-spacing in em units.  Typical values
4021      * for slight expansion will be around 0.05.  Negative values tighten text.
4022      *
4023      * @see #getLetterSpacing()
4024      * @see Paint#getLetterSpacing
4025      *
4026      * @param letterSpacing A text letter-space value in ems.
4027      * @attr ref android.R.styleable#TextView_letterSpacing
4028      */
4029     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)4030     public void setLetterSpacing(float letterSpacing) {
4031         if (letterSpacing != mTextPaint.getLetterSpacing()) {
4032             mTextPaint.setLetterSpacing(letterSpacing);
4033 
4034             if (mLayout != null) {
4035                 nullLayouts();
4036                 requestLayout();
4037                 invalidate();
4038             }
4039         }
4040     }
4041 
4042     /**
4043      * Returns the font feature settings. The format is the same as the CSS
4044      * font-feature-settings attribute:
4045      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4046      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4047      *
4048      * @return the currently set font feature settings.  Default is null.
4049      *
4050      * @see #setFontFeatureSettings(String)
4051      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
4052      */
4053     @Nullable
getFontFeatureSettings()4054     public String getFontFeatureSettings() {
4055         return mTextPaint.getFontFeatureSettings();
4056     }
4057 
4058     /**
4059      * Returns the font variation settings.
4060      *
4061      * @return the currently set font variation settings.  Returns null if no variation is
4062      * specified.
4063      *
4064      * @see #setFontVariationSettings(String)
4065      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
4066      */
4067     @Nullable
getFontVariationSettings()4068     public String getFontVariationSettings() {
4069         return mTextPaint.getFontVariationSettings();
4070     }
4071 
4072     /**
4073      * Sets the break strategy for breaking paragraphs into lines. The default value for
4074      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
4075      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
4076      * text "dancing" when being edited.
4077      *
4078      * @attr ref android.R.styleable#TextView_breakStrategy
4079      * @see #getBreakStrategy()
4080      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4081     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
4082         mBreakStrategy = breakStrategy;
4083         if (mLayout != null) {
4084             nullLayouts();
4085             requestLayout();
4086             invalidate();
4087         }
4088     }
4089 
4090     /**
4091      * Gets the current strategy for breaking paragraphs into lines.
4092      * @return the current strategy for breaking paragraphs into lines.
4093      *
4094      * @attr ref android.R.styleable#TextView_breakStrategy
4095      * @see #setBreakStrategy(int)
4096      */
4097     @Layout.BreakStrategy
getBreakStrategy()4098     public int getBreakStrategy() {
4099         return mBreakStrategy;
4100     }
4101 
4102     /**
4103      * Sets the frequency of automatic hyphenation to use when determining word breaks.
4104      * The default value for both TextView and {@link EditText} is
4105      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
4106      * Note that the default hyphenation frequency value is set from the theme.
4107      *
4108      * @param hyphenationFrequency The hyphenation frequency to use.
4109      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4110      * @see #getHyphenationFrequency()
4111      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4112     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
4113         mHyphenationFrequency = hyphenationFrequency;
4114         if (mLayout != null) {
4115             nullLayouts();
4116             requestLayout();
4117             invalidate();
4118         }
4119     }
4120 
4121     /**
4122      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
4123      * @return the current frequency of automatic hyphenation to be used when determining word
4124      * breaks.
4125      *
4126      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4127      * @see #setHyphenationFrequency(int)
4128      */
4129     @Layout.HyphenationFrequency
getHyphenationFrequency()4130     public int getHyphenationFrequency() {
4131         return mHyphenationFrequency;
4132     }
4133 
4134     /**
4135      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
4136      *
4137      * @return a current {@link PrecomputedText.Params}
4138      * @see PrecomputedText
4139      */
getTextMetricsParams()4140     public @NonNull PrecomputedText.Params getTextMetricsParams() {
4141         return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
4142                 mBreakStrategy, mHyphenationFrequency);
4143     }
4144 
4145     /**
4146      * Apply the text layout parameter.
4147      *
4148      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
4149      * @see PrecomputedText
4150      */
setTextMetricsParams(@onNull PrecomputedText.Params params)4151     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
4152         mTextPaint.set(params.getTextPaint());
4153         mUserSetTextScaleX = true;
4154         mTextDir = params.getTextDirection();
4155         mBreakStrategy = params.getBreakStrategy();
4156         mHyphenationFrequency = params.getHyphenationFrequency();
4157         if (mLayout != null) {
4158             nullLayouts();
4159             requestLayout();
4160             invalidate();
4161         }
4162     }
4163 
4164     /**
4165      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
4166      * last line is too short for justification, the last line will be displayed with the
4167      * alignment set by {@link android.view.View#setTextAlignment}.
4168      *
4169      * @see #getJustificationMode()
4170      */
4171     @Layout.JustificationMode
setJustificationMode(@ayout.JustificationMode int justificationMode)4172     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
4173         mJustificationMode = justificationMode;
4174         if (mLayout != null) {
4175             nullLayouts();
4176             requestLayout();
4177             invalidate();
4178         }
4179     }
4180 
4181     /**
4182      * @return true if currently paragraph justification mode.
4183      *
4184      * @see #setJustificationMode(int)
4185      */
getJustificationMode()4186     public @Layout.JustificationMode int getJustificationMode() {
4187         return mJustificationMode;
4188     }
4189 
4190     /**
4191      * Sets font feature settings. The format is the same as the CSS
4192      * font-feature-settings attribute:
4193      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4194      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4195      *
4196      * @param fontFeatureSettings font feature settings represented as CSS compatible string
4197      *
4198      * @see #getFontFeatureSettings()
4199      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
4200      *
4201      * @attr ref android.R.styleable#TextView_fontFeatureSettings
4202      */
4203     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)4204     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
4205         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
4206             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
4207 
4208             if (mLayout != null) {
4209                 nullLayouts();
4210                 requestLayout();
4211                 invalidate();
4212             }
4213         }
4214     }
4215 
4216 
4217     /**
4218      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
4219      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
4220      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
4221      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
4222      * are invalid. If a specified axis name is not defined in the font, the settings will be
4223      * ignored.
4224      *
4225      * <p>
4226      * Examples,
4227      * <ul>
4228      * <li>Set font width to 150.
4229      * <pre>
4230      * <code>
4231      *   TextView textView = (TextView) findViewById(R.id.textView);
4232      *   textView.setFontVariationSettings("'wdth' 150");
4233      * </code>
4234      * </pre>
4235      * </li>
4236      *
4237      * <li>Set the font slant to 20 degrees and ask for italic style.
4238      * <pre>
4239      * <code>
4240      *   TextView textView = (TextView) findViewById(R.id.textView);
4241      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
4242      * </code>
4243      * </pre>
4244      * </p>
4245      * </li>
4246      * </ul>
4247      *
4248      * @param fontVariationSettings font variation settings. You can pass null or empty string as
4249      *                              no variation settings.
4250      * @return true if the given settings is effective to at least one font file underlying this
4251      *         TextView. This function also returns true for empty settings string. Otherwise
4252      *         returns false.
4253      *
4254      * @throws IllegalArgumentException If given string is not a valid font variation settings
4255      *                                  format.
4256      *
4257      * @see #getFontVariationSettings()
4258      * @see FontVariationAxis
4259      */
setFontVariationSettings(@ullable String fontVariationSettings)4260     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
4261         final String existingSettings = mTextPaint.getFontVariationSettings();
4262         if (fontVariationSettings == existingSettings
4263                 || (fontVariationSettings != null
4264                         && fontVariationSettings.equals(existingSettings))) {
4265             return true;
4266         }
4267         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
4268 
4269         if (effective && mLayout != null) {
4270             nullLayouts();
4271             requestLayout();
4272             invalidate();
4273         }
4274         return effective;
4275     }
4276 
4277     /**
4278      * Sets the text color for all the states (normal, selected,
4279      * focused) to be this color.
4280      *
4281      * @param color A color value in the form 0xAARRGGBB.
4282      * Do not pass a resource ID. To get a color value from a resource ID, call
4283      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
4284      *
4285      * @see #setTextColor(ColorStateList)
4286      * @see #getTextColors()
4287      *
4288      * @attr ref android.R.styleable#TextView_textColor
4289      */
4290     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)4291     public void setTextColor(@ColorInt int color) {
4292         mTextColor = ColorStateList.valueOf(color);
4293         updateTextColors();
4294     }
4295 
4296     /**
4297      * Sets the text color.
4298      *
4299      * @see #setTextColor(int)
4300      * @see #getTextColors()
4301      * @see #setHintTextColor(ColorStateList)
4302      * @see #setLinkTextColor(ColorStateList)
4303      *
4304      * @attr ref android.R.styleable#TextView_textColor
4305      */
4306     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)4307     public void setTextColor(ColorStateList colors) {
4308         if (colors == null) {
4309             throw new NullPointerException();
4310         }
4311 
4312         mTextColor = colors;
4313         updateTextColors();
4314     }
4315 
4316     /**
4317      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
4318      *
4319      * @see #setTextColor(ColorStateList)
4320      * @see #setTextColor(int)
4321      *
4322      * @attr ref android.R.styleable#TextView_textColor
4323      */
getTextColors()4324     public final ColorStateList getTextColors() {
4325         return mTextColor;
4326     }
4327 
4328     /**
4329      * Return the current color selected for normal text.
4330      *
4331      * @return Returns the current text color.
4332      */
4333     @ColorInt
getCurrentTextColor()4334     public final int getCurrentTextColor() {
4335         return mCurTextColor;
4336     }
4337 
4338     /**
4339      * Sets the color used to display the selection highlight.
4340      *
4341      * @attr ref android.R.styleable#TextView_textColorHighlight
4342      */
4343     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)4344     public void setHighlightColor(@ColorInt int color) {
4345         if (mHighlightColor != color) {
4346             mHighlightColor = color;
4347             invalidate();
4348         }
4349     }
4350 
4351     /**
4352      * @return the color used to display the selection highlight
4353      *
4354      * @see #setHighlightColor(int)
4355      *
4356      * @attr ref android.R.styleable#TextView_textColorHighlight
4357      */
4358     @ColorInt
getHighlightColor()4359     public int getHighlightColor() {
4360         return mHighlightColor;
4361     }
4362 
4363     /**
4364      * Sets whether the soft input method will be made visible when this
4365      * TextView gets focused. The default is true.
4366      */
4367     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)4368     public final void setShowSoftInputOnFocus(boolean show) {
4369         createEditorIfNeeded();
4370         mEditor.mShowSoftInputOnFocus = show;
4371     }
4372 
4373     /**
4374      * Returns whether the soft input method will be made visible when this
4375      * TextView gets focused. The default is true.
4376      */
getShowSoftInputOnFocus()4377     public final boolean getShowSoftInputOnFocus() {
4378         // When there is no Editor, return default true value
4379         return mEditor == null || mEditor.mShowSoftInputOnFocus;
4380     }
4381 
4382     /**
4383      * Gives the text a shadow of the specified blur radius and color, the specified
4384      * distance from its drawn position.
4385      * <p>
4386      * The text shadow produced does not interact with the properties on view
4387      * that are responsible for real time shadows,
4388      * {@link View#getElevation() elevation} and
4389      * {@link View#getTranslationZ() translationZ}.
4390      *
4391      * @see Paint#setShadowLayer(float, float, float, int)
4392      *
4393      * @attr ref android.R.styleable#TextView_shadowColor
4394      * @attr ref android.R.styleable#TextView_shadowDx
4395      * @attr ref android.R.styleable#TextView_shadowDy
4396      * @attr ref android.R.styleable#TextView_shadowRadius
4397      */
setShadowLayer(float radius, float dx, float dy, int color)4398     public void setShadowLayer(float radius, float dx, float dy, int color) {
4399         mTextPaint.setShadowLayer(radius, dx, dy, color);
4400 
4401         mShadowRadius = radius;
4402         mShadowDx = dx;
4403         mShadowDy = dy;
4404         mShadowColor = color;
4405 
4406         // Will change text clip region
4407         if (mEditor != null) {
4408             mEditor.invalidateTextDisplayList();
4409             mEditor.invalidateHandlesAndActionMode();
4410         }
4411         invalidate();
4412     }
4413 
4414     /**
4415      * Gets the radius of the shadow layer.
4416      *
4417      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
4418      *
4419      * @see #setShadowLayer(float, float, float, int)
4420      *
4421      * @attr ref android.R.styleable#TextView_shadowRadius
4422      */
getShadowRadius()4423     public float getShadowRadius() {
4424         return mShadowRadius;
4425     }
4426 
4427     /**
4428      * @return the horizontal offset of the shadow layer
4429      *
4430      * @see #setShadowLayer(float, float, float, int)
4431      *
4432      * @attr ref android.R.styleable#TextView_shadowDx
4433      */
getShadowDx()4434     public float getShadowDx() {
4435         return mShadowDx;
4436     }
4437 
4438     /**
4439      * Gets the vertical offset of the shadow layer.
4440      * @return The vertical offset of the shadow layer.
4441      *
4442      * @see #setShadowLayer(float, float, float, int)
4443      *
4444      * @attr ref android.R.styleable#TextView_shadowDy
4445      */
getShadowDy()4446     public float getShadowDy() {
4447         return mShadowDy;
4448     }
4449 
4450     /**
4451      * Gets the color of the shadow layer.
4452      * @return the color of the shadow layer
4453      *
4454      * @see #setShadowLayer(float, float, float, int)
4455      *
4456      * @attr ref android.R.styleable#TextView_shadowColor
4457      */
4458     @ColorInt
getShadowColor()4459     public int getShadowColor() {
4460         return mShadowColor;
4461     }
4462 
4463     /**
4464      * Gets the {@link TextPaint} used for the text.
4465      * Use this only to consult the Paint's properties and not to change them.
4466      * @return The base paint used for the text.
4467      */
getPaint()4468     public TextPaint getPaint() {
4469         return mTextPaint;
4470     }
4471 
4472     /**
4473      * Sets the autolink mask of the text.  See {@link
4474      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
4475      * possible values.
4476      *
4477      * @attr ref android.R.styleable#TextView_autoLink
4478      */
4479     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)4480     public final void setAutoLinkMask(int mask) {
4481         mAutoLinkMask = mask;
4482     }
4483 
4484     /**
4485      * Sets whether the movement method will automatically be set to
4486      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4487      * set to nonzero and links are detected in {@link #setText}.
4488      * The default is true.
4489      *
4490      * @attr ref android.R.styleable#TextView_linksClickable
4491      */
4492     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)4493     public final void setLinksClickable(boolean whether) {
4494         mLinksClickable = whether;
4495     }
4496 
4497     /**
4498      * Returns whether the movement method will automatically be set to
4499      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4500      * set to nonzero and links are detected in {@link #setText}.
4501      * The default is true.
4502      *
4503      * @attr ref android.R.styleable#TextView_linksClickable
4504      */
getLinksClickable()4505     public final boolean getLinksClickable() {
4506         return mLinksClickable;
4507     }
4508 
4509     /**
4510      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
4511      * (by {@link Linkify} or otherwise) if any.  You can call
4512      * {@link URLSpan#getURL} on them to find where they link to
4513      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
4514      * to find the region of the text they are attached to.
4515      */
getUrls()4516     public URLSpan[] getUrls() {
4517         if (mText instanceof Spanned) {
4518             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
4519         } else {
4520             return new URLSpan[0];
4521         }
4522     }
4523 
4524     /**
4525      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
4526      * TextView.
4527      *
4528      * @see #setHintTextColor(ColorStateList)
4529      * @see #getHintTextColors()
4530      * @see #setTextColor(int)
4531      *
4532      * @attr ref android.R.styleable#TextView_textColorHint
4533      */
4534     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)4535     public final void setHintTextColor(@ColorInt int color) {
4536         mHintTextColor = ColorStateList.valueOf(color);
4537         updateTextColors();
4538     }
4539 
4540     /**
4541      * Sets the color of the hint text.
4542      *
4543      * @see #getHintTextColors()
4544      * @see #setHintTextColor(int)
4545      * @see #setTextColor(ColorStateList)
4546      * @see #setLinkTextColor(ColorStateList)
4547      *
4548      * @attr ref android.R.styleable#TextView_textColorHint
4549      */
setHintTextColor(ColorStateList colors)4550     public final void setHintTextColor(ColorStateList colors) {
4551         mHintTextColor = colors;
4552         updateTextColors();
4553     }
4554 
4555     /**
4556      * @return the color of the hint text, for the different states of this TextView.
4557      *
4558      * @see #setHintTextColor(ColorStateList)
4559      * @see #setHintTextColor(int)
4560      * @see #setTextColor(ColorStateList)
4561      * @see #setLinkTextColor(ColorStateList)
4562      *
4563      * @attr ref android.R.styleable#TextView_textColorHint
4564      */
getHintTextColors()4565     public final ColorStateList getHintTextColors() {
4566         return mHintTextColor;
4567     }
4568 
4569     /**
4570      * <p>Return the current color selected to paint the hint text.</p>
4571      *
4572      * @return Returns the current hint text color.
4573      */
4574     @ColorInt
getCurrentHintTextColor()4575     public final int getCurrentHintTextColor() {
4576         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
4577     }
4578 
4579     /**
4580      * Sets the color of links in the text.
4581      *
4582      * @see #setLinkTextColor(ColorStateList)
4583      * @see #getLinkTextColors()
4584      *
4585      * @attr ref android.R.styleable#TextView_textColorLink
4586      */
4587     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)4588     public final void setLinkTextColor(@ColorInt int color) {
4589         mLinkTextColor = ColorStateList.valueOf(color);
4590         updateTextColors();
4591     }
4592 
4593     /**
4594      * Sets the color of links in the text.
4595      *
4596      * @see #setLinkTextColor(int)
4597      * @see #getLinkTextColors()
4598      * @see #setTextColor(ColorStateList)
4599      * @see #setHintTextColor(ColorStateList)
4600      *
4601      * @attr ref android.R.styleable#TextView_textColorLink
4602      */
setLinkTextColor(ColorStateList colors)4603     public final void setLinkTextColor(ColorStateList colors) {
4604         mLinkTextColor = colors;
4605         updateTextColors();
4606     }
4607 
4608     /**
4609      * @return the list of colors used to paint the links in the text, for the different states of
4610      * this TextView
4611      *
4612      * @see #setLinkTextColor(ColorStateList)
4613      * @see #setLinkTextColor(int)
4614      *
4615      * @attr ref android.R.styleable#TextView_textColorLink
4616      */
getLinkTextColors()4617     public final ColorStateList getLinkTextColors() {
4618         return mLinkTextColor;
4619     }
4620 
4621     /**
4622      * Sets the horizontal alignment of the text and the
4623      * vertical gravity that will be used when there is extra space
4624      * in the TextView beyond what is required for the text itself.
4625      *
4626      * @see android.view.Gravity
4627      * @attr ref android.R.styleable#TextView_gravity
4628      */
setGravity(int gravity)4629     public void setGravity(int gravity) {
4630         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
4631             gravity |= Gravity.START;
4632         }
4633         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
4634             gravity |= Gravity.TOP;
4635         }
4636 
4637         boolean newLayout = false;
4638 
4639         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
4640                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
4641             newLayout = true;
4642         }
4643 
4644         if (gravity != mGravity) {
4645             invalidate();
4646         }
4647 
4648         mGravity = gravity;
4649 
4650         if (mLayout != null && newLayout) {
4651             // XXX this is heavy-handed because no actual content changes.
4652             int want = mLayout.getWidth();
4653             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
4654 
4655             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
4656                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
4657         }
4658     }
4659 
4660     /**
4661      * Returns the horizontal and vertical alignment of this TextView.
4662      *
4663      * @see android.view.Gravity
4664      * @attr ref android.R.styleable#TextView_gravity
4665      */
getGravity()4666     public int getGravity() {
4667         return mGravity;
4668     }
4669 
4670     /**
4671      * Gets the flags on the Paint being used to display the text.
4672      * @return The flags on the Paint being used to display the text.
4673      * @see Paint#getFlags
4674      */
getPaintFlags()4675     public int getPaintFlags() {
4676         return mTextPaint.getFlags();
4677     }
4678 
4679     /**
4680      * Sets flags on the Paint being used to display the text and
4681      * reflows the text if they are different from the old flags.
4682      * @see Paint#setFlags
4683      */
4684     @android.view.RemotableViewMethod
setPaintFlags(int flags)4685     public void setPaintFlags(int flags) {
4686         if (mTextPaint.getFlags() != flags) {
4687             mTextPaint.setFlags(flags);
4688 
4689             if (mLayout != null) {
4690                 nullLayouts();
4691                 requestLayout();
4692                 invalidate();
4693             }
4694         }
4695     }
4696 
4697     /**
4698      * Sets whether the text should be allowed to be wider than the
4699      * View is.  If false, it will be wrapped to the width of the View.
4700      *
4701      * @attr ref android.R.styleable#TextView_scrollHorizontally
4702      */
setHorizontallyScrolling(boolean whether)4703     public void setHorizontallyScrolling(boolean whether) {
4704         if (mHorizontallyScrolling != whether) {
4705             mHorizontallyScrolling = whether;
4706 
4707             if (mLayout != null) {
4708                 nullLayouts();
4709                 requestLayout();
4710                 invalidate();
4711             }
4712         }
4713     }
4714 
4715     /**
4716      * Returns whether the text is allowed to be wider than the View is.
4717      * If false, the text will be wrapped to the width of the View.
4718      *
4719      * @attr ref android.R.styleable#TextView_scrollHorizontally
4720      * @hide
4721      */
getHorizontallyScrolling()4722     public boolean getHorizontallyScrolling() {
4723         return mHorizontallyScrolling;
4724     }
4725 
4726     /**
4727      * Sets the height of the TextView to be at least {@code minLines} tall.
4728      * <p>
4729      * This value is used for height calculation if LayoutParams does not force TextView to have an
4730      * exact height. Setting this value overrides other previous minimum height configurations such
4731      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
4732      * this value to 1.
4733      *
4734      * @param minLines the minimum height of TextView in terms of number of lines
4735      *
4736      * @see #getMinLines()
4737      * @see #setLines(int)
4738      *
4739      * @attr ref android.R.styleable#TextView_minLines
4740      */
4741     @android.view.RemotableViewMethod
setMinLines(int minLines)4742     public void setMinLines(int minLines) {
4743         mMinimum = minLines;
4744         mMinMode = LINES;
4745 
4746         requestLayout();
4747         invalidate();
4748     }
4749 
4750     /**
4751      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
4752      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
4753      *
4754      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
4755      *         height is not defined in lines
4756      *
4757      * @see #setMinLines(int)
4758      * @see #setLines(int)
4759      *
4760      * @attr ref android.R.styleable#TextView_minLines
4761      */
getMinLines()4762     public int getMinLines() {
4763         return mMinMode == LINES ? mMinimum : -1;
4764     }
4765 
4766     /**
4767      * Sets the height of the TextView to be at least {@code minPixels} tall.
4768      * <p>
4769      * This value is used for height calculation if LayoutParams does not force TextView to have an
4770      * exact height. Setting this value overrides previous minimum height configurations such as
4771      * {@link #setMinLines(int)} or {@link #setLines(int)}.
4772      * <p>
4773      * The value given here is different than {@link #setMinimumHeight(int)}. Between
4774      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
4775      * used to decide the final height.
4776      *
4777      * @param minPixels the minimum height of TextView in terms of pixels
4778      *
4779      * @see #getMinHeight()
4780      * @see #setHeight(int)
4781      *
4782      * @attr ref android.R.styleable#TextView_minHeight
4783      */
4784     @android.view.RemotableViewMethod
setMinHeight(int minPixels)4785     public void setMinHeight(int minPixels) {
4786         mMinimum = minPixels;
4787         mMinMode = PIXELS;
4788 
4789         requestLayout();
4790         invalidate();
4791     }
4792 
4793     /**
4794      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
4795      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
4796      *
4797      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
4798      *         defined in pixels
4799      *
4800      * @see #setMinHeight(int)
4801      * @see #setHeight(int)
4802      *
4803      * @attr ref android.R.styleable#TextView_minHeight
4804      */
getMinHeight()4805     public int getMinHeight() {
4806         return mMinMode == PIXELS ? mMinimum : -1;
4807     }
4808 
4809     /**
4810      * Sets the height of the TextView to be at most {@code maxLines} tall.
4811      * <p>
4812      * This value is used for height calculation if LayoutParams does not force TextView to have an
4813      * exact height. Setting this value overrides previous maximum height configurations such as
4814      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
4815      *
4816      * @param maxLines the maximum height of TextView in terms of number of lines
4817      *
4818      * @see #getMaxLines()
4819      * @see #setLines(int)
4820      *
4821      * @attr ref android.R.styleable#TextView_maxLines
4822      */
4823     @android.view.RemotableViewMethod
setMaxLines(int maxLines)4824     public void setMaxLines(int maxLines) {
4825         mMaximum = maxLines;
4826         mMaxMode = LINES;
4827 
4828         requestLayout();
4829         invalidate();
4830     }
4831 
4832     /**
4833      * Returns the maximum height of TextView in terms of number of lines or -1 if the
4834      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
4835      *
4836      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
4837      *         is not defined in lines.
4838      *
4839      * @see #setMaxLines(int)
4840      * @see #setLines(int)
4841      *
4842      * @attr ref android.R.styleable#TextView_maxLines
4843      */
getMaxLines()4844     public int getMaxLines() {
4845         return mMaxMode == LINES ? mMaximum : -1;
4846     }
4847 
4848     /**
4849      * Sets the height of the TextView to be at most {@code maxPixels} tall.
4850      * <p>
4851      * This value is used for height calculation if LayoutParams does not force TextView to have an
4852      * exact height. Setting this value overrides previous maximum height configurations such as
4853      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
4854      *
4855      * @param maxPixels the maximum height of TextView in terms of pixels
4856      *
4857      * @see #getMaxHeight()
4858      * @see #setHeight(int)
4859      *
4860      * @attr ref android.R.styleable#TextView_maxHeight
4861      */
4862     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)4863     public void setMaxHeight(int maxPixels) {
4864         mMaximum = maxPixels;
4865         mMaxMode = PIXELS;
4866 
4867         requestLayout();
4868         invalidate();
4869     }
4870 
4871     /**
4872      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
4873      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
4874      *
4875      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
4876      *         is not defined in pixels
4877      *
4878      * @see #setMaxHeight(int)
4879      * @see #setHeight(int)
4880      *
4881      * @attr ref android.R.styleable#TextView_maxHeight
4882      */
getMaxHeight()4883     public int getMaxHeight() {
4884         return mMaxMode == PIXELS ? mMaximum : -1;
4885     }
4886 
4887     /**
4888      * Sets the height of the TextView to be exactly {@code lines} tall.
4889      * <p>
4890      * This value is used for height calculation if LayoutParams does not force TextView to have an
4891      * exact height. Setting this value overrides previous minimum/maximum height configurations
4892      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
4893      * set this value to 1.
4894      *
4895      * @param lines the exact height of the TextView in terms of lines
4896      *
4897      * @see #setHeight(int)
4898      *
4899      * @attr ref android.R.styleable#TextView_lines
4900      */
4901     @android.view.RemotableViewMethod
setLines(int lines)4902     public void setLines(int lines) {
4903         mMaximum = mMinimum = lines;
4904         mMaxMode = mMinMode = LINES;
4905 
4906         requestLayout();
4907         invalidate();
4908     }
4909 
4910     /**
4911      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
4912      * <p>
4913      * This value is used for height calculation if LayoutParams does not force TextView to have an
4914      * exact height. Setting this value overrides previous minimum/maximum height configurations
4915      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
4916      *
4917      * @param pixels the exact height of the TextView in terms of pixels
4918      *
4919      * @see #setLines(int)
4920      *
4921      * @attr ref android.R.styleable#TextView_height
4922      */
4923     @android.view.RemotableViewMethod
setHeight(int pixels)4924     public void setHeight(int pixels) {
4925         mMaximum = mMinimum = pixels;
4926         mMaxMode = mMinMode = PIXELS;
4927 
4928         requestLayout();
4929         invalidate();
4930     }
4931 
4932     /**
4933      * Sets the width of the TextView to be at least {@code minEms} wide.
4934      * <p>
4935      * This value is used for width calculation if LayoutParams does not force TextView to have an
4936      * exact width. Setting this value overrides previous minimum width configurations such as
4937      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
4938      *
4939      * @param minEms the minimum width of TextView in terms of ems
4940      *
4941      * @see #getMinEms()
4942      * @see #setEms(int)
4943      *
4944      * @attr ref android.R.styleable#TextView_minEms
4945      */
4946     @android.view.RemotableViewMethod
setMinEms(int minEms)4947     public void setMinEms(int minEms) {
4948         mMinWidth = minEms;
4949         mMinWidthMode = EMS;
4950 
4951         requestLayout();
4952         invalidate();
4953     }
4954 
4955     /**
4956      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
4957      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
4958      *
4959      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
4960      *         defined in ems
4961      *
4962      * @see #setMinEms(int)
4963      * @see #setEms(int)
4964      *
4965      * @attr ref android.R.styleable#TextView_minEms
4966      */
getMinEms()4967     public int getMinEms() {
4968         return mMinWidthMode == EMS ? mMinWidth : -1;
4969     }
4970 
4971     /**
4972      * Sets the width of the TextView to be at least {@code minPixels} wide.
4973      * <p>
4974      * This value is used for width calculation if LayoutParams does not force TextView to have an
4975      * exact width. Setting this value overrides previous minimum width configurations such as
4976      * {@link #setMinEms(int)} or {@link #setEms(int)}.
4977      * <p>
4978      * The value given here is different than {@link #setMinimumWidth(int)}. Between
4979      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
4980      * to decide the final width.
4981      *
4982      * @param minPixels the minimum width of TextView in terms of pixels
4983      *
4984      * @see #getMinWidth()
4985      * @see #setWidth(int)
4986      *
4987      * @attr ref android.R.styleable#TextView_minWidth
4988      */
4989     @android.view.RemotableViewMethod
setMinWidth(int minPixels)4990     public void setMinWidth(int minPixels) {
4991         mMinWidth = minPixels;
4992         mMinWidthMode = PIXELS;
4993 
4994         requestLayout();
4995         invalidate();
4996     }
4997 
4998     /**
4999      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
5000      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
5001      *
5002      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
5003      *         defined in pixels
5004      *
5005      * @see #setMinWidth(int)
5006      * @see #setWidth(int)
5007      *
5008      * @attr ref android.R.styleable#TextView_minWidth
5009      */
getMinWidth()5010     public int getMinWidth() {
5011         return mMinWidthMode == PIXELS ? mMinWidth : -1;
5012     }
5013 
5014     /**
5015      * Sets the width of the TextView to be at most {@code maxEms} wide.
5016      * <p>
5017      * This value is used for width calculation if LayoutParams does not force TextView to have an
5018      * exact width. Setting this value overrides previous maximum width configurations such as
5019      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5020      *
5021      * @param maxEms the maximum width of TextView in terms of ems
5022      *
5023      * @see #getMaxEms()
5024      * @see #setEms(int)
5025      *
5026      * @attr ref android.R.styleable#TextView_maxEms
5027      */
5028     @android.view.RemotableViewMethod
setMaxEms(int maxEms)5029     public void setMaxEms(int maxEms) {
5030         mMaxWidth = maxEms;
5031         mMaxWidthMode = EMS;
5032 
5033         requestLayout();
5034         invalidate();
5035     }
5036 
5037     /**
5038      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
5039      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5040      *
5041      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
5042      *         defined in ems
5043      *
5044      * @see #setMaxEms(int)
5045      * @see #setEms(int)
5046      *
5047      * @attr ref android.R.styleable#TextView_maxEms
5048      */
getMaxEms()5049     public int getMaxEms() {
5050         return mMaxWidthMode == EMS ? mMaxWidth : -1;
5051     }
5052 
5053     /**
5054      * Sets the width of the TextView to be at most {@code maxPixels} wide.
5055      * <p>
5056      * This value is used for width calculation if LayoutParams does not force TextView to have an
5057      * exact width. Setting this value overrides previous maximum width configurations such as
5058      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
5059      *
5060      * @param maxPixels the maximum width of TextView in terms of pixels
5061      *
5062      * @see #getMaxWidth()
5063      * @see #setWidth(int)
5064      *
5065      * @attr ref android.R.styleable#TextView_maxWidth
5066      */
5067     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)5068     public void setMaxWidth(int maxPixels) {
5069         mMaxWidth = maxPixels;
5070         mMaxWidthMode = PIXELS;
5071 
5072         requestLayout();
5073         invalidate();
5074     }
5075 
5076     /**
5077      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
5078      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
5079      *
5080      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
5081      *         defined in pixels
5082      *
5083      * @see #setMaxWidth(int)
5084      * @see #setWidth(int)
5085      *
5086      * @attr ref android.R.styleable#TextView_maxWidth
5087      */
getMaxWidth()5088     public int getMaxWidth() {
5089         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
5090     }
5091 
5092     /**
5093      * Sets the width of the TextView to be exactly {@code ems} wide.
5094      *
5095      * This value is used for width calculation if LayoutParams does not force TextView to have an
5096      * exact width. Setting this value overrides previous minimum/maximum configurations such as
5097      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
5098      *
5099      * @param ems the exact width of the TextView in terms of ems
5100      *
5101      * @see #setWidth(int)
5102      *
5103      * @attr ref android.R.styleable#TextView_ems
5104      */
5105     @android.view.RemotableViewMethod
setEms(int ems)5106     public void setEms(int ems) {
5107         mMaxWidth = mMinWidth = ems;
5108         mMaxWidthMode = mMinWidthMode = EMS;
5109 
5110         requestLayout();
5111         invalidate();
5112     }
5113 
5114     /**
5115      * Sets the width of the TextView to be exactly {@code pixels} wide.
5116      * <p>
5117      * This value is used for width calculation if LayoutParams does not force TextView to have an
5118      * exact width. Setting this value overrides previous minimum/maximum width configurations
5119      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
5120      *
5121      * @param pixels the exact width of the TextView in terms of pixels
5122      *
5123      * @see #setEms(int)
5124      *
5125      * @attr ref android.R.styleable#TextView_width
5126      */
5127     @android.view.RemotableViewMethod
setWidth(int pixels)5128     public void setWidth(int pixels) {
5129         mMaxWidth = mMinWidth = pixels;
5130         mMaxWidthMode = mMinWidthMode = PIXELS;
5131 
5132         requestLayout();
5133         invalidate();
5134     }
5135 
5136     /**
5137      * Sets line spacing for this TextView.  Each line other than the last line will have its height
5138      * multiplied by {@code mult} and have {@code add} added to it.
5139      *
5140      * @param add The value in pixels that should be added to each line other than the last line.
5141      *            This will be applied after the multiplier
5142      * @param mult The value by which each line height other than the last line will be multiplied
5143      *             by
5144      *
5145      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5146      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5147      */
setLineSpacing(float add, float mult)5148     public void setLineSpacing(float add, float mult) {
5149         if (mSpacingAdd != add || mSpacingMult != mult) {
5150             mSpacingAdd = add;
5151             mSpacingMult = mult;
5152 
5153             if (mLayout != null) {
5154                 nullLayouts();
5155                 requestLayout();
5156                 invalidate();
5157             }
5158         }
5159     }
5160 
5161     /**
5162      * Gets the line spacing multiplier
5163      *
5164      * @return the value by which each line's height is multiplied to get its actual height.
5165      *
5166      * @see #setLineSpacing(float, float)
5167      * @see #getLineSpacingExtra()
5168      *
5169      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5170      */
getLineSpacingMultiplier()5171     public float getLineSpacingMultiplier() {
5172         return mSpacingMult;
5173     }
5174 
5175     /**
5176      * Gets the line spacing extra space
5177      *
5178      * @return the extra space that is added to the height of each lines of this TextView.
5179      *
5180      * @see #setLineSpacing(float, float)
5181      * @see #getLineSpacingMultiplier()
5182      *
5183      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5184      */
getLineSpacingExtra()5185     public float getLineSpacingExtra() {
5186         return mSpacingAdd;
5187     }
5188 
5189     /**
5190      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
5191      * between subsequent baselines in the TextView.
5192      *
5193      * @param lineHeight the line height in pixels
5194      *
5195      * @see #setLineSpacing(float, float)
5196      * @see #getLineSpacing()
5197      *
5198      * @attr ref android.R.styleable#TextView_lineHeight
5199      */
setLineHeight(@x @ntRangefrom = 0) int lineHeight)5200     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
5201         Preconditions.checkArgumentNonnegative(lineHeight);
5202 
5203         final int fontHeight = getPaint().getFontMetricsInt(null);
5204         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
5205         if (lineHeight != fontHeight) {
5206             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
5207             setLineSpacing(lineHeight - fontHeight, 1f);
5208         }
5209     }
5210 
5211     /**
5212      * Convenience method to append the specified text to the TextView's
5213      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5214      * if it was not already editable.
5215      *
5216      * @param text text to be appended to the already displayed text
5217      */
append(CharSequence text)5218     public final void append(CharSequence text) {
5219         append(text, 0, text.length());
5220     }
5221 
5222     /**
5223      * Convenience method to append the specified text slice to the TextView's
5224      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5225      * if it was not already editable.
5226      *
5227      * @param text text to be appended to the already displayed text
5228      * @param start the index of the first character in the {@code text}
5229      * @param end the index of the character following the last character in the {@code text}
5230      *
5231      * @see Appendable#append(CharSequence, int, int)
5232      */
append(CharSequence text, int start, int end)5233     public void append(CharSequence text, int start, int end) {
5234         if (!(mText instanceof Editable)) {
5235             setText(mText, BufferType.EDITABLE);
5236         }
5237 
5238         ((Editable) mText).append(text, start, end);
5239 
5240         if (mAutoLinkMask != 0) {
5241             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
5242             // Do not change the movement method for text that support text selection as it
5243             // would prevent an arbitrary cursor displacement.
5244             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
5245                 setMovementMethod(LinkMovementMethod.getInstance());
5246             }
5247         }
5248     }
5249 
updateTextColors()5250     private void updateTextColors() {
5251         boolean inval = false;
5252         final int[] drawableState = getDrawableState();
5253         int color = mTextColor.getColorForState(drawableState, 0);
5254         if (color != mCurTextColor) {
5255             mCurTextColor = color;
5256             inval = true;
5257         }
5258         if (mLinkTextColor != null) {
5259             color = mLinkTextColor.getColorForState(drawableState, 0);
5260             if (color != mTextPaint.linkColor) {
5261                 mTextPaint.linkColor = color;
5262                 inval = true;
5263             }
5264         }
5265         if (mHintTextColor != null) {
5266             color = mHintTextColor.getColorForState(drawableState, 0);
5267             if (color != mCurHintTextColor) {
5268                 mCurHintTextColor = color;
5269                 if (mText.length() == 0) {
5270                     inval = true;
5271                 }
5272             }
5273         }
5274         if (inval) {
5275             // Text needs to be redrawn with the new color
5276             if (mEditor != null) mEditor.invalidateTextDisplayList();
5277             invalidate();
5278         }
5279     }
5280 
5281     @Override
drawableStateChanged()5282     protected void drawableStateChanged() {
5283         super.drawableStateChanged();
5284 
5285         if (mTextColor != null && mTextColor.isStateful()
5286                 || (mHintTextColor != null && mHintTextColor.isStateful())
5287                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
5288             updateTextColors();
5289         }
5290 
5291         if (mDrawables != null) {
5292             final int[] state = getDrawableState();
5293             for (Drawable dr : mDrawables.mShowing) {
5294                 if (dr != null && dr.isStateful() && dr.setState(state)) {
5295                     invalidateDrawable(dr);
5296                 }
5297             }
5298         }
5299     }
5300 
5301     @Override
drawableHotspotChanged(float x, float y)5302     public void drawableHotspotChanged(float x, float y) {
5303         super.drawableHotspotChanged(x, y);
5304 
5305         if (mDrawables != null) {
5306             for (Drawable dr : mDrawables.mShowing) {
5307                 if (dr != null) {
5308                     dr.setHotspot(x, y);
5309                 }
5310             }
5311         }
5312     }
5313 
5314     @Override
onSaveInstanceState()5315     public Parcelable onSaveInstanceState() {
5316         Parcelable superState = super.onSaveInstanceState();
5317 
5318         // Save state if we are forced to
5319         final boolean freezesText = getFreezesText();
5320         boolean hasSelection = false;
5321         int start = -1;
5322         int end = -1;
5323 
5324         if (mText != null) {
5325             start = getSelectionStart();
5326             end = getSelectionEnd();
5327             if (start >= 0 || end >= 0) {
5328                 // Or save state if there is a selection
5329                 hasSelection = true;
5330             }
5331         }
5332 
5333         if (freezesText || hasSelection) {
5334             SavedState ss = new SavedState(superState);
5335 
5336             if (freezesText) {
5337                 if (mText instanceof Spanned) {
5338                     final Spannable sp = new SpannableStringBuilder(mText);
5339 
5340                     if (mEditor != null) {
5341                         removeMisspelledSpans(sp);
5342                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
5343                     }
5344 
5345                     ss.text = sp;
5346                 } else {
5347                     ss.text = mText.toString();
5348                 }
5349             }
5350 
5351             if (hasSelection) {
5352                 // XXX Should also save the current scroll position!
5353                 ss.selStart = start;
5354                 ss.selEnd = end;
5355             }
5356 
5357             if (isFocused() && start >= 0 && end >= 0) {
5358                 ss.frozenWithFocus = true;
5359             }
5360 
5361             ss.error = getError();
5362 
5363             if (mEditor != null) {
5364                 ss.editorState = mEditor.saveInstanceState();
5365             }
5366             return ss;
5367         }
5368 
5369         return superState;
5370     }
5371 
removeMisspelledSpans(Spannable spannable)5372     void removeMisspelledSpans(Spannable spannable) {
5373         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
5374                 SuggestionSpan.class);
5375         for (int i = 0; i < suggestionSpans.length; i++) {
5376             int flags = suggestionSpans[i].getFlags();
5377             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
5378                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
5379                 spannable.removeSpan(suggestionSpans[i]);
5380             }
5381         }
5382     }
5383 
5384     @Override
onRestoreInstanceState(Parcelable state)5385     public void onRestoreInstanceState(Parcelable state) {
5386         if (!(state instanceof SavedState)) {
5387             super.onRestoreInstanceState(state);
5388             return;
5389         }
5390 
5391         SavedState ss = (SavedState) state;
5392         super.onRestoreInstanceState(ss.getSuperState());
5393 
5394         // XXX restore buffer type too, as well as lots of other stuff
5395         if (ss.text != null) {
5396             setText(ss.text);
5397         }
5398 
5399         if (ss.selStart >= 0 && ss.selEnd >= 0) {
5400             if (mSpannable != null) {
5401                 int len = mText.length();
5402 
5403                 if (ss.selStart > len || ss.selEnd > len) {
5404                     String restored = "";
5405 
5406                     if (ss.text != null) {
5407                         restored = "(restored) ";
5408                     }
5409 
5410                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
5411                             + " out of range for " + restored + "text " + mText);
5412                 } else {
5413                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
5414 
5415                     if (ss.frozenWithFocus) {
5416                         createEditorIfNeeded();
5417                         mEditor.mFrozenWithFocus = true;
5418                     }
5419                 }
5420             }
5421         }
5422 
5423         if (ss.error != null) {
5424             final CharSequence error = ss.error;
5425             // Display the error later, after the first layout pass
5426             post(new Runnable() {
5427                 public void run() {
5428                     if (mEditor == null || !mEditor.mErrorWasChanged) {
5429                         setError(error);
5430                     }
5431                 }
5432             });
5433         }
5434 
5435         if (ss.editorState != null) {
5436             createEditorIfNeeded();
5437             mEditor.restoreInstanceState(ss.editorState);
5438         }
5439     }
5440 
5441     /**
5442      * Control whether this text view saves its entire text contents when
5443      * freezing to an icicle, in addition to dynamic state such as cursor
5444      * position.  By default this is false, not saving the text.  Set to true
5445      * if the text in the text view is not being saved somewhere else in
5446      * persistent storage (such as in a content provider) so that if the
5447      * view is later thawed the user will not lose their data. For
5448      * {@link android.widget.EditText} it is always enabled, regardless of
5449      * the value of the attribute.
5450      *
5451      * @param freezesText Controls whether a frozen icicle should include the
5452      * entire text data: true to include it, false to not.
5453      *
5454      * @attr ref android.R.styleable#TextView_freezesText
5455      */
5456     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)5457     public void setFreezesText(boolean freezesText) {
5458         mFreezesText = freezesText;
5459     }
5460 
5461     /**
5462      * Return whether this text view is including its entire text contents
5463      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
5464      *
5465      * @return Returns true if text is included, false if it isn't.
5466      *
5467      * @see #setFreezesText
5468      */
getFreezesText()5469     public boolean getFreezesText() {
5470         return mFreezesText;
5471     }
5472 
5473     ///////////////////////////////////////////////////////////////////////////
5474 
5475     /**
5476      * Sets the Factory used to create new {@link Editable Editables}.
5477      *
5478      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
5479      *
5480      * @see android.text.Editable.Factory
5481      * @see android.widget.TextView.BufferType#EDITABLE
5482      */
setEditableFactory(Editable.Factory factory)5483     public final void setEditableFactory(Editable.Factory factory) {
5484         mEditableFactory = factory;
5485         setText(mText);
5486     }
5487 
5488     /**
5489      * Sets the Factory used to create new {@link Spannable Spannables}.
5490      *
5491      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
5492      *
5493      * @see android.text.Spannable.Factory
5494      * @see android.widget.TextView.BufferType#SPANNABLE
5495      */
setSpannableFactory(Spannable.Factory factory)5496     public final void setSpannableFactory(Spannable.Factory factory) {
5497         mSpannableFactory = factory;
5498         setText(mText);
5499     }
5500 
5501     /**
5502      * Sets the text to be displayed. TextView <em>does not</em> accept
5503      * HTML-like formatting, which you can do with text strings in XML resource files.
5504      * To style your strings, attach android.text.style.* objects to a
5505      * {@link android.text.SpannableString}, or see the
5506      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
5507      * Available Resource Types</a> documentation for an example of setting
5508      * formatted text in the XML resource file.
5509      * <p/>
5510      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5511      * intermediate {@link Spannable Spannables}. Likewise it will use
5512      * {@link android.text.Editable.Factory} to create final or intermediate
5513      * {@link Editable Editables}.
5514      *
5515      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
5516      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
5517      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
5518      *
5519      * @param text text to be displayed
5520      *
5521      * @attr ref android.R.styleable#TextView_text
5522      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
5523      *                                  parameters used to create the PrecomputedText mismatches
5524      *                                  with this TextView.
5525      */
5526     @android.view.RemotableViewMethod
setText(CharSequence text)5527     public final void setText(CharSequence text) {
5528         setText(text, mBufferType);
5529     }
5530 
5531     /**
5532      * Sets the text to be displayed but retains the cursor position. Same as
5533      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
5534      * new text.
5535      * <p/>
5536      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5537      * intermediate {@link Spannable Spannables}. Likewise it will use
5538      * {@link android.text.Editable.Factory} to create final or intermediate
5539      * {@link Editable Editables}.
5540      *
5541      * @param text text to be displayed
5542      *
5543      * @see #setText(CharSequence)
5544      */
5545     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)5546     public final void setTextKeepState(CharSequence text) {
5547         setTextKeepState(text, mBufferType);
5548     }
5549 
5550     /**
5551      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
5552      * <p/>
5553      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5554      * intermediate {@link Spannable Spannables}. Likewise it will use
5555      * {@link android.text.Editable.Factory} to create final or intermediate
5556      * {@link Editable Editables}.
5557      *
5558      * @param text text to be displayed
5559      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5560      *              stored as a static text, styleable/spannable text, or editable text
5561      *
5562      * @see #setText(CharSequence)
5563      * @see android.widget.TextView.BufferType
5564      * @see #setSpannableFactory(Spannable.Factory)
5565      * @see #setEditableFactory(Editable.Factory)
5566      *
5567      * @attr ref android.R.styleable#TextView_text
5568      * @attr ref android.R.styleable#TextView_bufferType
5569      */
setText(CharSequence text, BufferType type)5570     public void setText(CharSequence text, BufferType type) {
5571         setText(text, type, true, 0);
5572 
5573         if (mCharWrapper != null) {
5574             mCharWrapper.mChars = null;
5575         }
5576     }
5577 
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)5578     private void setText(CharSequence text, BufferType type,
5579                          boolean notifyBefore, int oldlen) {
5580         mTextSetFromXmlOrResourceId = false;
5581         if (text == null) {
5582             text = "";
5583         }
5584 
5585         // If suggestions are not enabled, remove the suggestion spans from the text
5586         if (!isSuggestionsEnabled()) {
5587             text = removeSuggestionSpans(text);
5588         }
5589 
5590         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
5591 
5592         if (text instanceof Spanned
5593                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
5594             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
5595                 setHorizontalFadingEdgeEnabled(true);
5596                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
5597             } else {
5598                 setHorizontalFadingEdgeEnabled(false);
5599                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
5600             }
5601             setEllipsize(TextUtils.TruncateAt.MARQUEE);
5602         }
5603 
5604         int n = mFilters.length;
5605         for (int i = 0; i < n; i++) {
5606             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
5607             if (out != null) {
5608                 text = out;
5609             }
5610         }
5611 
5612         if (notifyBefore) {
5613             if (mText != null) {
5614                 oldlen = mText.length();
5615                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
5616             } else {
5617                 sendBeforeTextChanged("", 0, 0, text.length());
5618             }
5619         }
5620 
5621         boolean needEditableForNotification = false;
5622 
5623         if (mListeners != null && mListeners.size() != 0) {
5624             needEditableForNotification = true;
5625         }
5626 
5627         PrecomputedText precomputed =
5628                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
5629         if (type == BufferType.EDITABLE || getKeyListener() != null
5630                 || needEditableForNotification) {
5631             createEditorIfNeeded();
5632             mEditor.forgetUndoRedo();
5633             Editable t = mEditableFactory.newEditable(text);
5634             text = t;
5635             setFilters(t, mFilters);
5636             InputMethodManager imm = InputMethodManager.peekInstance();
5637             if (imm != null) imm.restartInput(this);
5638         } else if (precomputed != null) {
5639             if (mTextDir == null) {
5640                 mTextDir = getTextDirectionHeuristic();
5641             }
5642             if (!precomputed.getParams().isSameTextMetricsInternal(
5643                     getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency)) {
5644                 throw new IllegalArgumentException(
5645                         "PrecomputedText's Parameters don't match the parameters of this TextView."
5646                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
5647                         + "to override the settings of this TextView: "
5648                         + "PrecomputedText: " + precomputed.getParams()
5649                         + "TextView: " + getTextMetricsParams());
5650             }
5651         } else if (type == BufferType.SPANNABLE || mMovement != null) {
5652             text = mSpannableFactory.newSpannable(text);
5653         } else if (!(text instanceof CharWrapper)) {
5654             text = TextUtils.stringOrSpannedString(text);
5655         }
5656 
5657         if (mAutoLinkMask != 0) {
5658             Spannable s2;
5659 
5660             if (type == BufferType.EDITABLE || text instanceof Spannable) {
5661                 s2 = (Spannable) text;
5662             } else {
5663                 s2 = mSpannableFactory.newSpannable(text);
5664             }
5665 
5666             if (Linkify.addLinks(s2, mAutoLinkMask)) {
5667                 text = s2;
5668                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
5669 
5670                 /*
5671                  * We must go ahead and set the text before changing the
5672                  * movement method, because setMovementMethod() may call
5673                  * setText() again to try to upgrade the buffer type.
5674                  */
5675                 setTextInternal(text);
5676 
5677                 // Do not change the movement method for text that support text selection as it
5678                 // would prevent an arbitrary cursor displacement.
5679                 if (mLinksClickable && !textCanBeSelected()) {
5680                     setMovementMethod(LinkMovementMethod.getInstance());
5681                 }
5682             }
5683         }
5684 
5685         mBufferType = type;
5686         setTextInternal(text);
5687 
5688         if (mTransformation == null) {
5689             mTransformed = text;
5690         } else {
5691             mTransformed = mTransformation.getTransformation(text, this);
5692         }
5693 
5694         final int textLength = text.length();
5695 
5696         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
5697             Spannable sp = (Spannable) text;
5698 
5699             // Remove any ChangeWatchers that might have come from other TextViews.
5700             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
5701             final int count = watchers.length;
5702             for (int i = 0; i < count; i++) {
5703                 sp.removeSpan(watchers[i]);
5704             }
5705 
5706             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
5707 
5708             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
5709                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
5710 
5711             if (mEditor != null) mEditor.addSpanWatchers(sp);
5712 
5713             if (mTransformation != null) {
5714                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
5715             }
5716 
5717             if (mMovement != null) {
5718                 mMovement.initialize(this, (Spannable) text);
5719 
5720                 /*
5721                  * Initializing the movement method will have set the
5722                  * selection, so reset mSelectionMoved to keep that from
5723                  * interfering with the normal on-focus selection-setting.
5724                  */
5725                 if (mEditor != null) mEditor.mSelectionMoved = false;
5726             }
5727         }
5728 
5729         if (mLayout != null) {
5730             checkForRelayout();
5731         }
5732 
5733         sendOnTextChanged(text, 0, oldlen, textLength);
5734         onTextChanged(text, 0, oldlen, textLength);
5735 
5736         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
5737 
5738         if (needEditableForNotification) {
5739             sendAfterTextChanged((Editable) text);
5740         } else {
5741             notifyAutoFillManagerAfterTextChangedIfNeeded();
5742         }
5743 
5744         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
5745         if (mEditor != null) mEditor.prepareCursorControllers();
5746     }
5747 
5748     /**
5749      * Sets the TextView to display the specified slice of the specified
5750      * char array. You must promise that you will not change the contents
5751      * of the array except for right before another call to setText(),
5752      * since the TextView has no way to know that the text
5753      * has changed and that it needs to invalidate and re-layout.
5754      *
5755      * @param text char array to be displayed
5756      * @param start start index in the char array
5757      * @param len length of char count after {@code start}
5758      */
setText(char[] text, int start, int len)5759     public final void setText(char[] text, int start, int len) {
5760         int oldlen = 0;
5761 
5762         if (start < 0 || len < 0 || start + len > text.length) {
5763             throw new IndexOutOfBoundsException(start + ", " + len);
5764         }
5765 
5766         /*
5767          * We must do the before-notification here ourselves because if
5768          * the old text is a CharWrapper we destroy it before calling
5769          * into the normal path.
5770          */
5771         if (mText != null) {
5772             oldlen = mText.length();
5773             sendBeforeTextChanged(mText, 0, oldlen, len);
5774         } else {
5775             sendBeforeTextChanged("", 0, 0, len);
5776         }
5777 
5778         if (mCharWrapper == null) {
5779             mCharWrapper = new CharWrapper(text, start, len);
5780         } else {
5781             mCharWrapper.set(text, start, len);
5782         }
5783 
5784         setText(mCharWrapper, mBufferType, false, oldlen);
5785     }
5786 
5787     /**
5788      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
5789      * the cursor position. Same as
5790      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
5791      * position (if any) is retained in the new text.
5792      * <p/>
5793      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5794      * intermediate {@link Spannable Spannables}. Likewise it will use
5795      * {@link android.text.Editable.Factory} to create final or intermediate
5796      * {@link Editable Editables}.
5797      *
5798      * @param text text to be displayed
5799      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5800      *              stored as a static text, styleable/spannable text, or editable text
5801      *
5802      * @see #setText(CharSequence, android.widget.TextView.BufferType)
5803      */
setTextKeepState(CharSequence text, BufferType type)5804     public final void setTextKeepState(CharSequence text, BufferType type) {
5805         int start = getSelectionStart();
5806         int end = getSelectionEnd();
5807         int len = text.length();
5808 
5809         setText(text, type);
5810 
5811         if (start >= 0 || end >= 0) {
5812             if (mSpannable != null) {
5813                 Selection.setSelection(mSpannable,
5814                                        Math.max(0, Math.min(start, len)),
5815                                        Math.max(0, Math.min(end, len)));
5816             }
5817         }
5818     }
5819 
5820     /**
5821      * Sets the text to be displayed using a string resource identifier.
5822      *
5823      * @param resid the resource identifier of the string resource to be displayed
5824      *
5825      * @see #setText(CharSequence)
5826      *
5827      * @attr ref android.R.styleable#TextView_text
5828      */
5829     @android.view.RemotableViewMethod
setText(@tringRes int resid)5830     public final void setText(@StringRes int resid) {
5831         setText(getContext().getResources().getText(resid));
5832         mTextSetFromXmlOrResourceId = true;
5833         mTextId = resid;
5834     }
5835 
5836     /**
5837      * Sets the text to be displayed using a string resource identifier and the
5838      * {@link android.widget.TextView.BufferType}.
5839      * <p/>
5840      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5841      * intermediate {@link Spannable Spannables}. Likewise it will use
5842      * {@link android.text.Editable.Factory} to create final or intermediate
5843      * {@link Editable Editables}.
5844      *
5845      * @param resid the resource identifier of the string resource to be displayed
5846      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5847      *              stored as a static text, styleable/spannable text, or editable text
5848      *
5849      * @see #setText(int)
5850      * @see #setText(CharSequence)
5851      * @see android.widget.TextView.BufferType
5852      * @see #setSpannableFactory(Spannable.Factory)
5853      * @see #setEditableFactory(Editable.Factory)
5854      *
5855      * @attr ref android.R.styleable#TextView_text
5856      * @attr ref android.R.styleable#TextView_bufferType
5857      */
setText(@tringRes int resid, BufferType type)5858     public final void setText(@StringRes int resid, BufferType type) {
5859         setText(getContext().getResources().getText(resid), type);
5860         mTextSetFromXmlOrResourceId = true;
5861         mTextId = resid;
5862     }
5863 
5864     /**
5865      * Sets the text to be displayed when the text of the TextView is empty.
5866      * Null means to use the normal empty text. The hint does not currently
5867      * participate in determining the size of the view.
5868      *
5869      * @attr ref android.R.styleable#TextView_hint
5870      */
5871     @android.view.RemotableViewMethod
setHint(CharSequence hint)5872     public final void setHint(CharSequence hint) {
5873         setHintInternal(hint);
5874 
5875         if (mEditor != null && isInputMethodTarget()) {
5876             mEditor.reportExtractedText();
5877         }
5878     }
5879 
setHintInternal(CharSequence hint)5880     private void setHintInternal(CharSequence hint) {
5881         mHint = TextUtils.stringOrSpannedString(hint);
5882 
5883         if (mLayout != null) {
5884             checkForRelayout();
5885         }
5886 
5887         if (mText.length() == 0) {
5888             invalidate();
5889         }
5890 
5891         // Invalidate display list if hint is currently used
5892         if (mEditor != null && mText.length() == 0 && mHint != null) {
5893             mEditor.invalidateTextDisplayList();
5894         }
5895     }
5896 
5897     /**
5898      * Sets the text to be displayed when the text of the TextView is empty,
5899      * from a resource.
5900      *
5901      * @attr ref android.R.styleable#TextView_hint
5902      */
5903     @android.view.RemotableViewMethod
setHint(@tringRes int resid)5904     public final void setHint(@StringRes int resid) {
5905         setHint(getContext().getResources().getText(resid));
5906     }
5907 
5908     /**
5909      * Returns the hint that is displayed when the text of the TextView
5910      * is empty.
5911      *
5912      * @attr ref android.R.styleable#TextView_hint
5913      */
5914     @ViewDebug.CapturedViewProperty
getHint()5915     public CharSequence getHint() {
5916         return mHint;
5917     }
5918 
isSingleLine()5919     boolean isSingleLine() {
5920         return mSingleLine;
5921     }
5922 
isMultilineInputType(int type)5923     private static boolean isMultilineInputType(int type) {
5924         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
5925                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
5926     }
5927 
5928     /**
5929      * Removes the suggestion spans.
5930      */
removeSuggestionSpans(CharSequence text)5931     CharSequence removeSuggestionSpans(CharSequence text) {
5932         if (text instanceof Spanned) {
5933             Spannable spannable;
5934             if (text instanceof Spannable) {
5935                 spannable = (Spannable) text;
5936             } else {
5937                 spannable = mSpannableFactory.newSpannable(text);
5938             }
5939 
5940             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
5941             if (spans.length == 0) {
5942                 return text;
5943             } else {
5944                 text = spannable;
5945             }
5946 
5947             for (int i = 0; i < spans.length; i++) {
5948                 spannable.removeSpan(spans[i]);
5949             }
5950         }
5951         return text;
5952     }
5953 
5954     /**
5955      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
5956      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
5957      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
5958      * then a soft keyboard will not be displayed for this text view.
5959      *
5960      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
5961      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
5962      * type.
5963      *
5964      * @see #getInputType()
5965      * @see #setRawInputType(int)
5966      * @see android.text.InputType
5967      * @attr ref android.R.styleable#TextView_inputType
5968      */
setInputType(int type)5969     public void setInputType(int type) {
5970         final boolean wasPassword = isPasswordInputType(getInputType());
5971         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
5972         setInputType(type, false);
5973         final boolean isPassword = isPasswordInputType(type);
5974         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
5975         boolean forceUpdate = false;
5976         if (isPassword) {
5977             setTransformationMethod(PasswordTransformationMethod.getInstance());
5978             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
5979                     Typeface.NORMAL, -1 /* weight, not specifeid */);
5980         } else if (isVisiblePassword) {
5981             if (mTransformation == PasswordTransformationMethod.getInstance()) {
5982                 forceUpdate = true;
5983             }
5984             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
5985                     Typeface.NORMAL, -1 /* weight, not specified */);
5986         } else if (wasPassword || wasVisiblePassword) {
5987             // not in password mode, clean up typeface and transformation
5988             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
5989                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
5990                     -1 /* weight, not specified */);
5991             if (mTransformation == PasswordTransformationMethod.getInstance()) {
5992                 forceUpdate = true;
5993             }
5994         }
5995 
5996         boolean singleLine = !isMultilineInputType(type);
5997 
5998         // We need to update the single line mode if it has changed or we
5999         // were previously in password mode.
6000         if (mSingleLine != singleLine || forceUpdate) {
6001             // Change single line mode, but only change the transformation if
6002             // we are not in password mode.
6003             applySingleLine(singleLine, !isPassword, true);
6004         }
6005 
6006         if (!isSuggestionsEnabled()) {
6007             setTextInternal(removeSuggestionSpans(mText));
6008         }
6009 
6010         InputMethodManager imm = InputMethodManager.peekInstance();
6011         if (imm != null) imm.restartInput(this);
6012     }
6013 
6014     /**
6015      * It would be better to rely on the input type for everything. A password inputType should have
6016      * a password transformation. We should hence use isPasswordInputType instead of this method.
6017      *
6018      * We should:
6019      * - Call setInputType in setKeyListener instead of changing the input type directly (which
6020      * would install the correct transformation).
6021      * - Refuse the installation of a non-password transformation in setTransformation if the input
6022      * type is password.
6023      *
6024      * However, this is like this for legacy reasons and we cannot break existing apps. This method
6025      * is useful since it matches what the user can see (obfuscated text or not).
6026      *
6027      * @return true if the current transformation method is of the password type.
6028      */
hasPasswordTransformationMethod()6029     boolean hasPasswordTransformationMethod() {
6030         return mTransformation instanceof PasswordTransformationMethod;
6031     }
6032 
isPasswordInputType(int inputType)6033     static boolean isPasswordInputType(int inputType) {
6034         final int variation =
6035                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6036         return variation
6037                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
6038                 || variation
6039                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
6040                 || variation
6041                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
6042     }
6043 
isVisiblePasswordInputType(int inputType)6044     private static boolean isVisiblePasswordInputType(int inputType) {
6045         final int variation =
6046                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6047         return variation
6048                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
6049     }
6050 
6051     /**
6052      * Directly change the content type integer of the text view, without
6053      * modifying any other state.
6054      * @see #setInputType(int)
6055      * @see android.text.InputType
6056      * @attr ref android.R.styleable#TextView_inputType
6057      */
setRawInputType(int type)6058     public void setRawInputType(int type) {
6059         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
6060         createEditorIfNeeded();
6061         mEditor.mInputType = type;
6062     }
6063 
6064     /**
6065      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
6066      *         a {@code Locale} object that can be used to customize key various listeners.
6067      * @see DateKeyListener#getInstance(Locale)
6068      * @see DateTimeKeyListener#getInstance(Locale)
6069      * @see DigitsKeyListener#getInstance(Locale)
6070      * @see TimeKeyListener#getInstance(Locale)
6071      */
6072     @Nullable
getCustomLocaleForKeyListenerOrNull()6073     private Locale getCustomLocaleForKeyListenerOrNull() {
6074         if (!mUseInternationalizedInput) {
6075             // If the application does not target O, stick to the previous behavior.
6076             return null;
6077         }
6078         final LocaleList locales = getImeHintLocales();
6079         if (locales == null) {
6080             // If the application does not explicitly specify IME hint locale, also stick to the
6081             // previous behavior.
6082             return null;
6083         }
6084         return locales.get(0);
6085     }
6086 
setInputType(int type, boolean direct)6087     private void setInputType(int type, boolean direct) {
6088         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
6089         KeyListener input;
6090         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
6091             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
6092             TextKeyListener.Capitalize cap;
6093             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
6094                 cap = TextKeyListener.Capitalize.CHARACTERS;
6095             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
6096                 cap = TextKeyListener.Capitalize.WORDS;
6097             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
6098                 cap = TextKeyListener.Capitalize.SENTENCES;
6099             } else {
6100                 cap = TextKeyListener.Capitalize.NONE;
6101             }
6102             input = TextKeyListener.getInstance(autotext, cap);
6103         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
6104             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6105             input = DigitsKeyListener.getInstance(
6106                     locale,
6107                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
6108                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
6109             if (locale != null) {
6110                 // Override type, if necessary for i18n.
6111                 int newType = input.getInputType();
6112                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
6113                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
6114                     // The class is different from the original class. So we need to override
6115                     // 'type'. But we want to keep the password flag if it's there.
6116                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
6117                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
6118                     }
6119                     type = newType;
6120                 }
6121             }
6122         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
6123             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6124             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
6125                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
6126                     input = DateKeyListener.getInstance(locale);
6127                     break;
6128                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
6129                     input = TimeKeyListener.getInstance(locale);
6130                     break;
6131                 default:
6132                     input = DateTimeKeyListener.getInstance(locale);
6133                     break;
6134             }
6135             if (mUseInternationalizedInput) {
6136                 type = input.getInputType(); // Override type, if necessary for i18n.
6137             }
6138         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
6139             input = DialerKeyListener.getInstance();
6140         } else {
6141             input = TextKeyListener.getInstance();
6142         }
6143         setRawInputType(type);
6144         mListenerChanged = false;
6145         if (direct) {
6146             createEditorIfNeeded();
6147             mEditor.mKeyListener = input;
6148         } else {
6149             setKeyListenerOnly(input);
6150         }
6151     }
6152 
6153     /**
6154      * Get the type of the editable content.
6155      *
6156      * @see #setInputType(int)
6157      * @see android.text.InputType
6158      */
getInputType()6159     public int getInputType() {
6160         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
6161     }
6162 
6163     /**
6164      * Change the editor type integer associated with the text view, which
6165      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
6166      * when it has focus.
6167      * @see #getImeOptions
6168      * @see android.view.inputmethod.EditorInfo
6169      * @attr ref android.R.styleable#TextView_imeOptions
6170      */
setImeOptions(int imeOptions)6171     public void setImeOptions(int imeOptions) {
6172         createEditorIfNeeded();
6173         mEditor.createInputContentTypeIfNeeded();
6174         mEditor.mInputContentType.imeOptions = imeOptions;
6175     }
6176 
6177     /**
6178      * Get the type of the Input Method Editor (IME).
6179      * @return the type of the IME
6180      * @see #setImeOptions(int)
6181      * @see android.view.inputmethod.EditorInfo
6182      */
getImeOptions()6183     public int getImeOptions() {
6184         return mEditor != null && mEditor.mInputContentType != null
6185                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
6186     }
6187 
6188     /**
6189      * Change the custom IME action associated with the text view, which
6190      * will be reported to an IME with {@link EditorInfo#actionLabel}
6191      * and {@link EditorInfo#actionId} when it has focus.
6192      * @see #getImeActionLabel
6193      * @see #getImeActionId
6194      * @see android.view.inputmethod.EditorInfo
6195      * @attr ref android.R.styleable#TextView_imeActionLabel
6196      * @attr ref android.R.styleable#TextView_imeActionId
6197      */
setImeActionLabel(CharSequence label, int actionId)6198     public void setImeActionLabel(CharSequence label, int actionId) {
6199         createEditorIfNeeded();
6200         mEditor.createInputContentTypeIfNeeded();
6201         mEditor.mInputContentType.imeActionLabel = label;
6202         mEditor.mInputContentType.imeActionId = actionId;
6203     }
6204 
6205     /**
6206      * Get the IME action label previous set with {@link #setImeActionLabel}.
6207      *
6208      * @see #setImeActionLabel
6209      * @see android.view.inputmethod.EditorInfo
6210      */
getImeActionLabel()6211     public CharSequence getImeActionLabel() {
6212         return mEditor != null && mEditor.mInputContentType != null
6213                 ? mEditor.mInputContentType.imeActionLabel : null;
6214     }
6215 
6216     /**
6217      * Get the IME action ID previous set with {@link #setImeActionLabel}.
6218      *
6219      * @see #setImeActionLabel
6220      * @see android.view.inputmethod.EditorInfo
6221      */
getImeActionId()6222     public int getImeActionId() {
6223         return mEditor != null && mEditor.mInputContentType != null
6224                 ? mEditor.mInputContentType.imeActionId : 0;
6225     }
6226 
6227     /**
6228      * Set a special listener to be called when an action is performed
6229      * on the text view.  This will be called when the enter key is pressed,
6230      * or when an action supplied to the IME is selected by the user.  Setting
6231      * this means that the normal hard key event will not insert a newline
6232      * into the text view, even if it is multi-line; holding down the ALT
6233      * modifier will, however, allow the user to insert a newline character.
6234      */
setOnEditorActionListener(OnEditorActionListener l)6235     public void setOnEditorActionListener(OnEditorActionListener l) {
6236         createEditorIfNeeded();
6237         mEditor.createInputContentTypeIfNeeded();
6238         mEditor.mInputContentType.onEditorActionListener = l;
6239     }
6240 
6241     /**
6242      * Called when an attached input method calls
6243      * {@link InputConnection#performEditorAction(int)
6244      * InputConnection.performEditorAction()}
6245      * for this text view.  The default implementation will call your action
6246      * listener supplied to {@link #setOnEditorActionListener}, or perform
6247      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
6248      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
6249      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
6250      * EditorInfo.IME_ACTION_DONE}.
6251      *
6252      * <p>For backwards compatibility, if no IME options have been set and the
6253      * text view would not normally advance focus on enter, then
6254      * the NEXT and DONE actions received here will be turned into an enter
6255      * key down/up pair to go through the normal key handling.
6256      *
6257      * @param actionCode The code of the action being performed.
6258      *
6259      * @see #setOnEditorActionListener
6260      */
onEditorAction(int actionCode)6261     public void onEditorAction(int actionCode) {
6262         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
6263         if (ict != null) {
6264             if (ict.onEditorActionListener != null) {
6265                 if (ict.onEditorActionListener.onEditorAction(this,
6266                         actionCode, null)) {
6267                     return;
6268                 }
6269             }
6270 
6271             // This is the handling for some default action.
6272             // Note that for backwards compatibility we don't do this
6273             // default handling if explicit ime options have not been given,
6274             // instead turning this into the normal enter key codes that an
6275             // app may be expecting.
6276             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
6277                 View v = focusSearch(FOCUS_FORWARD);
6278                 if (v != null) {
6279                     if (!v.requestFocus(FOCUS_FORWARD)) {
6280                         throw new IllegalStateException("focus search returned a view "
6281                                 + "that wasn't able to take focus!");
6282                     }
6283                 }
6284                 return;
6285 
6286             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
6287                 View v = focusSearch(FOCUS_BACKWARD);
6288                 if (v != null) {
6289                     if (!v.requestFocus(FOCUS_BACKWARD)) {
6290                         throw new IllegalStateException("focus search returned a view "
6291                                 + "that wasn't able to take focus!");
6292                     }
6293                 }
6294                 return;
6295 
6296             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
6297                 InputMethodManager imm = InputMethodManager.peekInstance();
6298                 if (imm != null && imm.isActive(this)) {
6299                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
6300                 }
6301                 return;
6302             }
6303         }
6304 
6305         ViewRootImpl viewRootImpl = getViewRootImpl();
6306         if (viewRootImpl != null) {
6307             long eventTime = SystemClock.uptimeMillis();
6308             viewRootImpl.dispatchKeyFromIme(
6309                     new KeyEvent(eventTime, eventTime,
6310                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
6311                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
6312                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
6313                     | KeyEvent.FLAG_EDITOR_ACTION));
6314             viewRootImpl.dispatchKeyFromIme(
6315                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
6316                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
6317                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
6318                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
6319                     | KeyEvent.FLAG_EDITOR_ACTION));
6320         }
6321     }
6322 
6323     /**
6324      * Set the private content type of the text, which is the
6325      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
6326      * field that will be filled in when creating an input connection.
6327      *
6328      * @see #getPrivateImeOptions()
6329      * @see EditorInfo#privateImeOptions
6330      * @attr ref android.R.styleable#TextView_privateImeOptions
6331      */
setPrivateImeOptions(String type)6332     public void setPrivateImeOptions(String type) {
6333         createEditorIfNeeded();
6334         mEditor.createInputContentTypeIfNeeded();
6335         mEditor.mInputContentType.privateImeOptions = type;
6336     }
6337 
6338     /**
6339      * Get the private type of the content.
6340      *
6341      * @see #setPrivateImeOptions(String)
6342      * @see EditorInfo#privateImeOptions
6343      */
getPrivateImeOptions()6344     public String getPrivateImeOptions() {
6345         return mEditor != null && mEditor.mInputContentType != null
6346                 ? mEditor.mInputContentType.privateImeOptions : null;
6347     }
6348 
6349     /**
6350      * Set the extra input data of the text, which is the
6351      * {@link EditorInfo#extras TextBoxAttribute.extras}
6352      * Bundle that will be filled in when creating an input connection.  The
6353      * given integer is the resource identifier of an XML resource holding an
6354      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
6355      *
6356      * @see #getInputExtras(boolean)
6357      * @see EditorInfo#extras
6358      * @attr ref android.R.styleable#TextView_editorExtras
6359      */
setInputExtras(@mlRes int xmlResId)6360     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
6361         createEditorIfNeeded();
6362         XmlResourceParser parser = getResources().getXml(xmlResId);
6363         mEditor.createInputContentTypeIfNeeded();
6364         mEditor.mInputContentType.extras = new Bundle();
6365         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
6366     }
6367 
6368     /**
6369      * Retrieve the input extras currently associated with the text view, which
6370      * can be viewed as well as modified.
6371      *
6372      * @param create If true, the extras will be created if they don't already
6373      * exist.  Otherwise, null will be returned if none have been created.
6374      * @see #setInputExtras(int)
6375      * @see EditorInfo#extras
6376      * @attr ref android.R.styleable#TextView_editorExtras
6377      */
getInputExtras(boolean create)6378     public Bundle getInputExtras(boolean create) {
6379         if (mEditor == null && !create) return null;
6380         createEditorIfNeeded();
6381         if (mEditor.mInputContentType == null) {
6382             if (!create) return null;
6383             mEditor.createInputContentTypeIfNeeded();
6384         }
6385         if (mEditor.mInputContentType.extras == null) {
6386             if (!create) return null;
6387             mEditor.mInputContentType.extras = new Bundle();
6388         }
6389         return mEditor.mInputContentType.extras;
6390     }
6391 
6392     /**
6393      * Change "hint" locales associated with the text view, which will be reported to an IME with
6394      * {@link EditorInfo#hintLocales} when it has focus.
6395      *
6396      * Starting with Android O, this also causes internationalized listeners to be created (or
6397      * change locale) based on the first locale in the input locale list.
6398      *
6399      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
6400      * call {@link InputMethodManager#restartInput(View)}.</p>
6401      * @param hintLocales List of the languages that the user is supposed to switch to no matter
6402      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
6403      * @see #getImeHintLocales()
6404      * @see android.view.inputmethod.EditorInfo#hintLocales
6405      */
setImeHintLocales(@ullable LocaleList hintLocales)6406     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
6407         createEditorIfNeeded();
6408         mEditor.createInputContentTypeIfNeeded();
6409         mEditor.mInputContentType.imeHintLocales = hintLocales;
6410         if (mUseInternationalizedInput) {
6411             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
6412         }
6413     }
6414 
6415     /**
6416      * @return The current languages list "hint". {@code null} when no "hint" is available.
6417      * @see #setImeHintLocales(LocaleList)
6418      * @see android.view.inputmethod.EditorInfo#hintLocales
6419      */
6420     @Nullable
getImeHintLocales()6421     public LocaleList getImeHintLocales() {
6422         if (mEditor == null) {
6423             return null;
6424         }
6425         if (mEditor.mInputContentType == null) {
6426             return null;
6427         }
6428         return mEditor.mInputContentType.imeHintLocales;
6429     }
6430 
6431     /**
6432      * Returns the error message that was set to be displayed with
6433      * {@link #setError}, or <code>null</code> if no error was set
6434      * or if it the error was cleared by the widget after user input.
6435      */
getError()6436     public CharSequence getError() {
6437         return mEditor == null ? null : mEditor.mError;
6438     }
6439 
6440     /**
6441      * Sets the right-hand compound drawable of the TextView to the "error"
6442      * icon and sets an error message that will be displayed in a popup when
6443      * the TextView has focus.  The icon and error message will be reset to
6444      * null when any key events cause changes to the TextView's text.  If the
6445      * <code>error</code> is <code>null</code>, the error message and icon
6446      * will be cleared.
6447      */
6448     @android.view.RemotableViewMethod
setError(CharSequence error)6449     public void setError(CharSequence error) {
6450         if (error == null) {
6451             setError(null, null);
6452         } else {
6453             Drawable dr = getContext().getDrawable(
6454                     com.android.internal.R.drawable.indicator_input_error);
6455 
6456             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
6457             setError(error, dr);
6458         }
6459     }
6460 
6461     /**
6462      * Sets the right-hand compound drawable of the TextView to the specified
6463      * icon and sets an error message that will be displayed in a popup when
6464      * the TextView has focus.  The icon and error message will be reset to
6465      * null when any key events cause changes to the TextView's text.  The
6466      * drawable must already have had {@link Drawable#setBounds} set on it.
6467      * If the <code>error</code> is <code>null</code>, the error message will
6468      * be cleared (and you should provide a <code>null</code> icon as well).
6469      */
setError(CharSequence error, Drawable icon)6470     public void setError(CharSequence error, Drawable icon) {
6471         createEditorIfNeeded();
6472         mEditor.setError(error, icon);
6473         notifyViewAccessibilityStateChangedIfNeeded(
6474                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
6475     }
6476 
6477     @Override
setFrame(int l, int t, int r, int b)6478     protected boolean setFrame(int l, int t, int r, int b) {
6479         boolean result = super.setFrame(l, t, r, b);
6480 
6481         if (mEditor != null) mEditor.setFrame();
6482 
6483         restartMarqueeIfNeeded();
6484 
6485         return result;
6486     }
6487 
restartMarqueeIfNeeded()6488     private void restartMarqueeIfNeeded() {
6489         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6490             mRestartMarquee = false;
6491             startMarquee();
6492         }
6493     }
6494 
6495     /**
6496      * Sets the list of input filters that will be used if the buffer is
6497      * Editable. Has no effect otherwise.
6498      *
6499      * @attr ref android.R.styleable#TextView_maxLength
6500      */
setFilters(InputFilter[] filters)6501     public void setFilters(InputFilter[] filters) {
6502         if (filters == null) {
6503             throw new IllegalArgumentException();
6504         }
6505 
6506         mFilters = filters;
6507 
6508         if (mText instanceof Editable) {
6509             setFilters((Editable) mText, filters);
6510         }
6511     }
6512 
6513     /**
6514      * Sets the list of input filters on the specified Editable,
6515      * and includes mInput in the list if it is an InputFilter.
6516      */
setFilters(Editable e, InputFilter[] filters)6517     private void setFilters(Editable e, InputFilter[] filters) {
6518         if (mEditor != null) {
6519             final boolean undoFilter = mEditor.mUndoInputFilter != null;
6520             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
6521             int num = 0;
6522             if (undoFilter) num++;
6523             if (keyFilter) num++;
6524             if (num > 0) {
6525                 InputFilter[] nf = new InputFilter[filters.length + num];
6526 
6527                 System.arraycopy(filters, 0, nf, 0, filters.length);
6528                 num = 0;
6529                 if (undoFilter) {
6530                     nf[filters.length] = mEditor.mUndoInputFilter;
6531                     num++;
6532                 }
6533                 if (keyFilter) {
6534                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
6535                 }
6536 
6537                 e.setFilters(nf);
6538                 return;
6539             }
6540         }
6541         e.setFilters(filters);
6542     }
6543 
6544     /**
6545      * Returns the current list of input filters.
6546      *
6547      * @attr ref android.R.styleable#TextView_maxLength
6548      */
getFilters()6549     public InputFilter[] getFilters() {
6550         return mFilters;
6551     }
6552 
6553     /////////////////////////////////////////////////////////////////////////
6554 
getBoxHeight(Layout l)6555     private int getBoxHeight(Layout l) {
6556         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
6557         int padding = (l == mHintLayout)
6558                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
6559                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
6560         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
6561     }
6562 
getVerticalOffset(boolean forceNormal)6563     int getVerticalOffset(boolean forceNormal) {
6564         int voffset = 0;
6565         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
6566 
6567         Layout l = mLayout;
6568         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
6569             l = mHintLayout;
6570         }
6571 
6572         if (gravity != Gravity.TOP) {
6573             int boxht = getBoxHeight(l);
6574             int textht = l.getHeight();
6575 
6576             if (textht < boxht) {
6577                 if (gravity == Gravity.BOTTOM) {
6578                     voffset = boxht - textht;
6579                 } else { // (gravity == Gravity.CENTER_VERTICAL)
6580                     voffset = (boxht - textht) >> 1;
6581                 }
6582             }
6583         }
6584         return voffset;
6585     }
6586 
getBottomVerticalOffset(boolean forceNormal)6587     private int getBottomVerticalOffset(boolean forceNormal) {
6588         int voffset = 0;
6589         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
6590 
6591         Layout l = mLayout;
6592         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
6593             l = mHintLayout;
6594         }
6595 
6596         if (gravity != Gravity.BOTTOM) {
6597             int boxht = getBoxHeight(l);
6598             int textht = l.getHeight();
6599 
6600             if (textht < boxht) {
6601                 if (gravity == Gravity.TOP) {
6602                     voffset = boxht - textht;
6603                 } else { // (gravity == Gravity.CENTER_VERTICAL)
6604                     voffset = (boxht - textht) >> 1;
6605                 }
6606             }
6607         }
6608         return voffset;
6609     }
6610 
invalidateCursorPath()6611     void invalidateCursorPath() {
6612         if (mHighlightPathBogus) {
6613             invalidateCursor();
6614         } else {
6615             final int horizontalPadding = getCompoundPaddingLeft();
6616             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6617 
6618             if (mEditor.mDrawableForCursor == null) {
6619                 synchronized (TEMP_RECTF) {
6620                     /*
6621                      * The reason for this concern about the thickness of the
6622                      * cursor and doing the floor/ceil on the coordinates is that
6623                      * some EditTexts (notably textfields in the Browser) have
6624                      * anti-aliased text where not all the characters are
6625                      * necessarily at integer-multiple locations.  This should
6626                      * make sure the entire cursor gets invalidated instead of
6627                      * sometimes missing half a pixel.
6628                      */
6629                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
6630                     if (thick < 1.0f) {
6631                         thick = 1.0f;
6632                     }
6633 
6634                     thick /= 2.0f;
6635 
6636                     // mHighlightPath is guaranteed to be non null at that point.
6637                     mHighlightPath.computeBounds(TEMP_RECTF, false);
6638 
6639                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
6640                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
6641                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
6642                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
6643                 }
6644             } else {
6645                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
6646                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
6647                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
6648             }
6649         }
6650     }
6651 
invalidateCursor()6652     void invalidateCursor() {
6653         int where = getSelectionEnd();
6654 
6655         invalidateCursor(where, where, where);
6656     }
6657 
invalidateCursor(int a, int b, int c)6658     private void invalidateCursor(int a, int b, int c) {
6659         if (a >= 0 || b >= 0 || c >= 0) {
6660             int start = Math.min(Math.min(a, b), c);
6661             int end = Math.max(Math.max(a, b), c);
6662             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
6663         }
6664     }
6665 
6666     /**
6667      * Invalidates the region of text enclosed between the start and end text offsets.
6668      */
invalidateRegion(int start, int end, boolean invalidateCursor)6669     void invalidateRegion(int start, int end, boolean invalidateCursor) {
6670         if (mLayout == null) {
6671             invalidate();
6672         } else {
6673             int lineStart = mLayout.getLineForOffset(start);
6674             int top = mLayout.getLineTop(lineStart);
6675 
6676             // This is ridiculous, but the descent from the line above
6677             // can hang down into the line we really want to redraw,
6678             // so we have to invalidate part of the line above to make
6679             // sure everything that needs to be redrawn really is.
6680             // (But not the whole line above, because that would cause
6681             // the same problem with the descenders on the line above it!)
6682             if (lineStart > 0) {
6683                 top -= mLayout.getLineDescent(lineStart - 1);
6684             }
6685 
6686             int lineEnd;
6687 
6688             if (start == end) {
6689                 lineEnd = lineStart;
6690             } else {
6691                 lineEnd = mLayout.getLineForOffset(end);
6692             }
6693 
6694             int bottom = mLayout.getLineBottom(lineEnd);
6695 
6696             // mEditor can be null in case selection is set programmatically.
6697             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
6698                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
6699                 top = Math.min(top, bounds.top);
6700                 bottom = Math.max(bottom, bounds.bottom);
6701             }
6702 
6703             final int compoundPaddingLeft = getCompoundPaddingLeft();
6704             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6705 
6706             int left, right;
6707             if (lineStart == lineEnd && !invalidateCursor) {
6708                 left = (int) mLayout.getPrimaryHorizontal(start);
6709                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
6710                 left += compoundPaddingLeft;
6711                 right += compoundPaddingLeft;
6712             } else {
6713                 // Rectangle bounding box when the region spans several lines
6714                 left = compoundPaddingLeft;
6715                 right = getWidth() - getCompoundPaddingRight();
6716             }
6717 
6718             invalidate(mScrollX + left, verticalPadding + top,
6719                     mScrollX + right, verticalPadding + bottom);
6720         }
6721     }
6722 
registerForPreDraw()6723     private void registerForPreDraw() {
6724         if (!mPreDrawRegistered) {
6725             getViewTreeObserver().addOnPreDrawListener(this);
6726             mPreDrawRegistered = true;
6727         }
6728     }
6729 
unregisterForPreDraw()6730     private void unregisterForPreDraw() {
6731         getViewTreeObserver().removeOnPreDrawListener(this);
6732         mPreDrawRegistered = false;
6733         mPreDrawListenerDetached = false;
6734     }
6735 
6736     /**
6737      * {@inheritDoc}
6738      */
6739     @Override
onPreDraw()6740     public boolean onPreDraw() {
6741         if (mLayout == null) {
6742             assumeLayout();
6743         }
6744 
6745         if (mMovement != null) {
6746             /* This code also provides auto-scrolling when a cursor is moved using a
6747              * CursorController (insertion point or selection limits).
6748              * For selection, ensure start or end is visible depending on controller's state.
6749              */
6750             int curs = getSelectionEnd();
6751             // Do not create the controller if it is not already created.
6752             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
6753                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
6754                 curs = getSelectionStart();
6755             }
6756 
6757             /*
6758              * TODO: This should really only keep the end in view if
6759              * it already was before the text changed.  I'm not sure
6760              * of a good way to tell from here if it was.
6761              */
6762             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6763                 curs = mText.length();
6764             }
6765 
6766             if (curs >= 0) {
6767                 bringPointIntoView(curs);
6768             }
6769         } else {
6770             bringTextIntoView();
6771         }
6772 
6773         // This has to be checked here since:
6774         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
6775         //   a screen rotation) since layout is not yet initialized at that point.
6776         if (mEditor != null && mEditor.mCreatedWithASelection) {
6777             mEditor.refreshTextActionMode();
6778             mEditor.mCreatedWithASelection = false;
6779         }
6780 
6781         unregisterForPreDraw();
6782 
6783         return true;
6784     }
6785 
6786     @Override
onAttachedToWindow()6787     protected void onAttachedToWindow() {
6788         super.onAttachedToWindow();
6789 
6790         if (mEditor != null) mEditor.onAttachedToWindow();
6791 
6792         if (mPreDrawListenerDetached) {
6793             getViewTreeObserver().addOnPreDrawListener(this);
6794             mPreDrawListenerDetached = false;
6795         }
6796     }
6797 
6798     /** @hide */
6799     @Override
onDetachedFromWindowInternal()6800     protected void onDetachedFromWindowInternal() {
6801         if (mPreDrawRegistered) {
6802             getViewTreeObserver().removeOnPreDrawListener(this);
6803             mPreDrawListenerDetached = true;
6804         }
6805 
6806         resetResolvedDrawables();
6807 
6808         if (mEditor != null) mEditor.onDetachedFromWindow();
6809 
6810         super.onDetachedFromWindowInternal();
6811     }
6812 
6813     @Override
onScreenStateChanged(int screenState)6814     public void onScreenStateChanged(int screenState) {
6815         super.onScreenStateChanged(screenState);
6816         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
6817     }
6818 
6819     @Override
isPaddingOffsetRequired()6820     protected boolean isPaddingOffsetRequired() {
6821         return mShadowRadius != 0 || mDrawables != null;
6822     }
6823 
6824     @Override
getLeftPaddingOffset()6825     protected int getLeftPaddingOffset() {
6826         return getCompoundPaddingLeft() - mPaddingLeft
6827                 + (int) Math.min(0, mShadowDx - mShadowRadius);
6828     }
6829 
6830     @Override
getTopPaddingOffset()6831     protected int getTopPaddingOffset() {
6832         return (int) Math.min(0, mShadowDy - mShadowRadius);
6833     }
6834 
6835     @Override
getBottomPaddingOffset()6836     protected int getBottomPaddingOffset() {
6837         return (int) Math.max(0, mShadowDy + mShadowRadius);
6838     }
6839 
6840     @Override
getRightPaddingOffset()6841     protected int getRightPaddingOffset() {
6842         return -(getCompoundPaddingRight() - mPaddingRight)
6843                 + (int) Math.max(0, mShadowDx + mShadowRadius);
6844     }
6845 
6846     @Override
verifyDrawable(@onNull Drawable who)6847     protected boolean verifyDrawable(@NonNull Drawable who) {
6848         final boolean verified = super.verifyDrawable(who);
6849         if (!verified && mDrawables != null) {
6850             for (Drawable dr : mDrawables.mShowing) {
6851                 if (who == dr) {
6852                     return true;
6853                 }
6854             }
6855         }
6856         return verified;
6857     }
6858 
6859     @Override
jumpDrawablesToCurrentState()6860     public void jumpDrawablesToCurrentState() {
6861         super.jumpDrawablesToCurrentState();
6862         if (mDrawables != null) {
6863             for (Drawable dr : mDrawables.mShowing) {
6864                 if (dr != null) {
6865                     dr.jumpToCurrentState();
6866                 }
6867             }
6868         }
6869     }
6870 
6871     @Override
invalidateDrawable(@onNull Drawable drawable)6872     public void invalidateDrawable(@NonNull Drawable drawable) {
6873         boolean handled = false;
6874 
6875         if (verifyDrawable(drawable)) {
6876             final Rect dirty = drawable.getBounds();
6877             int scrollX = mScrollX;
6878             int scrollY = mScrollY;
6879 
6880             // IMPORTANT: The coordinates below are based on the coordinates computed
6881             // for each compound drawable in onDraw(). Make sure to update each section
6882             // accordingly.
6883             final TextView.Drawables drawables = mDrawables;
6884             if (drawables != null) {
6885                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
6886                     final int compoundPaddingTop = getCompoundPaddingTop();
6887                     final int compoundPaddingBottom = getCompoundPaddingBottom();
6888                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6889 
6890                     scrollX += mPaddingLeft;
6891                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
6892                     handled = true;
6893                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
6894                     final int compoundPaddingTop = getCompoundPaddingTop();
6895                     final int compoundPaddingBottom = getCompoundPaddingBottom();
6896                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6897 
6898                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
6899                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
6900                     handled = true;
6901                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
6902                     final int compoundPaddingLeft = getCompoundPaddingLeft();
6903                     final int compoundPaddingRight = getCompoundPaddingRight();
6904                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6905 
6906                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
6907                     scrollY += mPaddingTop;
6908                     handled = true;
6909                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
6910                     final int compoundPaddingLeft = getCompoundPaddingLeft();
6911                     final int compoundPaddingRight = getCompoundPaddingRight();
6912                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6913 
6914                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
6915                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
6916                     handled = true;
6917                 }
6918             }
6919 
6920             if (handled) {
6921                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
6922                         dirty.right + scrollX, dirty.bottom + scrollY);
6923             }
6924         }
6925 
6926         if (!handled) {
6927             super.invalidateDrawable(drawable);
6928         }
6929     }
6930 
6931     @Override
hasOverlappingRendering()6932     public boolean hasOverlappingRendering() {
6933         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
6934         return ((getBackground() != null && getBackground().getCurrent() != null)
6935                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled());
6936     }
6937 
6938     /**
6939      *
6940      * Returns the state of the {@code textIsSelectable} flag (See
6941      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
6942      * to allow users to select and copy text in a non-editable TextView, the content of an
6943      * {@link EditText} can always be selected, independently of the value of this flag.
6944      * <p>
6945      *
6946      * @return True if the text displayed in this TextView can be selected by the user.
6947      *
6948      * @attr ref android.R.styleable#TextView_textIsSelectable
6949      */
isTextSelectable()6950     public boolean isTextSelectable() {
6951         return mEditor == null ? false : mEditor.mTextIsSelectable;
6952     }
6953 
6954     /**
6955      * Sets whether the content of this view is selectable by the user. The default is
6956      * {@code false}, meaning that the content is not selectable.
6957      * <p>
6958      * When you use a TextView to display a useful piece of information to the user (such as a
6959      * contact's address), make it selectable, so that the user can select and copy its
6960      * content. You can also use set the XML attribute
6961      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
6962      * <p>
6963      * When you call this method to set the value of {@code textIsSelectable}, it sets
6964      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
6965      * and {@code longClickable} to the same value. These flags correspond to the attributes
6966      * {@link android.R.styleable#View_focusable android:focusable},
6967      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
6968      * {@link android.R.styleable#View_clickable android:clickable}, and
6969      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
6970      * flags to a state you had set previously, call one or more of the following methods:
6971      * {@link #setFocusable(boolean) setFocusable()},
6972      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
6973      * {@link #setClickable(boolean) setClickable()} or
6974      * {@link #setLongClickable(boolean) setLongClickable()}.
6975      *
6976      * @param selectable Whether the content of this TextView should be selectable.
6977      */
setTextIsSelectable(boolean selectable)6978     public void setTextIsSelectable(boolean selectable) {
6979         if (!selectable && mEditor == null) return; // false is default value with no edit data
6980 
6981         createEditorIfNeeded();
6982         if (mEditor.mTextIsSelectable == selectable) return;
6983 
6984         mEditor.mTextIsSelectable = selectable;
6985         setFocusableInTouchMode(selectable);
6986         setFocusable(FOCUSABLE_AUTO);
6987         setClickable(selectable);
6988         setLongClickable(selectable);
6989 
6990         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
6991 
6992         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
6993         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
6994 
6995         // Called by setText above, but safer in case of future code changes
6996         mEditor.prepareCursorControllers();
6997     }
6998 
6999     @Override
onCreateDrawableState(int extraSpace)7000     protected int[] onCreateDrawableState(int extraSpace) {
7001         final int[] drawableState;
7002 
7003         if (mSingleLine) {
7004             drawableState = super.onCreateDrawableState(extraSpace);
7005         } else {
7006             drawableState = super.onCreateDrawableState(extraSpace + 1);
7007             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
7008         }
7009 
7010         if (isTextSelectable()) {
7011             // Disable pressed state, which was introduced when TextView was made clickable.
7012             // Prevents text color change.
7013             // setClickable(false) would have a similar effect, but it also disables focus changes
7014             // and long press actions, which are both needed by text selection.
7015             final int length = drawableState.length;
7016             for (int i = 0; i < length; i++) {
7017                 if (drawableState[i] == R.attr.state_pressed) {
7018                     final int[] nonPressedState = new int[length - 1];
7019                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
7020                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
7021                     return nonPressedState;
7022                 }
7023             }
7024         }
7025 
7026         return drawableState;
7027     }
7028 
getUpdatedHighlightPath()7029     private Path getUpdatedHighlightPath() {
7030         Path highlight = null;
7031         Paint highlightPaint = mHighlightPaint;
7032 
7033         final int selStart = getSelectionStart();
7034         final int selEnd = getSelectionEnd();
7035         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
7036             if (selStart == selEnd) {
7037                 if (mEditor != null && mEditor.shouldRenderCursor()) {
7038                     if (mHighlightPathBogus) {
7039                         if (mHighlightPath == null) mHighlightPath = new Path();
7040                         mHighlightPath.reset();
7041                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
7042                         mEditor.updateCursorPosition();
7043                         mHighlightPathBogus = false;
7044                     }
7045 
7046                     // XXX should pass to skin instead of drawing directly
7047                     highlightPaint.setColor(mCurTextColor);
7048                     highlightPaint.setStyle(Paint.Style.STROKE);
7049                     highlight = mHighlightPath;
7050                 }
7051             } else {
7052                 if (mHighlightPathBogus) {
7053                     if (mHighlightPath == null) mHighlightPath = new Path();
7054                     mHighlightPath.reset();
7055                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7056                     mHighlightPathBogus = false;
7057                 }
7058 
7059                 // XXX should pass to skin instead of drawing directly
7060                 highlightPaint.setColor(mHighlightColor);
7061                 highlightPaint.setStyle(Paint.Style.FILL);
7062 
7063                 highlight = mHighlightPath;
7064             }
7065         }
7066         return highlight;
7067     }
7068 
7069     /**
7070      * @hide
7071      */
getHorizontalOffsetForDrawables()7072     public int getHorizontalOffsetForDrawables() {
7073         return 0;
7074     }
7075 
7076     @Override
onDraw(Canvas canvas)7077     protected void onDraw(Canvas canvas) {
7078         restartMarqueeIfNeeded();
7079 
7080         // Draw the background for this view
7081         super.onDraw(canvas);
7082 
7083         final int compoundPaddingLeft = getCompoundPaddingLeft();
7084         final int compoundPaddingTop = getCompoundPaddingTop();
7085         final int compoundPaddingRight = getCompoundPaddingRight();
7086         final int compoundPaddingBottom = getCompoundPaddingBottom();
7087         final int scrollX = mScrollX;
7088         final int scrollY = mScrollY;
7089         final int right = mRight;
7090         final int left = mLeft;
7091         final int bottom = mBottom;
7092         final int top = mTop;
7093         final boolean isLayoutRtl = isLayoutRtl();
7094         final int offset = getHorizontalOffsetForDrawables();
7095         final int leftOffset = isLayoutRtl ? 0 : offset;
7096         final int rightOffset = isLayoutRtl ? offset : 0;
7097 
7098         final Drawables dr = mDrawables;
7099         if (dr != null) {
7100             /*
7101              * Compound, not extended, because the icon is not clipped
7102              * if the text height is smaller.
7103              */
7104 
7105             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
7106             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
7107 
7108             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7109             // Make sure to update invalidateDrawable() when changing this code.
7110             if (dr.mShowing[Drawables.LEFT] != null) {
7111                 canvas.save();
7112                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
7113                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
7114                 dr.mShowing[Drawables.LEFT].draw(canvas);
7115                 canvas.restore();
7116             }
7117 
7118             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7119             // Make sure to update invalidateDrawable() when changing this code.
7120             if (dr.mShowing[Drawables.RIGHT] != null) {
7121                 canvas.save();
7122                 canvas.translate(scrollX + right - left - mPaddingRight
7123                         - dr.mDrawableSizeRight - rightOffset,
7124                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
7125                 dr.mShowing[Drawables.RIGHT].draw(canvas);
7126                 canvas.restore();
7127             }
7128 
7129             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7130             // Make sure to update invalidateDrawable() when changing this code.
7131             if (dr.mShowing[Drawables.TOP] != null) {
7132                 canvas.save();
7133                 canvas.translate(scrollX + compoundPaddingLeft
7134                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
7135                 dr.mShowing[Drawables.TOP].draw(canvas);
7136                 canvas.restore();
7137             }
7138 
7139             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7140             // Make sure to update invalidateDrawable() when changing this code.
7141             if (dr.mShowing[Drawables.BOTTOM] != null) {
7142                 canvas.save();
7143                 canvas.translate(scrollX + compoundPaddingLeft
7144                         + (hspace - dr.mDrawableWidthBottom) / 2,
7145                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
7146                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
7147                 canvas.restore();
7148             }
7149         }
7150 
7151         int color = mCurTextColor;
7152 
7153         if (mLayout == null) {
7154             assumeLayout();
7155         }
7156 
7157         Layout layout = mLayout;
7158 
7159         if (mHint != null && mText.length() == 0) {
7160             if (mHintTextColor != null) {
7161                 color = mCurHintTextColor;
7162             }
7163 
7164             layout = mHintLayout;
7165         }
7166 
7167         mTextPaint.setColor(color);
7168         mTextPaint.drawableState = getDrawableState();
7169 
7170         canvas.save();
7171         /*  Would be faster if we didn't have to do this. Can we chop the
7172             (displayable) text so that we don't need to do this ever?
7173         */
7174 
7175         int extendedPaddingTop = getExtendedPaddingTop();
7176         int extendedPaddingBottom = getExtendedPaddingBottom();
7177 
7178         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7179         final int maxScrollY = mLayout.getHeight() - vspace;
7180 
7181         float clipLeft = compoundPaddingLeft + scrollX;
7182         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
7183         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
7184         float clipBottom = bottom - top + scrollY
7185                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
7186 
7187         if (mShadowRadius != 0) {
7188             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
7189             clipRight += Math.max(0, mShadowDx + mShadowRadius);
7190 
7191             clipTop += Math.min(0, mShadowDy - mShadowRadius);
7192             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
7193         }
7194 
7195         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
7196 
7197         int voffsetText = 0;
7198         int voffsetCursor = 0;
7199 
7200         // translate in by our padding
7201         /* shortcircuit calling getVerticaOffset() */
7202         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7203             voffsetText = getVerticalOffset(false);
7204             voffsetCursor = getVerticalOffset(true);
7205         }
7206         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
7207 
7208         final int layoutDirection = getLayoutDirection();
7209         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7210         if (isMarqueeFadeEnabled()) {
7211             if (!mSingleLine && getLineCount() == 1 && canMarquee()
7212                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
7213                 final int width = mRight - mLeft;
7214                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
7215                 final float dx = mLayout.getLineRight(0) - (width - padding);
7216                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7217             }
7218 
7219             if (mMarquee != null && mMarquee.isRunning()) {
7220                 final float dx = -mMarquee.getScroll();
7221                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7222             }
7223         }
7224 
7225         final int cursorOffsetVertical = voffsetCursor - voffsetText;
7226 
7227         Path highlight = getUpdatedHighlightPath();
7228         if (mEditor != null) {
7229             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
7230         } else {
7231             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7232         }
7233 
7234         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
7235             final float dx = mMarquee.getGhostOffset();
7236             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7237             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7238         }
7239 
7240         canvas.restore();
7241     }
7242 
7243     @Override
getFocusedRect(Rect r)7244     public void getFocusedRect(Rect r) {
7245         if (mLayout == null) {
7246             super.getFocusedRect(r);
7247             return;
7248         }
7249 
7250         int selEnd = getSelectionEnd();
7251         if (selEnd < 0) {
7252             super.getFocusedRect(r);
7253             return;
7254         }
7255 
7256         int selStart = getSelectionStart();
7257         if (selStart < 0 || selStart >= selEnd) {
7258             int line = mLayout.getLineForOffset(selEnd);
7259             r.top = mLayout.getLineTop(line);
7260             r.bottom = mLayout.getLineBottom(line);
7261             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
7262             r.right = r.left + 4;
7263         } else {
7264             int lineStart = mLayout.getLineForOffset(selStart);
7265             int lineEnd = mLayout.getLineForOffset(selEnd);
7266             r.top = mLayout.getLineTop(lineStart);
7267             r.bottom = mLayout.getLineBottom(lineEnd);
7268             if (lineStart == lineEnd) {
7269                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
7270                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
7271             } else {
7272                 // Selection extends across multiple lines -- make the focused
7273                 // rect cover the entire width.
7274                 if (mHighlightPathBogus) {
7275                     if (mHighlightPath == null) mHighlightPath = new Path();
7276                     mHighlightPath.reset();
7277                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7278                     mHighlightPathBogus = false;
7279                 }
7280                 synchronized (TEMP_RECTF) {
7281                     mHighlightPath.computeBounds(TEMP_RECTF, true);
7282                     r.left = (int) TEMP_RECTF.left - 1;
7283                     r.right = (int) TEMP_RECTF.right + 1;
7284                 }
7285             }
7286         }
7287 
7288         // Adjust for padding and gravity.
7289         int paddingLeft = getCompoundPaddingLeft();
7290         int paddingTop = getExtendedPaddingTop();
7291         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7292             paddingTop += getVerticalOffset(false);
7293         }
7294         r.offset(paddingLeft, paddingTop);
7295         int paddingBottom = getExtendedPaddingBottom();
7296         r.bottom += paddingBottom;
7297     }
7298 
7299     /**
7300      * Return the number of lines of text, or 0 if the internal Layout has not
7301      * been built.
7302      */
getLineCount()7303     public int getLineCount() {
7304         return mLayout != null ? mLayout.getLineCount() : 0;
7305     }
7306 
7307     /**
7308      * Return the baseline for the specified line (0...getLineCount() - 1)
7309      * If bounds is not null, return the top, left, right, bottom extents
7310      * of the specified line in it. If the internal Layout has not been built,
7311      * return 0 and set bounds to (0, 0, 0, 0)
7312      * @param line which line to examine (0..getLineCount() - 1)
7313      * @param bounds Optional. If not null, it returns the extent of the line
7314      * @return the Y-coordinate of the baseline
7315      */
getLineBounds(int line, Rect bounds)7316     public int getLineBounds(int line, Rect bounds) {
7317         if (mLayout == null) {
7318             if (bounds != null) {
7319                 bounds.set(0, 0, 0, 0);
7320             }
7321             return 0;
7322         } else {
7323             int baseline = mLayout.getLineBounds(line, bounds);
7324 
7325             int voffset = getExtendedPaddingTop();
7326             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7327                 voffset += getVerticalOffset(true);
7328             }
7329             if (bounds != null) {
7330                 bounds.offset(getCompoundPaddingLeft(), voffset);
7331             }
7332             return baseline + voffset;
7333         }
7334     }
7335 
7336     @Override
getBaseline()7337     public int getBaseline() {
7338         if (mLayout == null) {
7339             return super.getBaseline();
7340         }
7341 
7342         return getBaselineOffset() + mLayout.getLineBaseline(0);
7343     }
7344 
getBaselineOffset()7345     int getBaselineOffset() {
7346         int voffset = 0;
7347         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7348             voffset = getVerticalOffset(true);
7349         }
7350 
7351         if (isLayoutModeOptical(mParent)) {
7352             voffset -= getOpticalInsets().top;
7353         }
7354 
7355         return getExtendedPaddingTop() + voffset;
7356     }
7357 
7358     /**
7359      * @hide
7360      */
7361     @Override
getFadeTop(boolean offsetRequired)7362     protected int getFadeTop(boolean offsetRequired) {
7363         if (mLayout == null) return 0;
7364 
7365         int voffset = 0;
7366         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7367             voffset = getVerticalOffset(true);
7368         }
7369 
7370         if (offsetRequired) voffset += getTopPaddingOffset();
7371 
7372         return getExtendedPaddingTop() + voffset;
7373     }
7374 
7375     /**
7376      * @hide
7377      */
7378     @Override
getFadeHeight(boolean offsetRequired)7379     protected int getFadeHeight(boolean offsetRequired) {
7380         return mLayout != null ? mLayout.getHeight() : 0;
7381     }
7382 
7383     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)7384     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
7385         if (mSpannable != null && mLinksClickable) {
7386             final float x = event.getX(pointerIndex);
7387             final float y = event.getY(pointerIndex);
7388             final int offset = getOffsetForPosition(x, y);
7389             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
7390                     ClickableSpan.class);
7391             if (clickables.length > 0) {
7392                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
7393             }
7394         }
7395         if (isTextSelectable() || isTextEditable()) {
7396             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
7397         }
7398         return super.onResolvePointerIcon(event, pointerIndex);
7399     }
7400 
7401     @Override
onKeyPreIme(int keyCode, KeyEvent event)7402     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
7403         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
7404         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
7405         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
7406         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
7407             return true;
7408         }
7409         return super.onKeyPreIme(keyCode, event);
7410     }
7411 
7412     /**
7413      * @hide
7414      */
handleBackInTextActionModeIfNeeded(KeyEvent event)7415     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
7416         // Do nothing unless mEditor is in text action mode.
7417         if (mEditor == null || mEditor.getTextActionMode() == null) {
7418             return false;
7419         }
7420 
7421         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
7422             KeyEvent.DispatcherState state = getKeyDispatcherState();
7423             if (state != null) {
7424                 state.startTracking(event, this);
7425             }
7426             return true;
7427         } else if (event.getAction() == KeyEvent.ACTION_UP) {
7428             KeyEvent.DispatcherState state = getKeyDispatcherState();
7429             if (state != null) {
7430                 state.handleUpEvent(event);
7431             }
7432             if (event.isTracking() && !event.isCanceled()) {
7433                 stopTextActionMode();
7434                 return true;
7435             }
7436         }
7437         return false;
7438     }
7439 
7440     @Override
onKeyDown(int keyCode, KeyEvent event)7441     public boolean onKeyDown(int keyCode, KeyEvent event) {
7442         final int which = doKeyDown(keyCode, event, null);
7443         if (which == KEY_EVENT_NOT_HANDLED) {
7444             return super.onKeyDown(keyCode, event);
7445         }
7446 
7447         return true;
7448     }
7449 
7450     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)7451     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
7452         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
7453         final int which = doKeyDown(keyCode, down, event);
7454         if (which == KEY_EVENT_NOT_HANDLED) {
7455             // Go through default dispatching.
7456             return super.onKeyMultiple(keyCode, repeatCount, event);
7457         }
7458         if (which == KEY_EVENT_HANDLED) {
7459             // Consumed the whole thing.
7460             return true;
7461         }
7462 
7463         repeatCount--;
7464 
7465         // We are going to dispatch the remaining events to either the input
7466         // or movement method.  To do this, we will just send a repeated stream
7467         // of down and up events until we have done the complete repeatCount.
7468         // It would be nice if those interfaces had an onKeyMultiple() method,
7469         // but adding that is a more complicated change.
7470         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
7471         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
7472             // mEditor and mEditor.mInput are not null from doKeyDown
7473             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
7474             while (--repeatCount > 0) {
7475                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
7476                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
7477             }
7478             hideErrorIfUnchanged();
7479 
7480         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
7481             // mMovement is not null from doKeyDown
7482             mMovement.onKeyUp(this, mSpannable, keyCode, up);
7483             while (--repeatCount > 0) {
7484                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
7485                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
7486             }
7487         }
7488 
7489         return true;
7490     }
7491 
7492     /**
7493      * Returns true if pressing ENTER in this field advances focus instead
7494      * of inserting the character.  This is true mostly in single-line fields,
7495      * but also in mail addresses and subjects which will display on multiple
7496      * lines but where it doesn't make sense to insert newlines.
7497      */
shouldAdvanceFocusOnEnter()7498     private boolean shouldAdvanceFocusOnEnter() {
7499         if (getKeyListener() == null) {
7500             return false;
7501         }
7502 
7503         if (mSingleLine) {
7504             return true;
7505         }
7506 
7507         if (mEditor != null
7508                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
7509                         == EditorInfo.TYPE_CLASS_TEXT) {
7510             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
7511             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
7512                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
7513                 return true;
7514             }
7515         }
7516 
7517         return false;
7518     }
7519 
7520     /**
7521      * Returns true if pressing TAB in this field advances focus instead
7522      * of inserting the character.  Insert tabs only in multi-line editors.
7523      */
shouldAdvanceFocusOnTab()7524     private boolean shouldAdvanceFocusOnTab() {
7525         if (getKeyListener() != null && !mSingleLine && mEditor != null
7526                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
7527                         == EditorInfo.TYPE_CLASS_TEXT) {
7528             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
7529             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
7530                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
7531                 return false;
7532             }
7533         }
7534         return true;
7535     }
7536 
isDirectionalNavigationKey(int keyCode)7537     private boolean isDirectionalNavigationKey(int keyCode) {
7538         switch(keyCode) {
7539             case KeyEvent.KEYCODE_DPAD_UP:
7540             case KeyEvent.KEYCODE_DPAD_DOWN:
7541             case KeyEvent.KEYCODE_DPAD_LEFT:
7542             case KeyEvent.KEYCODE_DPAD_RIGHT:
7543                 return true;
7544         }
7545         return false;
7546     }
7547 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)7548     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
7549         if (!isEnabled()) {
7550             return KEY_EVENT_NOT_HANDLED;
7551         }
7552 
7553         // If this is the initial keydown, we don't want to prevent a movement away from this view.
7554         // While this shouldn't be necessary because any time we're preventing default movement we
7555         // should be restricting the focus to remain within this view, thus we'll also receive
7556         // the key up event, occasionally key up events will get dropped and we don't want to
7557         // prevent the user from traversing out of this on the next key down.
7558         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
7559             mPreventDefaultMovement = false;
7560         }
7561 
7562         switch (keyCode) {
7563             case KeyEvent.KEYCODE_ENTER:
7564                 if (event.hasNoModifiers()) {
7565                     // When mInputContentType is set, we know that we are
7566                     // running in a "modern" cupcake environment, so don't need
7567                     // to worry about the application trying to capture
7568                     // enter key events.
7569                     if (mEditor != null && mEditor.mInputContentType != null) {
7570                         // If there is an action listener, given them a
7571                         // chance to consume the event.
7572                         if (mEditor.mInputContentType.onEditorActionListener != null
7573                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
7574                                         this, EditorInfo.IME_NULL, event)) {
7575                             mEditor.mInputContentType.enterDown = true;
7576                             // We are consuming the enter key for them.
7577                             return KEY_EVENT_HANDLED;
7578                         }
7579                     }
7580 
7581                     // If our editor should move focus when enter is pressed, or
7582                     // this is a generated event from an IME action button, then
7583                     // don't let it be inserted into the text.
7584                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7585                             || shouldAdvanceFocusOnEnter()) {
7586                         if (hasOnClickListeners()) {
7587                             return KEY_EVENT_NOT_HANDLED;
7588                         }
7589                         return KEY_EVENT_HANDLED;
7590                     }
7591                 }
7592                 break;
7593 
7594             case KeyEvent.KEYCODE_DPAD_CENTER:
7595                 if (event.hasNoModifiers()) {
7596                     if (shouldAdvanceFocusOnEnter()) {
7597                         return KEY_EVENT_NOT_HANDLED;
7598                     }
7599                 }
7600                 break;
7601 
7602             case KeyEvent.KEYCODE_TAB:
7603                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
7604                     if (shouldAdvanceFocusOnTab()) {
7605                         return KEY_EVENT_NOT_HANDLED;
7606                     }
7607                 }
7608                 break;
7609 
7610                 // Has to be done on key down (and not on key up) to correctly be intercepted.
7611             case KeyEvent.KEYCODE_BACK:
7612                 if (mEditor != null && mEditor.getTextActionMode() != null) {
7613                     stopTextActionMode();
7614                     return KEY_EVENT_HANDLED;
7615                 }
7616                 break;
7617 
7618             case KeyEvent.KEYCODE_CUT:
7619                 if (event.hasNoModifiers() && canCut()) {
7620                     if (onTextContextMenuItem(ID_CUT)) {
7621                         return KEY_EVENT_HANDLED;
7622                     }
7623                 }
7624                 break;
7625 
7626             case KeyEvent.KEYCODE_COPY:
7627                 if (event.hasNoModifiers() && canCopy()) {
7628                     if (onTextContextMenuItem(ID_COPY)) {
7629                         return KEY_EVENT_HANDLED;
7630                     }
7631                 }
7632                 break;
7633 
7634             case KeyEvent.KEYCODE_PASTE:
7635                 if (event.hasNoModifiers() && canPaste()) {
7636                     if (onTextContextMenuItem(ID_PASTE)) {
7637                         return KEY_EVENT_HANDLED;
7638                     }
7639                 }
7640                 break;
7641         }
7642 
7643         if (mEditor != null && mEditor.mKeyListener != null) {
7644             boolean doDown = true;
7645             if (otherEvent != null) {
7646                 try {
7647                     beginBatchEdit();
7648                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
7649                             otherEvent);
7650                     hideErrorIfUnchanged();
7651                     doDown = false;
7652                     if (handled) {
7653                         return KEY_EVENT_HANDLED;
7654                     }
7655                 } catch (AbstractMethodError e) {
7656                     // onKeyOther was added after 1.0, so if it isn't
7657                     // implemented we need to try to dispatch as a regular down.
7658                 } finally {
7659                     endBatchEdit();
7660                 }
7661             }
7662 
7663             if (doDown) {
7664                 beginBatchEdit();
7665                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
7666                         keyCode, event);
7667                 endBatchEdit();
7668                 hideErrorIfUnchanged();
7669                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
7670             }
7671         }
7672 
7673         // bug 650865: sometimes we get a key event before a layout.
7674         // don't try to move around if we don't know the layout.
7675 
7676         if (mMovement != null && mLayout != null) {
7677             boolean doDown = true;
7678             if (otherEvent != null) {
7679                 try {
7680                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
7681                     doDown = false;
7682                     if (handled) {
7683                         return KEY_EVENT_HANDLED;
7684                     }
7685                 } catch (AbstractMethodError e) {
7686                     // onKeyOther was added after 1.0, so if it isn't
7687                     // implemented we need to try to dispatch as a regular down.
7688                 }
7689             }
7690             if (doDown) {
7691                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
7692                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
7693                         mPreventDefaultMovement = true;
7694                     }
7695                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
7696                 }
7697             }
7698             // Consume arrows from keyboard devices to prevent focus leaving the editor.
7699             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
7700             // to move focus with arrows.
7701             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
7702                     && isDirectionalNavigationKey(keyCode)) {
7703                 return KEY_EVENT_HANDLED;
7704             }
7705         }
7706 
7707         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
7708                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
7709     }
7710 
7711     /**
7712      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
7713      * can be recorded.
7714      * @hide
7715      */
resetErrorChangedFlag()7716     public void resetErrorChangedFlag() {
7717         /*
7718          * Keep track of what the error was before doing the input
7719          * so that if an input filter changed the error, we leave
7720          * that error showing.  Otherwise, we take down whatever
7721          * error was showing when the user types something.
7722          */
7723         if (mEditor != null) mEditor.mErrorWasChanged = false;
7724     }
7725 
7726     /**
7727      * @hide
7728      */
hideErrorIfUnchanged()7729     public void hideErrorIfUnchanged() {
7730         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
7731             setError(null, null);
7732         }
7733     }
7734 
7735     @Override
onKeyUp(int keyCode, KeyEvent event)7736     public boolean onKeyUp(int keyCode, KeyEvent event) {
7737         if (!isEnabled()) {
7738             return super.onKeyUp(keyCode, event);
7739         }
7740 
7741         if (!KeyEvent.isModifierKey(keyCode)) {
7742             mPreventDefaultMovement = false;
7743         }
7744 
7745         switch (keyCode) {
7746             case KeyEvent.KEYCODE_DPAD_CENTER:
7747                 if (event.hasNoModifiers()) {
7748                     /*
7749                      * If there is a click listener, just call through to
7750                      * super, which will invoke it.
7751                      *
7752                      * If there isn't a click listener, try to show the soft
7753                      * input method.  (It will also
7754                      * call performClick(), but that won't do anything in
7755                      * this case.)
7756                      */
7757                     if (!hasOnClickListeners()) {
7758                         if (mMovement != null && mText instanceof Editable
7759                                 && mLayout != null && onCheckIsTextEditor()) {
7760                             InputMethodManager imm = InputMethodManager.peekInstance();
7761                             viewClicked(imm);
7762                             if (imm != null && getShowSoftInputOnFocus()) {
7763                                 imm.showSoftInput(this, 0);
7764                             }
7765                         }
7766                     }
7767                 }
7768                 return super.onKeyUp(keyCode, event);
7769 
7770             case KeyEvent.KEYCODE_ENTER:
7771                 if (event.hasNoModifiers()) {
7772                     if (mEditor != null && mEditor.mInputContentType != null
7773                             && mEditor.mInputContentType.onEditorActionListener != null
7774                             && mEditor.mInputContentType.enterDown) {
7775                         mEditor.mInputContentType.enterDown = false;
7776                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
7777                                 this, EditorInfo.IME_NULL, event)) {
7778                             return true;
7779                         }
7780                     }
7781 
7782                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7783                             || shouldAdvanceFocusOnEnter()) {
7784                         /*
7785                          * If there is a click listener, just call through to
7786                          * super, which will invoke it.
7787                          *
7788                          * If there isn't a click listener, try to advance focus,
7789                          * but still call through to super, which will reset the
7790                          * pressed state and longpress state.  (It will also
7791                          * call performClick(), but that won't do anything in
7792                          * this case.)
7793                          */
7794                         if (!hasOnClickListeners()) {
7795                             View v = focusSearch(FOCUS_DOWN);
7796 
7797                             if (v != null) {
7798                                 if (!v.requestFocus(FOCUS_DOWN)) {
7799                                     throw new IllegalStateException("focus search returned a view "
7800                                             + "that wasn't able to take focus!");
7801                                 }
7802 
7803                                 /*
7804                                  * Return true because we handled the key; super
7805                                  * will return false because there was no click
7806                                  * listener.
7807                                  */
7808                                 super.onKeyUp(keyCode, event);
7809                                 return true;
7810                             } else if ((event.getFlags()
7811                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
7812                                 // No target for next focus, but make sure the IME
7813                                 // if this came from it.
7814                                 InputMethodManager imm = InputMethodManager.peekInstance();
7815                                 if (imm != null && imm.isActive(this)) {
7816                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7817                                 }
7818                             }
7819                         }
7820                     }
7821                     return super.onKeyUp(keyCode, event);
7822                 }
7823                 break;
7824         }
7825 
7826         if (mEditor != null && mEditor.mKeyListener != null) {
7827             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
7828                 return true;
7829             }
7830         }
7831 
7832         if (mMovement != null && mLayout != null) {
7833             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
7834                 return true;
7835             }
7836         }
7837 
7838         return super.onKeyUp(keyCode, event);
7839     }
7840 
7841     @Override
onCheckIsTextEditor()7842     public boolean onCheckIsTextEditor() {
7843         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
7844     }
7845 
7846     @Override
onCreateInputConnection(EditorInfo outAttrs)7847     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
7848         if (onCheckIsTextEditor() && isEnabled()) {
7849             mEditor.createInputMethodStateIfNeeded();
7850             outAttrs.inputType = getInputType();
7851             if (mEditor.mInputContentType != null) {
7852                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
7853                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
7854                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
7855                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
7856                 outAttrs.extras = mEditor.mInputContentType.extras;
7857                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
7858             } else {
7859                 outAttrs.imeOptions = EditorInfo.IME_NULL;
7860                 outAttrs.hintLocales = null;
7861             }
7862             if (focusSearch(FOCUS_DOWN) != null) {
7863                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
7864             }
7865             if (focusSearch(FOCUS_UP) != null) {
7866                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
7867             }
7868             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
7869                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
7870                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
7871                     // An action has not been set, but the enter key will move to
7872                     // the next focus, so set the action to that.
7873                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
7874                 } else {
7875                     // An action has not been set, and there is no focus to move
7876                     // to, so let's just supply a "done" action.
7877                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
7878                 }
7879                 if (!shouldAdvanceFocusOnEnter()) {
7880                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7881                 }
7882             }
7883             if (isMultilineInputType(outAttrs.inputType)) {
7884                 // Multi-line text editors should always show an enter key.
7885                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7886             }
7887             outAttrs.hintText = mHint;
7888             if (mText instanceof Editable) {
7889                 InputConnection ic = new EditableInputConnection(this);
7890                 outAttrs.initialSelStart = getSelectionStart();
7891                 outAttrs.initialSelEnd = getSelectionEnd();
7892                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
7893                 return ic;
7894             }
7895         }
7896         return null;
7897     }
7898 
7899     /**
7900      * If this TextView contains editable content, extract a portion of it
7901      * based on the information in <var>request</var> in to <var>outText</var>.
7902      * @return Returns true if the text was successfully extracted, else false.
7903      */
extractText(ExtractedTextRequest request, ExtractedText outText)7904     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
7905         createEditorIfNeeded();
7906         return mEditor.extractText(request, outText);
7907     }
7908 
7909     /**
7910      * This is used to remove all style-impacting spans from text before new
7911      * extracted text is being replaced into it, so that we don't have any
7912      * lingering spans applied during the replace.
7913      */
removeParcelableSpans(Spannable spannable, int start, int end)7914     static void removeParcelableSpans(Spannable spannable, int start, int end) {
7915         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
7916         int i = spans.length;
7917         while (i > 0) {
7918             i--;
7919             spannable.removeSpan(spans[i]);
7920         }
7921     }
7922 
7923     /**
7924      * Apply to this text view the given extracted text, as previously
7925      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
7926      */
setExtractedText(ExtractedText text)7927     public void setExtractedText(ExtractedText text) {
7928         Editable content = getEditableText();
7929         if (text.text != null) {
7930             if (content == null) {
7931                 setText(text.text, TextView.BufferType.EDITABLE);
7932             } else {
7933                 int start = 0;
7934                 int end = content.length();
7935 
7936                 if (text.partialStartOffset >= 0) {
7937                     final int N = content.length();
7938                     start = text.partialStartOffset;
7939                     if (start > N) start = N;
7940                     end = text.partialEndOffset;
7941                     if (end > N) end = N;
7942                 }
7943 
7944                 removeParcelableSpans(content, start, end);
7945                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
7946                     if (text.text instanceof Spanned) {
7947                         // OK to copy spans only.
7948                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
7949                                 Object.class, content, start);
7950                     }
7951                 } else {
7952                     content.replace(start, end, text.text);
7953                 }
7954             }
7955         }
7956 
7957         // Now set the selection position...  make sure it is in range, to
7958         // avoid crashes.  If this is a partial update, it is possible that
7959         // the underlying text may have changed, causing us problems here.
7960         // Also we just don't want to trust clients to do the right thing.
7961         Spannable sp = (Spannable) getText();
7962         final int N = sp.length();
7963         int start = text.selectionStart;
7964         if (start < 0) {
7965             start = 0;
7966         } else if (start > N) {
7967             start = N;
7968         }
7969         int end = text.selectionEnd;
7970         if (end < 0) {
7971             end = 0;
7972         } else if (end > N) {
7973             end = N;
7974         }
7975         Selection.setSelection(sp, start, end);
7976 
7977         // Finally, update the selection mode.
7978         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
7979             MetaKeyKeyListener.startSelecting(this, sp);
7980         } else {
7981             MetaKeyKeyListener.stopSelecting(this, sp);
7982         }
7983 
7984         setHintInternal(text.hint);
7985     }
7986 
7987     /**
7988      * @hide
7989      */
setExtracting(ExtractedTextRequest req)7990     public void setExtracting(ExtractedTextRequest req) {
7991         if (mEditor.mInputMethodState != null) {
7992             mEditor.mInputMethodState.mExtractedTextRequest = req;
7993         }
7994         // This would stop a possible selection mode, but no such mode is started in case
7995         // extracted mode will start. Some text is selected though, and will trigger an action mode
7996         // in the extracted view.
7997         mEditor.hideCursorAndSpanControllers();
7998         stopTextActionMode();
7999         if (mEditor.mSelectionModifierCursorController != null) {
8000             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
8001         }
8002     }
8003 
8004     /**
8005      * Called by the framework in response to a text completion from
8006      * the current input method, provided by it calling
8007      * {@link InputConnection#commitCompletion
8008      * InputConnection.commitCompletion()}.  The default implementation does
8009      * nothing; text views that are supporting auto-completion should override
8010      * this to do their desired behavior.
8011      *
8012      * @param text The auto complete text the user has selected.
8013      */
onCommitCompletion(CompletionInfo text)8014     public void onCommitCompletion(CompletionInfo text) {
8015         // intentionally empty
8016     }
8017 
8018     /**
8019      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
8020      * dictionary) from the current input method, provided by it calling
8021      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
8022      * The default implementation flashes the background of the corrected word to provide
8023      * feedback to the user.
8024      *
8025      * @param info The auto correct info about the text that was corrected.
8026      */
onCommitCorrection(CorrectionInfo info)8027     public void onCommitCorrection(CorrectionInfo info) {
8028         if (mEditor != null) mEditor.onCommitCorrection(info);
8029     }
8030 
beginBatchEdit()8031     public void beginBatchEdit() {
8032         if (mEditor != null) mEditor.beginBatchEdit();
8033     }
8034 
endBatchEdit()8035     public void endBatchEdit() {
8036         if (mEditor != null) mEditor.endBatchEdit();
8037     }
8038 
8039     /**
8040      * Called by the framework in response to a request to begin a batch
8041      * of edit operations through a call to link {@link #beginBatchEdit()}.
8042      */
onBeginBatchEdit()8043     public void onBeginBatchEdit() {
8044         // intentionally empty
8045     }
8046 
8047     /**
8048      * Called by the framework in response to a request to end a batch
8049      * of edit operations through a call to link {@link #endBatchEdit}.
8050      */
onEndBatchEdit()8051     public void onEndBatchEdit() {
8052         // intentionally empty
8053     }
8054 
8055     /**
8056      * Called by the framework in response to a private command from the
8057      * current method, provided by it calling
8058      * {@link InputConnection#performPrivateCommand
8059      * InputConnection.performPrivateCommand()}.
8060      *
8061      * @param action The action name of the command.
8062      * @param data Any additional data for the command.  This may be null.
8063      * @return Return true if you handled the command, else false.
8064      */
onPrivateIMECommand(String action, Bundle data)8065     public boolean onPrivateIMECommand(String action, Bundle data) {
8066         return false;
8067     }
8068 
8069     /** @hide */
8070     @VisibleForTesting
nullLayouts()8071     public void nullLayouts() {
8072         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
8073             mSavedLayout = (BoringLayout) mLayout;
8074         }
8075         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
8076             mSavedHintLayout = (BoringLayout) mHintLayout;
8077         }
8078 
8079         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
8080 
8081         mBoring = mHintBoring = null;
8082 
8083         // Since it depends on the value of mLayout
8084         if (mEditor != null) mEditor.prepareCursorControllers();
8085     }
8086 
8087     /**
8088      * Make a new Layout based on the already-measured size of the view,
8089      * on the assumption that it was measured correctly at some point.
8090      */
assumeLayout()8091     private void assumeLayout() {
8092         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8093 
8094         if (width < 1) {
8095             width = 0;
8096         }
8097 
8098         int physicalWidth = width;
8099 
8100         if (mHorizontallyScrolling) {
8101             width = VERY_WIDE;
8102         }
8103 
8104         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
8105                       physicalWidth, false);
8106     }
8107 
getLayoutAlignment()8108     private Layout.Alignment getLayoutAlignment() {
8109         Layout.Alignment alignment;
8110         switch (getTextAlignment()) {
8111             case TEXT_ALIGNMENT_GRAVITY:
8112                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
8113                     case Gravity.START:
8114                         alignment = Layout.Alignment.ALIGN_NORMAL;
8115                         break;
8116                     case Gravity.END:
8117                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
8118                         break;
8119                     case Gravity.LEFT:
8120                         alignment = Layout.Alignment.ALIGN_LEFT;
8121                         break;
8122                     case Gravity.RIGHT:
8123                         alignment = Layout.Alignment.ALIGN_RIGHT;
8124                         break;
8125                     case Gravity.CENTER_HORIZONTAL:
8126                         alignment = Layout.Alignment.ALIGN_CENTER;
8127                         break;
8128                     default:
8129                         alignment = Layout.Alignment.ALIGN_NORMAL;
8130                         break;
8131                 }
8132                 break;
8133             case TEXT_ALIGNMENT_TEXT_START:
8134                 alignment = Layout.Alignment.ALIGN_NORMAL;
8135                 break;
8136             case TEXT_ALIGNMENT_TEXT_END:
8137                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
8138                 break;
8139             case TEXT_ALIGNMENT_CENTER:
8140                 alignment = Layout.Alignment.ALIGN_CENTER;
8141                 break;
8142             case TEXT_ALIGNMENT_VIEW_START:
8143                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8144                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8145                 break;
8146             case TEXT_ALIGNMENT_VIEW_END:
8147                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8148                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8149                 break;
8150             case TEXT_ALIGNMENT_INHERIT:
8151                 // This should never happen as we have already resolved the text alignment
8152                 // but better safe than sorry so we just fall through
8153             default:
8154                 alignment = Layout.Alignment.ALIGN_NORMAL;
8155                 break;
8156         }
8157         return alignment;
8158     }
8159 
8160     /**
8161      * The width passed in is now the desired layout width,
8162      * not the full view width with padding.
8163      * {@hide}
8164      */
8165     @VisibleForTesting
makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8166     public void makeNewLayout(int wantWidth, int hintWidth,
8167                                  BoringLayout.Metrics boring,
8168                                  BoringLayout.Metrics hintBoring,
8169                                  int ellipsisWidth, boolean bringIntoView) {
8170         stopMarquee();
8171 
8172         // Update "old" cached values
8173         mOldMaximum = mMaximum;
8174         mOldMaxMode = mMaxMode;
8175 
8176         mHighlightPathBogus = true;
8177 
8178         if (wantWidth < 0) {
8179             wantWidth = 0;
8180         }
8181         if (hintWidth < 0) {
8182             hintWidth = 0;
8183         }
8184 
8185         Layout.Alignment alignment = getLayoutAlignment();
8186         final boolean testDirChange = mSingleLine && mLayout != null
8187                 && (alignment == Layout.Alignment.ALIGN_NORMAL
8188                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
8189         int oldDir = 0;
8190         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
8191         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
8192         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
8193                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
8194         TruncateAt effectiveEllipsize = mEllipsize;
8195         if (mEllipsize == TruncateAt.MARQUEE
8196                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8197             effectiveEllipsize = TruncateAt.END_SMALL;
8198         }
8199 
8200         if (mTextDir == null) {
8201             mTextDir = getTextDirectionHeuristic();
8202         }
8203 
8204         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
8205                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
8206         if (switchEllipsize) {
8207             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
8208                     ? TruncateAt.END : TruncateAt.MARQUEE;
8209             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
8210                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
8211         }
8212 
8213         shouldEllipsize = mEllipsize != null;
8214         mHintLayout = null;
8215 
8216         if (mHint != null) {
8217             if (shouldEllipsize) hintWidth = wantWidth;
8218 
8219             if (hintBoring == UNKNOWN_BORING) {
8220                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
8221                                                    mHintBoring);
8222                 if (hintBoring != null) {
8223                     mHintBoring = hintBoring;
8224                 }
8225             }
8226 
8227             if (hintBoring != null) {
8228                 if (hintBoring.width <= hintWidth
8229                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
8230                     if (mSavedHintLayout != null) {
8231                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
8232                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
8233                                 hintBoring, mIncludePad);
8234                     } else {
8235                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
8236                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
8237                                 hintBoring, mIncludePad);
8238                     }
8239 
8240                     mSavedHintLayout = (BoringLayout) mHintLayout;
8241                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
8242                     if (mSavedHintLayout != null) {
8243                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
8244                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
8245                                 hintBoring, mIncludePad, mEllipsize,
8246                                 ellipsisWidth);
8247                     } else {
8248                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
8249                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
8250                                 hintBoring, mIncludePad, mEllipsize,
8251                                 ellipsisWidth);
8252                     }
8253                 }
8254             }
8255             // TODO: code duplication with makeSingleLayout()
8256             if (mHintLayout == null) {
8257                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
8258                         mHint.length(), mTextPaint, hintWidth)
8259                         .setAlignment(alignment)
8260                         .setTextDirection(mTextDir)
8261                         .setLineSpacing(mSpacingAdd, mSpacingMult)
8262                         .setIncludePad(mIncludePad)
8263                         .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
8264                         .setBreakStrategy(mBreakStrategy)
8265                         .setHyphenationFrequency(mHyphenationFrequency)
8266                         .setJustificationMode(mJustificationMode)
8267                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
8268                 if (shouldEllipsize) {
8269                     builder.setEllipsize(mEllipsize)
8270                             .setEllipsizedWidth(ellipsisWidth);
8271                 }
8272                 mHintLayout = builder.build();
8273             }
8274         }
8275 
8276         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
8277             registerForPreDraw();
8278         }
8279 
8280         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8281             if (!compressText(ellipsisWidth)) {
8282                 final int height = mLayoutParams.height;
8283                 // If the size of the view does not depend on the size of the text, try to
8284                 // start the marquee immediately
8285                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
8286                     startMarquee();
8287                 } else {
8288                     // Defer the start of the marquee until we know our width (see setFrame())
8289                     mRestartMarquee = true;
8290                 }
8291             }
8292         }
8293 
8294         // CursorControllers need a non-null mLayout
8295         if (mEditor != null) mEditor.prepareCursorControllers();
8296     }
8297 
8298     /**
8299      * Returns true if DynamicLayout is required
8300      *
8301      * @hide
8302      */
8303     @VisibleForTesting
useDynamicLayout()8304     public boolean useDynamicLayout() {
8305         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
8306     }
8307 
8308     /**
8309      * @hide
8310      */
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)8311     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
8312             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
8313             boolean useSaved) {
8314         Layout result = null;
8315         if (useDynamicLayout()) {
8316             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
8317                     wantWidth)
8318                     .setDisplayText(mTransformed)
8319                     .setAlignment(alignment)
8320                     .setTextDirection(mTextDir)
8321                     .setLineSpacing(mSpacingAdd, mSpacingMult)
8322                     .setIncludePad(mIncludePad)
8323                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
8324                     .setBreakStrategy(mBreakStrategy)
8325                     .setHyphenationFrequency(mHyphenationFrequency)
8326                     .setJustificationMode(mJustificationMode)
8327                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
8328                     .setEllipsizedWidth(ellipsisWidth);
8329             result = builder.build();
8330         } else {
8331             if (boring == UNKNOWN_BORING) {
8332                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
8333                 if (boring != null) {
8334                     mBoring = boring;
8335                 }
8336             }
8337 
8338             if (boring != null) {
8339                 if (boring.width <= wantWidth
8340                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
8341                     if (useSaved && mSavedLayout != null) {
8342                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
8343                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
8344                                 boring, mIncludePad);
8345                     } else {
8346                         result = BoringLayout.make(mTransformed, mTextPaint,
8347                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
8348                                 boring, mIncludePad);
8349                     }
8350 
8351                     if (useSaved) {
8352                         mSavedLayout = (BoringLayout) result;
8353                     }
8354                 } else if (shouldEllipsize && boring.width <= wantWidth) {
8355                     if (useSaved && mSavedLayout != null) {
8356                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
8357                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
8358                                 boring, mIncludePad, effectiveEllipsize,
8359                                 ellipsisWidth);
8360                     } else {
8361                         result = BoringLayout.make(mTransformed, mTextPaint,
8362                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
8363                                 boring, mIncludePad, effectiveEllipsize,
8364                                 ellipsisWidth);
8365                     }
8366                 }
8367             }
8368         }
8369         if (result == null) {
8370             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
8371                     0, mTransformed.length(), mTextPaint, wantWidth)
8372                     .setAlignment(alignment)
8373                     .setTextDirection(mTextDir)
8374                     .setLineSpacing(mSpacingAdd, mSpacingMult)
8375                     .setIncludePad(mIncludePad)
8376                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
8377                     .setBreakStrategy(mBreakStrategy)
8378                     .setHyphenationFrequency(mHyphenationFrequency)
8379                     .setJustificationMode(mJustificationMode)
8380                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
8381             if (shouldEllipsize) {
8382                 builder.setEllipsize(effectiveEllipsize)
8383                         .setEllipsizedWidth(ellipsisWidth);
8384             }
8385             result = builder.build();
8386         }
8387         return result;
8388     }
8389 
compressText(float width)8390     private boolean compressText(float width) {
8391         if (isHardwareAccelerated()) return false;
8392 
8393         // Only compress the text if it hasn't been compressed by the previous pass
8394         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
8395                 && mTextPaint.getTextScaleX() == 1.0f) {
8396             final float textWidth = mLayout.getLineWidth(0);
8397             final float overflow = (textWidth + 1.0f - width) / width;
8398             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
8399                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
8400                 post(new Runnable() {
8401                     public void run() {
8402                         requestLayout();
8403                     }
8404                 });
8405                 return true;
8406             }
8407         }
8408 
8409         return false;
8410     }
8411 
desired(Layout layout)8412     private static int desired(Layout layout) {
8413         int n = layout.getLineCount();
8414         CharSequence text = layout.getText();
8415         float max = 0;
8416 
8417         // if any line was wrapped, we can't use it.
8418         // but it's ok for the last line not to have a newline
8419 
8420         for (int i = 0; i < n - 1; i++) {
8421             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
8422                 return -1;
8423             }
8424         }
8425 
8426         for (int i = 0; i < n; i++) {
8427             max = Math.max(max, layout.getLineWidth(i));
8428         }
8429 
8430         return (int) Math.ceil(max);
8431     }
8432 
8433     /**
8434      * Set whether the TextView includes extra top and bottom padding to make
8435      * room for accents that go above the normal ascent and descent.
8436      * The default is true.
8437      *
8438      * @see #getIncludeFontPadding()
8439      *
8440      * @attr ref android.R.styleable#TextView_includeFontPadding
8441      */
setIncludeFontPadding(boolean includepad)8442     public void setIncludeFontPadding(boolean includepad) {
8443         if (mIncludePad != includepad) {
8444             mIncludePad = includepad;
8445 
8446             if (mLayout != null) {
8447                 nullLayouts();
8448                 requestLayout();
8449                 invalidate();
8450             }
8451         }
8452     }
8453 
8454     /**
8455      * Gets whether the TextView includes extra top and bottom padding to make
8456      * room for accents that go above the normal ascent and descent.
8457      *
8458      * @see #setIncludeFontPadding(boolean)
8459      *
8460      * @attr ref android.R.styleable#TextView_includeFontPadding
8461      */
getIncludeFontPadding()8462     public boolean getIncludeFontPadding() {
8463         return mIncludePad;
8464     }
8465 
8466     /** @hide */
8467     @VisibleForTesting
8468     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
8469 
8470     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)8471     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
8472         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
8473         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
8474         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
8475         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
8476 
8477         int width;
8478         int height;
8479 
8480         BoringLayout.Metrics boring = UNKNOWN_BORING;
8481         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
8482 
8483         if (mTextDir == null) {
8484             mTextDir = getTextDirectionHeuristic();
8485         }
8486 
8487         int des = -1;
8488         boolean fromexisting = false;
8489         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
8490                 ?  (float) widthSize : Float.MAX_VALUE;
8491 
8492         if (widthMode == MeasureSpec.EXACTLY) {
8493             // Parent has told us how big to be. So be it.
8494             width = widthSize;
8495         } else {
8496             if (mLayout != null && mEllipsize == null) {
8497                 des = desired(mLayout);
8498             }
8499 
8500             if (des < 0) {
8501                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
8502                 if (boring != null) {
8503                     mBoring = boring;
8504                 }
8505             } else {
8506                 fromexisting = true;
8507             }
8508 
8509             if (boring == null || boring == UNKNOWN_BORING) {
8510                 if (des < 0) {
8511                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
8512                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
8513                 }
8514                 width = des;
8515             } else {
8516                 width = boring.width;
8517             }
8518 
8519             final Drawables dr = mDrawables;
8520             if (dr != null) {
8521                 width = Math.max(width, dr.mDrawableWidthTop);
8522                 width = Math.max(width, dr.mDrawableWidthBottom);
8523             }
8524 
8525             if (mHint != null) {
8526                 int hintDes = -1;
8527                 int hintWidth;
8528 
8529                 if (mHintLayout != null && mEllipsize == null) {
8530                     hintDes = desired(mHintLayout);
8531                 }
8532 
8533                 if (hintDes < 0) {
8534                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
8535                     if (hintBoring != null) {
8536                         mHintBoring = hintBoring;
8537                     }
8538                 }
8539 
8540                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
8541                     if (hintDes < 0) {
8542                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
8543                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
8544                     }
8545                     hintWidth = hintDes;
8546                 } else {
8547                     hintWidth = hintBoring.width;
8548                 }
8549 
8550                 if (hintWidth > width) {
8551                     width = hintWidth;
8552                 }
8553             }
8554 
8555             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
8556 
8557             if (mMaxWidthMode == EMS) {
8558                 width = Math.min(width, mMaxWidth * getLineHeight());
8559             } else {
8560                 width = Math.min(width, mMaxWidth);
8561             }
8562 
8563             if (mMinWidthMode == EMS) {
8564                 width = Math.max(width, mMinWidth * getLineHeight());
8565             } else {
8566                 width = Math.max(width, mMinWidth);
8567             }
8568 
8569             // Check against our minimum width
8570             width = Math.max(width, getSuggestedMinimumWidth());
8571 
8572             if (widthMode == MeasureSpec.AT_MOST) {
8573                 width = Math.min(widthSize, width);
8574             }
8575         }
8576 
8577         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
8578         int unpaddedWidth = want;
8579 
8580         if (mHorizontallyScrolling) want = VERY_WIDE;
8581 
8582         int hintWant = want;
8583         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
8584 
8585         if (mLayout == null) {
8586             makeNewLayout(want, hintWant, boring, hintBoring,
8587                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8588         } else {
8589             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
8590                     || (mLayout.getEllipsizedWidth()
8591                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
8592 
8593             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
8594                     && (want > mLayout.getWidth())
8595                     && (mLayout instanceof BoringLayout
8596                             || (fromexisting && des >= 0 && des <= want));
8597 
8598             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
8599 
8600             if (layoutChanged || maximumChanged) {
8601                 if (!maximumChanged && widthChanged) {
8602                     mLayout.increaseWidthTo(want);
8603                 } else {
8604                     makeNewLayout(want, hintWant, boring, hintBoring,
8605                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8606                 }
8607             } else {
8608                 // Nothing has changed
8609             }
8610         }
8611 
8612         if (heightMode == MeasureSpec.EXACTLY) {
8613             // Parent has told us how big to be. So be it.
8614             height = heightSize;
8615             mDesiredHeightAtMeasure = -1;
8616         } else {
8617             int desired = getDesiredHeight();
8618 
8619             height = desired;
8620             mDesiredHeightAtMeasure = desired;
8621 
8622             if (heightMode == MeasureSpec.AT_MOST) {
8623                 height = Math.min(desired, heightSize);
8624             }
8625         }
8626 
8627         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
8628         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
8629             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
8630         }
8631 
8632         /*
8633          * We didn't let makeNewLayout() register to bring the cursor into view,
8634          * so do it here if there is any possibility that it is needed.
8635          */
8636         if (mMovement != null
8637                 || mLayout.getWidth() > unpaddedWidth
8638                 || mLayout.getHeight() > unpaddedHeight) {
8639             registerForPreDraw();
8640         } else {
8641             scrollTo(0, 0);
8642         }
8643 
8644         setMeasuredDimension(width, height);
8645     }
8646 
8647     /**
8648      * Automatically computes and sets the text size.
8649      */
autoSizeText()8650     private void autoSizeText() {
8651         if (!isAutoSizeEnabled()) {
8652             return;
8653         }
8654 
8655         if (mNeedsAutoSizeText) {
8656             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
8657                 return;
8658             }
8659 
8660             final int availableWidth = mHorizontallyScrolling
8661                     ? VERY_WIDE
8662                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
8663             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
8664                     - getExtendedPaddingTop();
8665 
8666             if (availableWidth <= 0 || availableHeight <= 0) {
8667                 return;
8668             }
8669 
8670             synchronized (TEMP_RECTF) {
8671                 TEMP_RECTF.setEmpty();
8672                 TEMP_RECTF.right = availableWidth;
8673                 TEMP_RECTF.bottom = availableHeight;
8674                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
8675 
8676                 if (optimalTextSize != getTextSize()) {
8677                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
8678                             false /* shouldRequestLayout */);
8679 
8680                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
8681                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8682                             false /* bringIntoView */);
8683                 }
8684             }
8685         }
8686         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
8687         // after the next layout pass should set this to false.
8688         mNeedsAutoSizeText = true;
8689     }
8690 
8691     /**
8692      * Performs a binary search to find the largest text size that will still fit within the size
8693      * available to this view.
8694      */
findLargestTextSizeWhichFits(RectF availableSpace)8695     private int findLargestTextSizeWhichFits(RectF availableSpace) {
8696         final int sizesCount = mAutoSizeTextSizesInPx.length;
8697         if (sizesCount == 0) {
8698             throw new IllegalStateException("No available text sizes to choose from.");
8699         }
8700 
8701         int bestSizeIndex = 0;
8702         int lowIndex = bestSizeIndex + 1;
8703         int highIndex = sizesCount - 1;
8704         int sizeToTryIndex;
8705         while (lowIndex <= highIndex) {
8706             sizeToTryIndex = (lowIndex + highIndex) / 2;
8707             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
8708                 bestSizeIndex = lowIndex;
8709                 lowIndex = sizeToTryIndex + 1;
8710             } else {
8711                 highIndex = sizeToTryIndex - 1;
8712                 bestSizeIndex = highIndex;
8713             }
8714         }
8715 
8716         return mAutoSizeTextSizesInPx[bestSizeIndex];
8717     }
8718 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)8719     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
8720         final CharSequence text = mTransformed != null
8721                 ? mTransformed
8722                 : getText();
8723         final int maxLines = getMaxLines();
8724         if (mTempTextPaint == null) {
8725             mTempTextPaint = new TextPaint();
8726         } else {
8727             mTempTextPaint.reset();
8728         }
8729         mTempTextPaint.set(getPaint());
8730         mTempTextPaint.setTextSize(suggestedSizeInPx);
8731 
8732         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
8733                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
8734 
8735         layoutBuilder.setAlignment(getLayoutAlignment())
8736                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
8737                 .setIncludePad(getIncludeFontPadding())
8738                 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
8739                 .setBreakStrategy(getBreakStrategy())
8740                 .setHyphenationFrequency(getHyphenationFrequency())
8741                 .setJustificationMode(getJustificationMode())
8742                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
8743                 .setTextDirection(getTextDirectionHeuristic());
8744 
8745         final StaticLayout layout = layoutBuilder.build();
8746 
8747         // Lines overflow.
8748         if (maxLines != -1 && layout.getLineCount() > maxLines) {
8749             return false;
8750         }
8751 
8752         // Height overflow.
8753         if (layout.getHeight() > availableSpace.bottom) {
8754             return false;
8755         }
8756 
8757         return true;
8758     }
8759 
getDesiredHeight()8760     private int getDesiredHeight() {
8761         return Math.max(
8762                 getDesiredHeight(mLayout, true),
8763                 getDesiredHeight(mHintLayout, mEllipsize != null));
8764     }
8765 
getDesiredHeight(Layout layout, boolean cap)8766     private int getDesiredHeight(Layout layout, boolean cap) {
8767         if (layout == null) {
8768             return 0;
8769         }
8770 
8771         /*
8772         * Don't cap the hint to a certain number of lines.
8773         * (Do cap it, though, if we have a maximum pixel height.)
8774         */
8775         int desired = layout.getHeight(cap);
8776 
8777         final Drawables dr = mDrawables;
8778         if (dr != null) {
8779             desired = Math.max(desired, dr.mDrawableHeightLeft);
8780             desired = Math.max(desired, dr.mDrawableHeightRight);
8781         }
8782 
8783         int linecount = layout.getLineCount();
8784         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
8785         desired += padding;
8786 
8787         if (mMaxMode != LINES) {
8788             desired = Math.min(desired, mMaximum);
8789         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
8790                 || layout instanceof BoringLayout)) {
8791             desired = layout.getLineTop(mMaximum);
8792 
8793             if (dr != null) {
8794                 desired = Math.max(desired, dr.mDrawableHeightLeft);
8795                 desired = Math.max(desired, dr.mDrawableHeightRight);
8796             }
8797 
8798             desired += padding;
8799             linecount = mMaximum;
8800         }
8801 
8802         if (mMinMode == LINES) {
8803             if (linecount < mMinimum) {
8804                 desired += getLineHeight() * (mMinimum - linecount);
8805             }
8806         } else {
8807             desired = Math.max(desired, mMinimum);
8808         }
8809 
8810         // Check against our minimum height
8811         desired = Math.max(desired, getSuggestedMinimumHeight());
8812 
8813         return desired;
8814     }
8815 
8816     /**
8817      * Check whether a change to the existing text layout requires a
8818      * new view layout.
8819      */
checkForResize()8820     private void checkForResize() {
8821         boolean sizeChanged = false;
8822 
8823         if (mLayout != null) {
8824             // Check if our width changed
8825             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
8826                 sizeChanged = true;
8827                 invalidate();
8828             }
8829 
8830             // Check if our height changed
8831             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
8832                 int desiredHeight = getDesiredHeight();
8833 
8834                 if (desiredHeight != this.getHeight()) {
8835                     sizeChanged = true;
8836                 }
8837             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
8838                 if (mDesiredHeightAtMeasure >= 0) {
8839                     int desiredHeight = getDesiredHeight();
8840 
8841                     if (desiredHeight != mDesiredHeightAtMeasure) {
8842                         sizeChanged = true;
8843                     }
8844                 }
8845             }
8846         }
8847 
8848         if (sizeChanged) {
8849             requestLayout();
8850             // caller will have already invalidated
8851         }
8852     }
8853 
8854     /**
8855      * Check whether entirely new text requires a new view layout
8856      * or merely a new text layout.
8857      */
checkForRelayout()8858     private void checkForRelayout() {
8859         // If we have a fixed width, we can just swap in a new text layout
8860         // if the text height stays the same or if the view height is fixed.
8861 
8862         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
8863                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
8864                 && (mHint == null || mHintLayout != null)
8865                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
8866             // Static width, so try making a new text layout.
8867 
8868             int oldht = mLayout.getHeight();
8869             int want = mLayout.getWidth();
8870             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
8871 
8872             /*
8873              * No need to bring the text into view, since the size is not
8874              * changing (unless we do the requestLayout(), in which case it
8875              * will happen at measure).
8876              */
8877             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
8878                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8879                           false);
8880 
8881             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
8882                 // In a fixed-height view, so use our new text layout.
8883                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
8884                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
8885                     autoSizeText();
8886                     invalidate();
8887                     return;
8888                 }
8889 
8890                 // Dynamic height, but height has stayed the same,
8891                 // so use our new text layout.
8892                 if (mLayout.getHeight() == oldht
8893                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
8894                     autoSizeText();
8895                     invalidate();
8896                     return;
8897                 }
8898             }
8899 
8900             // We lose: the height has changed and we have a dynamic height.
8901             // Request a new view layout using our new text layout.
8902             requestLayout();
8903             invalidate();
8904         } else {
8905             // Dynamic width, so we have no choice but to request a new
8906             // view layout with a new text layout.
8907             nullLayouts();
8908             requestLayout();
8909             invalidate();
8910         }
8911     }
8912 
8913     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)8914     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
8915         super.onLayout(changed, left, top, right, bottom);
8916         if (mDeferScroll >= 0) {
8917             int curs = mDeferScroll;
8918             mDeferScroll = -1;
8919             bringPointIntoView(Math.min(curs, mText.length()));
8920         }
8921         // Call auto-size after the width and height have been calculated.
8922         autoSizeText();
8923     }
8924 
isShowingHint()8925     private boolean isShowingHint() {
8926         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
8927     }
8928 
8929     /**
8930      * Returns true if anything changed.
8931      */
bringTextIntoView()8932     private boolean bringTextIntoView() {
8933         Layout layout = isShowingHint() ? mHintLayout : mLayout;
8934         int line = 0;
8935         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8936             line = layout.getLineCount() - 1;
8937         }
8938 
8939         Layout.Alignment a = layout.getParagraphAlignment(line);
8940         int dir = layout.getParagraphDirection(line);
8941         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8942         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8943         int ht = layout.getHeight();
8944 
8945         int scrollx, scrolly;
8946 
8947         // Convert to left, center, or right alignment.
8948         if (a == Layout.Alignment.ALIGN_NORMAL) {
8949             a = dir == Layout.DIR_LEFT_TO_RIGHT
8950                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8951         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
8952             a = dir == Layout.DIR_LEFT_TO_RIGHT
8953                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8954         }
8955 
8956         if (a == Layout.Alignment.ALIGN_CENTER) {
8957             /*
8958              * Keep centered if possible, or, if it is too wide to fit,
8959              * keep leading edge in view.
8960              */
8961 
8962             int left = (int) Math.floor(layout.getLineLeft(line));
8963             int right = (int) Math.ceil(layout.getLineRight(line));
8964 
8965             if (right - left < hspace) {
8966                 scrollx = (right + left) / 2 - hspace / 2;
8967             } else {
8968                 if (dir < 0) {
8969                     scrollx = right - hspace;
8970                 } else {
8971                     scrollx = left;
8972                 }
8973             }
8974         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
8975             int right = (int) Math.ceil(layout.getLineRight(line));
8976             scrollx = right - hspace;
8977         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
8978             scrollx = (int) Math.floor(layout.getLineLeft(line));
8979         }
8980 
8981         if (ht < vspace) {
8982             scrolly = 0;
8983         } else {
8984             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8985                 scrolly = ht - vspace;
8986             } else {
8987                 scrolly = 0;
8988             }
8989         }
8990 
8991         if (scrollx != mScrollX || scrolly != mScrollY) {
8992             scrollTo(scrollx, scrolly);
8993             return true;
8994         } else {
8995             return false;
8996         }
8997     }
8998 
8999     /**
9000      * Move the point, specified by the offset, into the view if it is needed.
9001      * This has to be called after layout. Returns true if anything changed.
9002      */
bringPointIntoView(int offset)9003     public boolean bringPointIntoView(int offset) {
9004         if (isLayoutRequested()) {
9005             mDeferScroll = offset;
9006             return false;
9007         }
9008         boolean changed = false;
9009 
9010         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9011 
9012         if (layout == null) return changed;
9013 
9014         int line = layout.getLineForOffset(offset);
9015 
9016         int grav;
9017 
9018         switch (layout.getParagraphAlignment(line)) {
9019             case ALIGN_LEFT:
9020                 grav = 1;
9021                 break;
9022             case ALIGN_RIGHT:
9023                 grav = -1;
9024                 break;
9025             case ALIGN_NORMAL:
9026                 grav = layout.getParagraphDirection(line);
9027                 break;
9028             case ALIGN_OPPOSITE:
9029                 grav = -layout.getParagraphDirection(line);
9030                 break;
9031             case ALIGN_CENTER:
9032             default:
9033                 grav = 0;
9034                 break;
9035         }
9036 
9037         // We only want to clamp the cursor to fit within the layout width
9038         // in left-to-right modes, because in a right to left alignment,
9039         // we want to scroll to keep the line-right on the screen, as other
9040         // lines are likely to have text flush with the right margin, which
9041         // we want to keep visible.
9042         // A better long-term solution would probably be to measure both
9043         // the full line and a blank-trimmed version, and, for example, use
9044         // the latter measurement for centering and right alignment, but for
9045         // the time being we only implement the cursor clamping in left to
9046         // right where it is most likely to be annoying.
9047         final boolean clamped = grav > 0;
9048         // FIXME: Is it okay to truncate this, or should we round?
9049         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
9050         final int top = layout.getLineTop(line);
9051         final int bottom = layout.getLineTop(line + 1);
9052 
9053         int left = (int) Math.floor(layout.getLineLeft(line));
9054         int right = (int) Math.ceil(layout.getLineRight(line));
9055         int ht = layout.getHeight();
9056 
9057         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9058         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9059         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
9060             // If cursor has been clamped, make sure we don't scroll.
9061             right = Math.max(x, left + hspace);
9062         }
9063 
9064         int hslack = (bottom - top) / 2;
9065         int vslack = hslack;
9066 
9067         if (vslack > vspace / 4) {
9068             vslack = vspace / 4;
9069         }
9070         if (hslack > hspace / 4) {
9071             hslack = hspace / 4;
9072         }
9073 
9074         int hs = mScrollX;
9075         int vs = mScrollY;
9076 
9077         if (top - vs < vslack) {
9078             vs = top - vslack;
9079         }
9080         if (bottom - vs > vspace - vslack) {
9081             vs = bottom - (vspace - vslack);
9082         }
9083         if (ht - vs < vspace) {
9084             vs = ht - vspace;
9085         }
9086         if (0 - vs > 0) {
9087             vs = 0;
9088         }
9089 
9090         if (grav != 0) {
9091             if (x - hs < hslack) {
9092                 hs = x - hslack;
9093             }
9094             if (x - hs > hspace - hslack) {
9095                 hs = x - (hspace - hslack);
9096             }
9097         }
9098 
9099         if (grav < 0) {
9100             if (left - hs > 0) {
9101                 hs = left;
9102             }
9103             if (right - hs < hspace) {
9104                 hs = right - hspace;
9105             }
9106         } else if (grav > 0) {
9107             if (right - hs < hspace) {
9108                 hs = right - hspace;
9109             }
9110             if (left - hs > 0) {
9111                 hs = left;
9112             }
9113         } else /* grav == 0 */ {
9114             if (right - left <= hspace) {
9115                 /*
9116                  * If the entire text fits, center it exactly.
9117                  */
9118                 hs = left - (hspace - (right - left)) / 2;
9119             } else if (x > right - hslack) {
9120                 /*
9121                  * If we are near the right edge, keep the right edge
9122                  * at the edge of the view.
9123                  */
9124                 hs = right - hspace;
9125             } else if (x < left + hslack) {
9126                 /*
9127                  * If we are near the left edge, keep the left edge
9128                  * at the edge of the view.
9129                  */
9130                 hs = left;
9131             } else if (left > hs) {
9132                 /*
9133                  * Is there whitespace visible at the left?  Fix it if so.
9134                  */
9135                 hs = left;
9136             } else if (right < hs + hspace) {
9137                 /*
9138                  * Is there whitespace visible at the right?  Fix it if so.
9139                  */
9140                 hs = right - hspace;
9141             } else {
9142                 /*
9143                  * Otherwise, float as needed.
9144                  */
9145                 if (x - hs < hslack) {
9146                     hs = x - hslack;
9147                 }
9148                 if (x - hs > hspace - hslack) {
9149                     hs = x - (hspace - hslack);
9150                 }
9151             }
9152         }
9153 
9154         if (hs != mScrollX || vs != mScrollY) {
9155             if (mScroller == null) {
9156                 scrollTo(hs, vs);
9157             } else {
9158                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
9159                 int dx = hs - mScrollX;
9160                 int dy = vs - mScrollY;
9161 
9162                 if (duration > ANIMATED_SCROLL_GAP) {
9163                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
9164                     awakenScrollBars(mScroller.getDuration());
9165                     invalidate();
9166                 } else {
9167                     if (!mScroller.isFinished()) {
9168                         mScroller.abortAnimation();
9169                     }
9170 
9171                     scrollBy(dx, dy);
9172                 }
9173 
9174                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
9175             }
9176 
9177             changed = true;
9178         }
9179 
9180         if (isFocused()) {
9181             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
9182             // requestRectangleOnScreen() is in terms of content coordinates.
9183 
9184             // The offsets here are to ensure the rectangle we are using is
9185             // within our view bounds, in case the cursor is on the far left
9186             // or right.  If it isn't withing the bounds, then this request
9187             // will be ignored.
9188             if (mTempRect == null) mTempRect = new Rect();
9189             mTempRect.set(x - 2, top, x + 2, bottom);
9190             getInterestingRect(mTempRect, line);
9191             mTempRect.offset(mScrollX, mScrollY);
9192 
9193             if (requestRectangleOnScreen(mTempRect)) {
9194                 changed = true;
9195             }
9196         }
9197 
9198         return changed;
9199     }
9200 
9201     /**
9202      * Move the cursor, if needed, so that it is at an offset that is visible
9203      * to the user.  This will not move the cursor if it represents more than
9204      * one character (a selection range).  This will only work if the
9205      * TextView contains spannable text; otherwise it will do nothing.
9206      *
9207      * @return True if the cursor was actually moved, false otherwise.
9208      */
moveCursorToVisibleOffset()9209     public boolean moveCursorToVisibleOffset() {
9210         if (!(mText instanceof Spannable)) {
9211             return false;
9212         }
9213         int start = getSelectionStart();
9214         int end = getSelectionEnd();
9215         if (start != end) {
9216             return false;
9217         }
9218 
9219         // First: make sure the line is visible on screen:
9220 
9221         int line = mLayout.getLineForOffset(start);
9222 
9223         final int top = mLayout.getLineTop(line);
9224         final int bottom = mLayout.getLineTop(line + 1);
9225         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9226         int vslack = (bottom - top) / 2;
9227         if (vslack > vspace / 4) {
9228             vslack = vspace / 4;
9229         }
9230         final int vs = mScrollY;
9231 
9232         if (top < (vs + vslack)) {
9233             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
9234         } else if (bottom > (vspace + vs - vslack)) {
9235             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
9236         }
9237 
9238         // Next: make sure the character is visible on screen:
9239 
9240         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9241         final int hs = mScrollX;
9242         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
9243         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
9244 
9245         // line might contain bidirectional text
9246         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
9247         final int highChar = leftChar > rightChar ? leftChar : rightChar;
9248 
9249         int newStart = start;
9250         if (newStart < lowChar) {
9251             newStart = lowChar;
9252         } else if (newStart > highChar) {
9253             newStart = highChar;
9254         }
9255 
9256         if (newStart != start) {
9257             Selection.setSelection(mSpannable, newStart);
9258             return true;
9259         }
9260 
9261         return false;
9262     }
9263 
9264     @Override
computeScroll()9265     public void computeScroll() {
9266         if (mScroller != null) {
9267             if (mScroller.computeScrollOffset()) {
9268                 mScrollX = mScroller.getCurrX();
9269                 mScrollY = mScroller.getCurrY();
9270                 invalidateParentCaches();
9271                 postInvalidate();  // So we draw again
9272             }
9273         }
9274     }
9275 
getInterestingRect(Rect r, int line)9276     private void getInterestingRect(Rect r, int line) {
9277         convertFromViewportToContentCoordinates(r);
9278 
9279         // Rectangle can can be expanded on first and last line to take
9280         // padding into account.
9281         // TODO Take left/right padding into account too?
9282         if (line == 0) r.top -= getExtendedPaddingTop();
9283         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
9284     }
9285 
convertFromViewportToContentCoordinates(Rect r)9286     private void convertFromViewportToContentCoordinates(Rect r) {
9287         final int horizontalOffset = viewportToContentHorizontalOffset();
9288         r.left += horizontalOffset;
9289         r.right += horizontalOffset;
9290 
9291         final int verticalOffset = viewportToContentVerticalOffset();
9292         r.top += verticalOffset;
9293         r.bottom += verticalOffset;
9294     }
9295 
viewportToContentHorizontalOffset()9296     int viewportToContentHorizontalOffset() {
9297         return getCompoundPaddingLeft() - mScrollX;
9298     }
9299 
viewportToContentVerticalOffset()9300     int viewportToContentVerticalOffset() {
9301         int offset = getExtendedPaddingTop() - mScrollY;
9302         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9303             offset += getVerticalOffset(false);
9304         }
9305         return offset;
9306     }
9307 
9308     @Override
debug(int depth)9309     public void debug(int depth) {
9310         super.debug(depth);
9311 
9312         String output = debugIndent(depth);
9313         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
9314                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
9315                 + "} ";
9316 
9317         if (mText != null) {
9318 
9319             output += "mText=\"" + mText + "\" ";
9320             if (mLayout != null) {
9321                 output += "mLayout width=" + mLayout.getWidth()
9322                         + " height=" + mLayout.getHeight();
9323             }
9324         } else {
9325             output += "mText=NULL";
9326         }
9327         Log.d(VIEW_LOG_TAG, output);
9328     }
9329 
9330     /**
9331      * Convenience for {@link Selection#getSelectionStart}.
9332      */
9333     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()9334     public int getSelectionStart() {
9335         return Selection.getSelectionStart(getText());
9336     }
9337 
9338     /**
9339      * Convenience for {@link Selection#getSelectionEnd}.
9340      */
9341     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()9342     public int getSelectionEnd() {
9343         return Selection.getSelectionEnd(getText());
9344     }
9345 
9346     /**
9347      * Return true iff there is a selection inside this text view.
9348      */
hasSelection()9349     public boolean hasSelection() {
9350         final int selectionStart = getSelectionStart();
9351         final int selectionEnd = getSelectionEnd();
9352 
9353         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
9354     }
9355 
getSelectedText()9356     String getSelectedText() {
9357         if (!hasSelection()) {
9358             return null;
9359         }
9360 
9361         final int start = getSelectionStart();
9362         final int end = getSelectionEnd();
9363         return String.valueOf(
9364                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
9365     }
9366 
9367     /**
9368      * Sets the properties of this field (lines, horizontally scrolling,
9369      * transformation method) to be for a single-line input.
9370      *
9371      * @attr ref android.R.styleable#TextView_singleLine
9372      */
setSingleLine()9373     public void setSingleLine() {
9374         setSingleLine(true);
9375     }
9376 
9377     /**
9378      * Sets the properties of this field to transform input to ALL CAPS
9379      * display. This may use a "small caps" formatting if available.
9380      * This setting will be ignored if this field is editable or selectable.
9381      *
9382      * This call replaces the current transformation method. Disabling this
9383      * will not necessarily restore the previous behavior from before this
9384      * was enabled.
9385      *
9386      * @see #setTransformationMethod(TransformationMethod)
9387      * @attr ref android.R.styleable#TextView_textAllCaps
9388      */
setAllCaps(boolean allCaps)9389     public void setAllCaps(boolean allCaps) {
9390         if (allCaps) {
9391             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
9392         } else {
9393             setTransformationMethod(null);
9394         }
9395     }
9396 
9397     /**
9398      *
9399      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
9400      * @return Whether the current transformation method is for ALL CAPS.
9401      *
9402      * @see #setAllCaps(boolean)
9403      * @see #setTransformationMethod(TransformationMethod)
9404      */
isAllCaps()9405     public boolean isAllCaps() {
9406         final TransformationMethod method = getTransformationMethod();
9407         return method != null && method instanceof AllCapsTransformationMethod;
9408     }
9409 
9410     /**
9411      * If true, sets the properties of this field (number of lines, horizontally scrolling,
9412      * transformation method) to be for a single-line input; if false, restores these to the default
9413      * conditions.
9414      *
9415      * Note that the default conditions are not necessarily those that were in effect prior this
9416      * method, and you may want to reset these properties to your custom values.
9417      *
9418      * @attr ref android.R.styleable#TextView_singleLine
9419      */
9420     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)9421     public void setSingleLine(boolean singleLine) {
9422         // Could be used, but may break backward compatibility.
9423         // if (mSingleLine == singleLine) return;
9424         setInputTypeSingleLine(singleLine);
9425         applySingleLine(singleLine, true, true);
9426     }
9427 
9428     /**
9429      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
9430      * @param singleLine
9431      */
setInputTypeSingleLine(boolean singleLine)9432     private void setInputTypeSingleLine(boolean singleLine) {
9433         if (mEditor != null
9434                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
9435                         == EditorInfo.TYPE_CLASS_TEXT) {
9436             if (singleLine) {
9437                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9438             } else {
9439                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9440             }
9441         }
9442     }
9443 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)9444     private void applySingleLine(boolean singleLine, boolean applyTransformation,
9445             boolean changeMaxLines) {
9446         mSingleLine = singleLine;
9447         if (singleLine) {
9448             setLines(1);
9449             setHorizontallyScrolling(true);
9450             if (applyTransformation) {
9451                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
9452             }
9453         } else {
9454             if (changeMaxLines) {
9455                 setMaxLines(Integer.MAX_VALUE);
9456             }
9457             setHorizontallyScrolling(false);
9458             if (applyTransformation) {
9459                 setTransformationMethod(null);
9460             }
9461         }
9462     }
9463 
9464     /**
9465      * Causes words in the text that are longer than the view's width
9466      * to be ellipsized instead of broken in the middle.  You may also
9467      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
9468      * to constrain the text to a single line.  Use <code>null</code>
9469      * to turn off ellipsizing.
9470      *
9471      * If {@link #setMaxLines} has been used to set two or more lines,
9472      * only {@link android.text.TextUtils.TruncateAt#END} and
9473      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
9474      * (other ellipsizing types will not do anything).
9475      *
9476      * @attr ref android.R.styleable#TextView_ellipsize
9477      */
setEllipsize(TextUtils.TruncateAt where)9478     public void setEllipsize(TextUtils.TruncateAt where) {
9479         // TruncateAt is an enum. != comparison is ok between these singleton objects.
9480         if (mEllipsize != where) {
9481             mEllipsize = where;
9482 
9483             if (mLayout != null) {
9484                 nullLayouts();
9485                 requestLayout();
9486                 invalidate();
9487             }
9488         }
9489     }
9490 
9491     /**
9492      * Sets how many times to repeat the marquee animation. Only applied if the
9493      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
9494      *
9495      * @see #getMarqueeRepeatLimit()
9496      *
9497      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9498      */
setMarqueeRepeatLimit(int marqueeLimit)9499     public void setMarqueeRepeatLimit(int marqueeLimit) {
9500         mMarqueeRepeatLimit = marqueeLimit;
9501     }
9502 
9503     /**
9504      * Gets the number of times the marquee animation is repeated. Only meaningful if the
9505      * TextView has marquee enabled.
9506      *
9507      * @return the number of times the marquee animation is repeated. -1 if the animation
9508      * repeats indefinitely
9509      *
9510      * @see #setMarqueeRepeatLimit(int)
9511      *
9512      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9513      */
getMarqueeRepeatLimit()9514     public int getMarqueeRepeatLimit() {
9515         return mMarqueeRepeatLimit;
9516     }
9517 
9518     /**
9519      * Returns where, if anywhere, words that are longer than the view
9520      * is wide should be ellipsized.
9521      */
9522     @ViewDebug.ExportedProperty
getEllipsize()9523     public TextUtils.TruncateAt getEllipsize() {
9524         return mEllipsize;
9525     }
9526 
9527     /**
9528      * Set the TextView so that when it takes focus, all the text is
9529      * selected.
9530      *
9531      * @attr ref android.R.styleable#TextView_selectAllOnFocus
9532      */
9533     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)9534     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
9535         createEditorIfNeeded();
9536         mEditor.mSelectAllOnFocus = selectAllOnFocus;
9537 
9538         if (selectAllOnFocus && !(mText instanceof Spannable)) {
9539             setText(mText, BufferType.SPANNABLE);
9540         }
9541     }
9542 
9543     /**
9544      * Set whether the cursor is visible. The default is true. Note that this property only
9545      * makes sense for editable TextView.
9546      *
9547      * @see #isCursorVisible()
9548      *
9549      * @attr ref android.R.styleable#TextView_cursorVisible
9550      */
9551     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)9552     public void setCursorVisible(boolean visible) {
9553         if (visible && mEditor == null) return; // visible is the default value with no edit data
9554         createEditorIfNeeded();
9555         if (mEditor.mCursorVisible != visible) {
9556             mEditor.mCursorVisible = visible;
9557             invalidate();
9558 
9559             mEditor.makeBlink();
9560 
9561             // InsertionPointCursorController depends on mCursorVisible
9562             mEditor.prepareCursorControllers();
9563         }
9564     }
9565 
9566     /**
9567      * @return whether or not the cursor is visible (assuming this TextView is editable)
9568      *
9569      * @see #setCursorVisible(boolean)
9570      *
9571      * @attr ref android.R.styleable#TextView_cursorVisible
9572      */
isCursorVisible()9573     public boolean isCursorVisible() {
9574         // true is the default value
9575         return mEditor == null ? true : mEditor.mCursorVisible;
9576     }
9577 
canMarquee()9578     private boolean canMarquee() {
9579         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9580         return width > 0 && (mLayout.getLineWidth(0) > width
9581                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
9582                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
9583     }
9584 
startMarquee()9585     private void startMarquee() {
9586         // Do not ellipsize EditText
9587         if (getKeyListener() != null) return;
9588 
9589         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
9590             return;
9591         }
9592 
9593         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
9594                 && getLineCount() == 1 && canMarquee()) {
9595 
9596             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
9597                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
9598                 final Layout tmp = mLayout;
9599                 mLayout = mSavedMarqueeModeLayout;
9600                 mSavedMarqueeModeLayout = tmp;
9601                 setHorizontalFadingEdgeEnabled(true);
9602                 requestLayout();
9603                 invalidate();
9604             }
9605 
9606             if (mMarquee == null) mMarquee = new Marquee(this);
9607             mMarquee.start(mMarqueeRepeatLimit);
9608         }
9609     }
9610 
stopMarquee()9611     private void stopMarquee() {
9612         if (mMarquee != null && !mMarquee.isStopped()) {
9613             mMarquee.stop();
9614         }
9615 
9616         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
9617             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9618             final Layout tmp = mSavedMarqueeModeLayout;
9619             mSavedMarqueeModeLayout = mLayout;
9620             mLayout = tmp;
9621             setHorizontalFadingEdgeEnabled(false);
9622             requestLayout();
9623             invalidate();
9624         }
9625     }
9626 
startStopMarquee(boolean start)9627     private void startStopMarquee(boolean start) {
9628         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9629             if (start) {
9630                 startMarquee();
9631             } else {
9632                 stopMarquee();
9633             }
9634         }
9635     }
9636 
9637     /**
9638      * This method is called when the text is changed, in case any subclasses
9639      * would like to know.
9640      *
9641      * Within <code>text</code>, the <code>lengthAfter</code> characters
9642      * beginning at <code>start</code> have just replaced old text that had
9643      * length <code>lengthBefore</code>. It is an error to attempt to make
9644      * changes to <code>text</code> from this callback.
9645      *
9646      * @param text The text the TextView is displaying
9647      * @param start The offset of the start of the range of the text that was
9648      * modified
9649      * @param lengthBefore The length of the former text that has been replaced
9650      * @param lengthAfter The length of the replacement modified text
9651      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)9652     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
9653         // intentionally empty, template pattern method can be overridden by subclasses
9654     }
9655 
9656     /**
9657      * This method is called when the selection has changed, in case any
9658      * subclasses would like to know.
9659      *
9660      * @param selStart The new selection start location.
9661      * @param selEnd The new selection end location.
9662      */
onSelectionChanged(int selStart, int selEnd)9663     protected void onSelectionChanged(int selStart, int selEnd) {
9664         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
9665     }
9666 
9667     /**
9668      * Adds a TextWatcher to the list of those whose methods are called
9669      * whenever this TextView's text changes.
9670      * <p>
9671      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
9672      * not called after {@link #setText} calls.  Now, doing {@link #setText}
9673      * if there are any text changed listeners forces the buffer type to
9674      * Editable if it would not otherwise be and does call this method.
9675      */
addTextChangedListener(TextWatcher watcher)9676     public void addTextChangedListener(TextWatcher watcher) {
9677         if (mListeners == null) {
9678             mListeners = new ArrayList<TextWatcher>();
9679         }
9680 
9681         mListeners.add(watcher);
9682     }
9683 
9684     /**
9685      * Removes the specified TextWatcher from the list of those whose
9686      * methods are called
9687      * whenever this TextView's text changes.
9688      */
removeTextChangedListener(TextWatcher watcher)9689     public void removeTextChangedListener(TextWatcher watcher) {
9690         if (mListeners != null) {
9691             int i = mListeners.indexOf(watcher);
9692 
9693             if (i >= 0) {
9694                 mListeners.remove(i);
9695             }
9696         }
9697     }
9698 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)9699     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
9700         if (mListeners != null) {
9701             final ArrayList<TextWatcher> list = mListeners;
9702             final int count = list.size();
9703             for (int i = 0; i < count; i++) {
9704                 list.get(i).beforeTextChanged(text, start, before, after);
9705             }
9706         }
9707 
9708         // The spans that are inside or intersect the modified region no longer make sense
9709         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
9710         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
9711     }
9712 
9713     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)9714     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
9715         if (!(mText instanceof Editable)) return;
9716         Editable text = (Editable) mText;
9717 
9718         T[] spans = text.getSpans(start, end, type);
9719         final int length = spans.length;
9720         for (int i = 0; i < length; i++) {
9721             final int spanStart = text.getSpanStart(spans[i]);
9722             final int spanEnd = text.getSpanEnd(spans[i]);
9723             if (spanEnd == start || spanStart == end) break;
9724             text.removeSpan(spans[i]);
9725         }
9726     }
9727 
removeAdjacentSuggestionSpans(final int pos)9728     void removeAdjacentSuggestionSpans(final int pos) {
9729         if (!(mText instanceof Editable)) return;
9730         final Editable text = (Editable) mText;
9731 
9732         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
9733         final int length = spans.length;
9734         for (int i = 0; i < length; i++) {
9735             final int spanStart = text.getSpanStart(spans[i]);
9736             final int spanEnd = text.getSpanEnd(spans[i]);
9737             if (spanEnd == pos || spanStart == pos) {
9738                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
9739                     text.removeSpan(spans[i]);
9740                 }
9741             }
9742         }
9743     }
9744 
9745     /**
9746      * Not private so it can be called from an inner class without going
9747      * through a thunk.
9748      */
sendOnTextChanged(CharSequence text, int start, int before, int after)9749     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
9750         if (mListeners != null) {
9751             final ArrayList<TextWatcher> list = mListeners;
9752             final int count = list.size();
9753             for (int i = 0; i < count; i++) {
9754                 list.get(i).onTextChanged(text, start, before, after);
9755             }
9756         }
9757 
9758         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
9759     }
9760 
9761     /**
9762      * Not private so it can be called from an inner class without going
9763      * through a thunk.
9764      */
sendAfterTextChanged(Editable text)9765     void sendAfterTextChanged(Editable text) {
9766         if (mListeners != null) {
9767             final ArrayList<TextWatcher> list = mListeners;
9768             final int count = list.size();
9769             for (int i = 0; i < count; i++) {
9770                 list.get(i).afterTextChanged(text);
9771             }
9772         }
9773 
9774         // Always notify AutoFillManager - it will return right away if autofill is disabled.
9775         notifyAutoFillManagerAfterTextChangedIfNeeded();
9776 
9777         hideErrorIfUnchanged();
9778     }
9779 
notifyAutoFillManagerAfterTextChangedIfNeeded()9780     private void notifyAutoFillManagerAfterTextChangedIfNeeded() {
9781         // It is important to not check whether the view is important for autofill
9782         // since the user can trigger autofill manually on not important views.
9783         if (!isAutofillable()) {
9784             return;
9785         }
9786         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
9787         if (afm == null) {
9788             return;
9789         }
9790 
9791         if (mLastValueSentToAutofillManager == null
9792                 || !mLastValueSentToAutofillManager.equals(mText)) {
9793             if (android.view.autofill.Helper.sVerbose) {
9794                 Log.v(LOG_TAG, "notifying AFM after text changed");
9795             }
9796             afm.notifyValueChanged(TextView.this);
9797             mLastValueSentToAutofillManager = mText;
9798         } else {
9799             if (android.view.autofill.Helper.sVerbose) {
9800                 Log.v(LOG_TAG, "not notifying AFM on unchanged text");
9801             }
9802         }
9803     }
9804 
isAutofillable()9805     private boolean isAutofillable() {
9806         // It is important to not check whether the view is important for autofill
9807         // since the user can trigger autofill manually on not important views.
9808         return getAutofillType() != AUTOFILL_TYPE_NONE;
9809     }
9810 
updateAfterEdit()9811     void updateAfterEdit() {
9812         invalidate();
9813         int curs = getSelectionStart();
9814 
9815         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9816             registerForPreDraw();
9817         }
9818 
9819         checkForResize();
9820 
9821         if (curs >= 0) {
9822             mHighlightPathBogus = true;
9823             if (mEditor != null) mEditor.makeBlink();
9824             bringPointIntoView(curs);
9825         }
9826     }
9827 
9828     /**
9829      * Not private so it can be called from an inner class without going
9830      * through a thunk.
9831      */
handleTextChanged(CharSequence buffer, int start, int before, int after)9832     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
9833         sLastCutCopyOrTextChangedTime = 0;
9834 
9835         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9836         if (ims == null || ims.mBatchEditNesting == 0) {
9837             updateAfterEdit();
9838         }
9839         if (ims != null) {
9840             ims.mContentChanged = true;
9841             if (ims.mChangedStart < 0) {
9842                 ims.mChangedStart = start;
9843                 ims.mChangedEnd = start + before;
9844             } else {
9845                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
9846                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
9847             }
9848             ims.mChangedDelta += after - before;
9849         }
9850         resetErrorChangedFlag();
9851         sendOnTextChanged(buffer, start, before, after);
9852         onTextChanged(buffer, start, before, after);
9853     }
9854 
9855     /**
9856      * Not private so it can be called from an inner class without going
9857      * through a thunk.
9858      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)9859     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
9860         // XXX Make the start and end move together if this ends up
9861         // spending too much time invalidating.
9862 
9863         boolean selChanged = false;
9864         int newSelStart = -1, newSelEnd = -1;
9865 
9866         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9867 
9868         if (what == Selection.SELECTION_END) {
9869             selChanged = true;
9870             newSelEnd = newStart;
9871 
9872             if (oldStart >= 0 || newStart >= 0) {
9873                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
9874                 checkForResize();
9875                 registerForPreDraw();
9876                 if (mEditor != null) mEditor.makeBlink();
9877             }
9878         }
9879 
9880         if (what == Selection.SELECTION_START) {
9881             selChanged = true;
9882             newSelStart = newStart;
9883 
9884             if (oldStart >= 0 || newStart >= 0) {
9885                 int end = Selection.getSelectionEnd(buf);
9886                 invalidateCursor(end, oldStart, newStart);
9887             }
9888         }
9889 
9890         if (selChanged) {
9891             mHighlightPathBogus = true;
9892             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
9893 
9894             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
9895                 if (newSelStart < 0) {
9896                     newSelStart = Selection.getSelectionStart(buf);
9897                 }
9898                 if (newSelEnd < 0) {
9899                     newSelEnd = Selection.getSelectionEnd(buf);
9900                 }
9901 
9902                 if (mEditor != null) {
9903                     mEditor.refreshTextActionMode();
9904                     if (!hasSelection()
9905                             && mEditor.getTextActionMode() == null && hasTransientState()) {
9906                         // User generated selection has been removed.
9907                         setHasTransientState(false);
9908                     }
9909                 }
9910                 onSelectionChanged(newSelStart, newSelEnd);
9911             }
9912         }
9913 
9914         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
9915                 || what instanceof CharacterStyle) {
9916             if (ims == null || ims.mBatchEditNesting == 0) {
9917                 invalidate();
9918                 mHighlightPathBogus = true;
9919                 checkForResize();
9920             } else {
9921                 ims.mContentChanged = true;
9922             }
9923             if (mEditor != null) {
9924                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
9925                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
9926                 mEditor.invalidateHandlesAndActionMode();
9927             }
9928         }
9929 
9930         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
9931             mHighlightPathBogus = true;
9932             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
9933                 ims.mSelectionModeChanged = true;
9934             }
9935 
9936             if (Selection.getSelectionStart(buf) >= 0) {
9937                 if (ims == null || ims.mBatchEditNesting == 0) {
9938                     invalidateCursor();
9939                 } else {
9940                     ims.mCursorChanged = true;
9941                 }
9942             }
9943         }
9944 
9945         if (what instanceof ParcelableSpan) {
9946             // If this is a span that can be sent to a remote process,
9947             // the current extract editor would be interested in it.
9948             if (ims != null && ims.mExtractedTextRequest != null) {
9949                 if (ims.mBatchEditNesting != 0) {
9950                     if (oldStart >= 0) {
9951                         if (ims.mChangedStart > oldStart) {
9952                             ims.mChangedStart = oldStart;
9953                         }
9954                         if (ims.mChangedStart > oldEnd) {
9955                             ims.mChangedStart = oldEnd;
9956                         }
9957                     }
9958                     if (newStart >= 0) {
9959                         if (ims.mChangedStart > newStart) {
9960                             ims.mChangedStart = newStart;
9961                         }
9962                         if (ims.mChangedStart > newEnd) {
9963                             ims.mChangedStart = newEnd;
9964                         }
9965                     }
9966                 } else {
9967                     if (DEBUG_EXTRACT) {
9968                         Log.v(LOG_TAG, "Span change outside of batch: "
9969                                 + oldStart + "-" + oldEnd + ","
9970                                 + newStart + "-" + newEnd + " " + what);
9971                     }
9972                     ims.mContentChanged = true;
9973                 }
9974             }
9975         }
9976 
9977         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
9978                 && what instanceof SpellCheckSpan) {
9979             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
9980         }
9981     }
9982 
9983     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)9984     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
9985         if (isTemporarilyDetached()) {
9986             // If we are temporarily in the detach state, then do nothing.
9987             super.onFocusChanged(focused, direction, previouslyFocusedRect);
9988             return;
9989         }
9990 
9991         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
9992 
9993         if (focused) {
9994             if (mSpannable != null) {
9995                 MetaKeyKeyListener.resetMetaState(mSpannable);
9996             }
9997         }
9998 
9999         startStopMarquee(focused);
10000 
10001         if (mTransformation != null) {
10002             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
10003         }
10004 
10005         super.onFocusChanged(focused, direction, previouslyFocusedRect);
10006     }
10007 
10008     @Override
onWindowFocusChanged(boolean hasWindowFocus)10009     public void onWindowFocusChanged(boolean hasWindowFocus) {
10010         super.onWindowFocusChanged(hasWindowFocus);
10011 
10012         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
10013 
10014         startStopMarquee(hasWindowFocus);
10015     }
10016 
10017     @Override
onVisibilityChanged(View changedView, int visibility)10018     protected void onVisibilityChanged(View changedView, int visibility) {
10019         super.onVisibilityChanged(changedView, visibility);
10020         if (mEditor != null && visibility != VISIBLE) {
10021             mEditor.hideCursorAndSpanControllers();
10022             stopTextActionMode();
10023         }
10024     }
10025 
10026     /**
10027      * Use {@link BaseInputConnection#removeComposingSpans
10028      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
10029      * state from this text view.
10030      */
clearComposingText()10031     public void clearComposingText() {
10032         if (mText instanceof Spannable) {
10033             BaseInputConnection.removeComposingSpans(mSpannable);
10034         }
10035     }
10036 
10037     @Override
setSelected(boolean selected)10038     public void setSelected(boolean selected) {
10039         boolean wasSelected = isSelected();
10040 
10041         super.setSelected(selected);
10042 
10043         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10044             if (selected) {
10045                 startMarquee();
10046             } else {
10047                 stopMarquee();
10048             }
10049         }
10050     }
10051 
10052     @Override
onTouchEvent(MotionEvent event)10053     public boolean onTouchEvent(MotionEvent event) {
10054         final int action = event.getActionMasked();
10055         if (mEditor != null) {
10056             mEditor.onTouchEvent(event);
10057 
10058             if (mEditor.mSelectionModifierCursorController != null
10059                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
10060                 return true;
10061             }
10062         }
10063 
10064         final boolean superResult = super.onTouchEvent(event);
10065 
10066         /*
10067          * Don't handle the release after a long press, because it will move the selection away from
10068          * whatever the menu action was trying to affect. If the long press should have triggered an
10069          * insertion action mode, we can now actually show it.
10070          */
10071         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
10072             mEditor.mDiscardNextActionUp = false;
10073 
10074             if (mEditor.mIsInsertionActionModeStartPending) {
10075                 mEditor.startInsertionActionMode();
10076                 mEditor.mIsInsertionActionModeStartPending = false;
10077             }
10078             return superResult;
10079         }
10080 
10081         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
10082                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
10083 
10084         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
10085                 && mText instanceof Spannable && mLayout != null) {
10086             boolean handled = false;
10087 
10088             if (mMovement != null) {
10089                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
10090             }
10091 
10092             final boolean textIsSelectable = isTextSelectable();
10093             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
10094                 // The LinkMovementMethod which should handle taps on links has not been installed
10095                 // on non editable text that support text selection.
10096                 // We reproduce its behavior here to open links for these.
10097                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
10098                     getSelectionEnd(), ClickableSpan.class);
10099 
10100                 if (links.length > 0) {
10101                     links[0].onClick(this);
10102                     handled = true;
10103                 }
10104             }
10105 
10106             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
10107                 // Show the IME, except when selecting in read-only text.
10108                 final InputMethodManager imm = InputMethodManager.peekInstance();
10109                 viewClicked(imm);
10110                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10111                     imm.showSoftInput(this, 0);
10112                 }
10113 
10114                 // The above condition ensures that the mEditor is not null
10115                 mEditor.onTouchUpEvent(event);
10116 
10117                 handled = true;
10118             }
10119 
10120             if (handled) {
10121                 return true;
10122             }
10123         }
10124 
10125         return superResult;
10126     }
10127 
10128     @Override
onGenericMotionEvent(MotionEvent event)10129     public boolean onGenericMotionEvent(MotionEvent event) {
10130         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
10131             try {
10132                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
10133                     return true;
10134                 }
10135             } catch (AbstractMethodError ex) {
10136                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
10137                 // Ignore its absence in case third party applications implemented the
10138                 // interface directly.
10139             }
10140         }
10141         return super.onGenericMotionEvent(event);
10142     }
10143 
10144     @Override
onCreateContextMenu(ContextMenu menu)10145     protected void onCreateContextMenu(ContextMenu menu) {
10146         if (mEditor != null) {
10147             mEditor.onCreateContextMenu(menu);
10148         }
10149     }
10150 
10151     @Override
showContextMenu()10152     public boolean showContextMenu() {
10153         if (mEditor != null) {
10154             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
10155         }
10156         return super.showContextMenu();
10157     }
10158 
10159     @Override
showContextMenu(float x, float y)10160     public boolean showContextMenu(float x, float y) {
10161         if (mEditor != null) {
10162             mEditor.setContextMenuAnchor(x, y);
10163         }
10164         return super.showContextMenu(x, y);
10165     }
10166 
10167     /**
10168      * @return True iff this TextView contains a text that can be edited, or if this is
10169      * a selectable TextView.
10170      */
isTextEditable()10171     boolean isTextEditable() {
10172         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
10173     }
10174 
10175     /**
10176      * Returns true, only while processing a touch gesture, if the initial
10177      * touch down event caused focus to move to the text view and as a result
10178      * its selection changed.  Only valid while processing the touch gesture
10179      * of interest, in an editable text view.
10180      */
didTouchFocusSelect()10181     public boolean didTouchFocusSelect() {
10182         return mEditor != null && mEditor.mTouchFocusSelected;
10183     }
10184 
10185     @Override
cancelLongPress()10186     public void cancelLongPress() {
10187         super.cancelLongPress();
10188         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
10189     }
10190 
10191     @Override
onTrackballEvent(MotionEvent event)10192     public boolean onTrackballEvent(MotionEvent event) {
10193         if (mMovement != null && mSpannable != null && mLayout != null) {
10194             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
10195                 return true;
10196             }
10197         }
10198 
10199         return super.onTrackballEvent(event);
10200     }
10201 
10202     /**
10203      * Sets the Scroller used for producing a scrolling animation
10204      *
10205      * @param s A Scroller instance
10206      */
setScroller(Scroller s)10207     public void setScroller(Scroller s) {
10208         mScroller = s;
10209     }
10210 
10211     @Override
getLeftFadingEdgeStrength()10212     protected float getLeftFadingEdgeStrength() {
10213         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
10214             final Marquee marquee = mMarquee;
10215             if (marquee.shouldDrawLeftFade()) {
10216                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
10217             } else {
10218                 return 0.0f;
10219             }
10220         } else if (getLineCount() == 1) {
10221             final float lineLeft = getLayout().getLineLeft(0);
10222             if (lineLeft > mScrollX) return 0.0f;
10223             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
10224         }
10225         return super.getLeftFadingEdgeStrength();
10226     }
10227 
10228     @Override
getRightFadingEdgeStrength()10229     protected float getRightFadingEdgeStrength() {
10230         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
10231             final Marquee marquee = mMarquee;
10232             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
10233         } else if (getLineCount() == 1) {
10234             final float rightEdge = mScrollX +
10235                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
10236             final float lineRight = getLayout().getLineRight(0);
10237             if (lineRight < rightEdge) return 0.0f;
10238             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
10239         }
10240         return super.getRightFadingEdgeStrength();
10241     }
10242 
10243     /**
10244      * Calculates the fading edge strength as the ratio of the distance between two
10245      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
10246      * value for the distance calculation.
10247      *
10248      * @param position1 A horizontal position.
10249      * @param position2 A horizontal position.
10250      * @return Fading edge strength between [0.0f, 1.0f].
10251      */
10252     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)10253     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
10254         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
10255         if (horizontalFadingEdgeLength == 0) return 0.0f;
10256         final float diff = Math.abs(position1 - position2);
10257         if (diff > horizontalFadingEdgeLength) return 1.0f;
10258         return diff / horizontalFadingEdgeLength;
10259     }
10260 
isMarqueeFadeEnabled()10261     private boolean isMarqueeFadeEnabled() {
10262         return mEllipsize == TextUtils.TruncateAt.MARQUEE
10263                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10264     }
10265 
10266     @Override
computeHorizontalScrollRange()10267     protected int computeHorizontalScrollRange() {
10268         if (mLayout != null) {
10269             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
10270                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
10271         }
10272 
10273         return super.computeHorizontalScrollRange();
10274     }
10275 
10276     @Override
computeVerticalScrollRange()10277     protected int computeVerticalScrollRange() {
10278         if (mLayout != null) {
10279             return mLayout.getHeight();
10280         }
10281         return super.computeVerticalScrollRange();
10282     }
10283 
10284     @Override
computeVerticalScrollExtent()10285     protected int computeVerticalScrollExtent() {
10286         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
10287     }
10288 
10289     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)10290     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
10291         super.findViewsWithText(outViews, searched, flags);
10292         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
10293                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
10294             String searchedLowerCase = searched.toString().toLowerCase();
10295             String textLowerCase = mText.toString().toLowerCase();
10296             if (textLowerCase.contains(searchedLowerCase)) {
10297                 outViews.add(this);
10298             }
10299         }
10300     }
10301 
10302     /**
10303      * Type of the text buffer that defines the characteristics of the text such as static,
10304      * styleable, or editable.
10305      */
10306     public enum BufferType {
10307         NORMAL, SPANNABLE, EDITABLE
10308     }
10309 
10310     /**
10311      * Returns the TextView_textColor attribute from the TypedArray, if set, or
10312      * the TextAppearance_textColor from the TextView_textAppearance attribute,
10313      * if TextView_textColor was not set directly.
10314      *
10315      * @removed
10316      */
getTextColors(Context context, TypedArray attrs)10317     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
10318         if (attrs == null) {
10319             // Preserve behavior prior to removal of this API.
10320             throw new NullPointerException();
10321         }
10322 
10323         // It's not safe to use this method from apps. The parameter 'attrs'
10324         // must have been obtained using the TextView filter array which is not
10325         // available to the SDK. As such, we grab a default TypedArray with the
10326         // right filter instead here.
10327         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
10328         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
10329         if (colors == null) {
10330             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
10331             if (ap != 0) {
10332                 final TypedArray appearance = context.obtainStyledAttributes(
10333                         ap, R.styleable.TextAppearance);
10334                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
10335                 appearance.recycle();
10336             }
10337         }
10338         a.recycle();
10339 
10340         return colors;
10341     }
10342 
10343     /**
10344      * Returns the default color from the TextView_textColor attribute from the
10345      * AttributeSet, if set, or the default color from the
10346      * TextAppearance_textColor from the TextView_textAppearance attribute, if
10347      * TextView_textColor was not set directly.
10348      *
10349      * @removed
10350      */
getTextColor(Context context, TypedArray attrs, int def)10351     public static int getTextColor(Context context, TypedArray attrs, int def) {
10352         final ColorStateList colors = getTextColors(context, attrs);
10353         if (colors == null) {
10354             return def;
10355         } else {
10356             return colors.getDefaultColor();
10357         }
10358     }
10359 
10360     @Override
onKeyShortcut(int keyCode, KeyEvent event)10361     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
10362         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
10363             // Handle Ctrl-only shortcuts.
10364             switch (keyCode) {
10365                 case KeyEvent.KEYCODE_A:
10366                     if (canSelectText()) {
10367                         return onTextContextMenuItem(ID_SELECT_ALL);
10368                     }
10369                     break;
10370                 case KeyEvent.KEYCODE_Z:
10371                     if (canUndo()) {
10372                         return onTextContextMenuItem(ID_UNDO);
10373                     }
10374                     break;
10375                 case KeyEvent.KEYCODE_X:
10376                     if (canCut()) {
10377                         return onTextContextMenuItem(ID_CUT);
10378                     }
10379                     break;
10380                 case KeyEvent.KEYCODE_C:
10381                     if (canCopy()) {
10382                         return onTextContextMenuItem(ID_COPY);
10383                     }
10384                     break;
10385                 case KeyEvent.KEYCODE_V:
10386                     if (canPaste()) {
10387                         return onTextContextMenuItem(ID_PASTE);
10388                     }
10389                     break;
10390             }
10391         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
10392             // Handle Ctrl-Shift shortcuts.
10393             switch (keyCode) {
10394                 case KeyEvent.KEYCODE_Z:
10395                     if (canRedo()) {
10396                         return onTextContextMenuItem(ID_REDO);
10397                     }
10398                     break;
10399                 case KeyEvent.KEYCODE_V:
10400                     if (canPaste()) {
10401                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
10402                     }
10403             }
10404         }
10405         return super.onKeyShortcut(keyCode, event);
10406     }
10407 
10408     /**
10409      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
10410      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
10411      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
10412      * sufficient.
10413      */
canSelectText()10414     boolean canSelectText() {
10415         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
10416     }
10417 
10418     /**
10419      * Test based on the <i>intrinsic</i> charateristics of the TextView.
10420      * The text must be spannable and the movement method must allow for arbitary selection.
10421      *
10422      * See also {@link #canSelectText()}.
10423      */
textCanBeSelected()10424     boolean textCanBeSelected() {
10425         // prepareCursorController() relies on this method.
10426         // If you change this condition, make sure prepareCursorController is called anywhere
10427         // the value of this condition might be changed.
10428         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
10429         return isTextEditable()
10430                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
10431     }
10432 
getTextServicesLocale(boolean allowNullLocale)10433     private Locale getTextServicesLocale(boolean allowNullLocale) {
10434         // Start fetching the text services locale asynchronously.
10435         updateTextServicesLocaleAsync();
10436         // If !allowNullLocale and there is no cached text services locale, just return the default
10437         // locale.
10438         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
10439                 : mCurrentSpellCheckerLocaleCache;
10440     }
10441 
10442     /**
10443      * This is a temporary method. Future versions may support multi-locale text.
10444      * Caveat: This method may not return the latest text services locale, but this should be
10445      * acceptable and it's more important to make this method asynchronous.
10446      *
10447      * @return The locale that should be used for a word iterator
10448      * in this TextView, based on the current spell checker settings,
10449      * the current IME's locale, or the system default locale.
10450      * Please note that a word iterator in this TextView is different from another word iterator
10451      * used by SpellChecker.java of TextView. This method should be used for the former.
10452      * @hide
10453      */
10454     // TODO: Support multi-locale
10455     // TODO: Update the text services locale immediately after the keyboard locale is switched
10456     // by catching intent of keyboard switch event
getTextServicesLocale()10457     public Locale getTextServicesLocale() {
10458         return getTextServicesLocale(false /* allowNullLocale */);
10459     }
10460 
10461     /**
10462      * @return {@code true} if this TextView is specialized for showing and interacting with the
10463      * extracted text in a full-screen input method.
10464      * @hide
10465      */
isInExtractedMode()10466     public boolean isInExtractedMode() {
10467         return false;
10468     }
10469 
10470     /**
10471      * @return {@code true} if this widget supports auto-sizing text and has been configured to
10472      * auto-size.
10473      */
isAutoSizeEnabled()10474     private boolean isAutoSizeEnabled() {
10475         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
10476     }
10477 
10478     /**
10479      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
10480      * @hide
10481      */
supportsAutoSizeText()10482     protected boolean supportsAutoSizeText() {
10483         return true;
10484     }
10485 
10486     /**
10487      * This is a temporary method. Future versions may support multi-locale text.
10488      * Caveat: This method may not return the latest spell checker locale, but this should be
10489      * acceptable and it's more important to make this method asynchronous.
10490      *
10491      * @return The locale that should be used for a spell checker in this TextView,
10492      * based on the current spell checker settings, the current IME's locale, or the system default
10493      * locale.
10494      * @hide
10495      */
getSpellCheckerLocale()10496     public Locale getSpellCheckerLocale() {
10497         return getTextServicesLocale(true /* allowNullLocale */);
10498     }
10499 
updateTextServicesLocaleAsync()10500     private void updateTextServicesLocaleAsync() {
10501         // AsyncTask.execute() uses a serial executor which means we don't have
10502         // to lock around updateTextServicesLocaleLocked() to prevent it from
10503         // being executed n times in parallel.
10504         AsyncTask.execute(new Runnable() {
10505             @Override
10506             public void run() {
10507                 updateTextServicesLocaleLocked();
10508             }
10509         });
10510     }
10511 
updateTextServicesLocaleLocked()10512     private void updateTextServicesLocaleLocked() {
10513         final TextServicesManager textServicesManager = (TextServicesManager)
10514                 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
10515         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
10516         final Locale locale;
10517         if (subtype != null) {
10518             locale = subtype.getLocaleObject();
10519         } else {
10520             locale = null;
10521         }
10522         mCurrentSpellCheckerLocaleCache = locale;
10523     }
10524 
onLocaleChanged()10525     void onLocaleChanged() {
10526         mEditor.onLocaleChanged();
10527     }
10528 
10529     /**
10530      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
10531      * Made available to achieve a consistent behavior.
10532      * @hide
10533      */
getWordIterator()10534     public WordIterator getWordIterator() {
10535         if (mEditor != null) {
10536             return mEditor.getWordIterator();
10537         } else {
10538             return null;
10539         }
10540     }
10541 
10542     /** @hide */
10543     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)10544     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
10545         super.onPopulateAccessibilityEventInternal(event);
10546 
10547         final CharSequence text = getTextForAccessibility();
10548         if (!TextUtils.isEmpty(text)) {
10549             event.getText().add(text);
10550         }
10551     }
10552 
10553     @Override
getAccessibilityClassName()10554     public CharSequence getAccessibilityClassName() {
10555         return TextView.class.getName();
10556     }
10557 
10558     @Override
onProvideStructure(ViewStructure structure)10559     public void onProvideStructure(ViewStructure structure) {
10560         super.onProvideStructure(structure);
10561         onProvideAutoStructureForAssistOrAutofill(structure, false);
10562     }
10563 
10564     @Override
onProvideAutofillStructure(ViewStructure structure, int flags)10565     public void onProvideAutofillStructure(ViewStructure structure, int flags) {
10566         super.onProvideAutofillStructure(structure, flags);
10567         onProvideAutoStructureForAssistOrAutofill(structure, true);
10568     }
10569 
onProvideAutoStructureForAssistOrAutofill(ViewStructure structure, boolean forAutofill)10570     private void onProvideAutoStructureForAssistOrAutofill(ViewStructure structure,
10571             boolean forAutofill) {
10572         final boolean isPassword = hasPasswordTransformationMethod()
10573                 || isPasswordInputType(getInputType());
10574         if (forAutofill) {
10575             structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
10576             if (mTextId != ResourceId.ID_NULL) {
10577                 try {
10578                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
10579                 } catch (Resources.NotFoundException e) {
10580                     if (android.view.autofill.Helper.sVerbose) {
10581                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
10582                                 + mTextId + ": " + e.getMessage());
10583                     }
10584                 }
10585             }
10586         }
10587 
10588         if (!isPassword || forAutofill) {
10589             if (mLayout == null) {
10590                 assumeLayout();
10591             }
10592             Layout layout = mLayout;
10593             final int lineCount = layout.getLineCount();
10594             if (lineCount <= 1) {
10595                 // Simple case: this is a single line.
10596                 final CharSequence text = getText();
10597                 if (forAutofill) {
10598                     structure.setText(text);
10599                 } else {
10600                     structure.setText(text, getSelectionStart(), getSelectionEnd());
10601                 }
10602             } else {
10603                 // Complex case: multi-line, could be scrolled or within a scroll container
10604                 // so some lines are not visible.
10605                 final int[] tmpCords = new int[2];
10606                 getLocationInWindow(tmpCords);
10607                 final int topWindowLocation = tmpCords[1];
10608                 View root = this;
10609                 ViewParent viewParent = getParent();
10610                 while (viewParent instanceof View) {
10611                     root = (View) viewParent;
10612                     viewParent = root.getParent();
10613                 }
10614                 final int windowHeight = root.getHeight();
10615                 final int topLine;
10616                 final int bottomLine;
10617                 if (topWindowLocation >= 0) {
10618                     // The top of the view is fully within its window; start text at line 0.
10619                     topLine = getLineAtCoordinateUnclamped(0);
10620                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
10621                 } else {
10622                     // The top of hte window has scrolled off the top of the window; figure out
10623                     // the starting line for this.
10624                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
10625                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
10626                 }
10627                 // We want to return some contextual lines above/below the lines that are
10628                 // actually visible.
10629                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
10630                 if (expandedTopLine < 0) {
10631                     expandedTopLine = 0;
10632                 }
10633                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
10634                 if (expandedBottomLine >= lineCount) {
10635                     expandedBottomLine = lineCount - 1;
10636                 }
10637 
10638                 // Convert lines into character offsets.
10639                 int expandedTopChar = layout.getLineStart(expandedTopLine);
10640                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
10641 
10642                 // Take into account selection -- if there is a selection, we need to expand
10643                 // the text we are returning to include that selection.
10644                 final int selStart = getSelectionStart();
10645                 final int selEnd = getSelectionEnd();
10646                 if (selStart < selEnd) {
10647                     if (selStart < expandedTopChar) {
10648                         expandedTopChar = selStart;
10649                     }
10650                     if (selEnd > expandedBottomChar) {
10651                         expandedBottomChar = selEnd;
10652                     }
10653                 }
10654 
10655                 // Get the text and trim it to the range we are reporting.
10656                 CharSequence text = getText();
10657                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
10658                     text = text.subSequence(expandedTopChar, expandedBottomChar);
10659                 }
10660 
10661                 if (forAutofill) {
10662                     structure.setText(text);
10663                 } else {
10664                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
10665 
10666                     final int[] lineOffsets = new int[bottomLine - topLine + 1];
10667                     final int[] lineBaselines = new int[bottomLine - topLine + 1];
10668                     final int baselineOffset = getBaselineOffset();
10669                     for (int i = topLine; i <= bottomLine; i++) {
10670                         lineOffsets[i - topLine] = layout.getLineStart(i);
10671                         lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
10672                     }
10673                     structure.setTextLines(lineOffsets, lineBaselines);
10674                 }
10675             }
10676 
10677             if (!forAutofill) {
10678                 // Extract style information that applies to the TextView as a whole.
10679                 int style = 0;
10680                 int typefaceStyle = getTypefaceStyle();
10681                 if ((typefaceStyle & Typeface.BOLD) != 0) {
10682                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10683                 }
10684                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
10685                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
10686                 }
10687 
10688                 // Global styles can also be set via TextView.setPaintFlags().
10689                 int paintFlags = mTextPaint.getFlags();
10690                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
10691                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10692                 }
10693                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
10694                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
10695                 }
10696                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
10697                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
10698                 }
10699 
10700                 // TextView does not have its own text background color. A background is either part
10701                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
10702                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
10703                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
10704             } else {
10705                 structure.setMinTextEms(getMinEms());
10706                 structure.setMaxTextEms(getMaxEms());
10707                 int maxLength = -1;
10708                 for (InputFilter filter: getFilters()) {
10709                     if (filter instanceof InputFilter.LengthFilter) {
10710                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
10711                         break;
10712                     }
10713                 }
10714                 structure.setMaxTextLength(maxLength);
10715             }
10716         }
10717         structure.setHint(getHint());
10718         structure.setInputType(getInputType());
10719     }
10720 
canRequestAutofill()10721     boolean canRequestAutofill() {
10722         if (!isAutofillable()) {
10723             return false;
10724         }
10725         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10726         if (afm != null) {
10727             return afm.isEnabled();
10728         }
10729         return false;
10730     }
10731 
requestAutofill()10732     private void requestAutofill() {
10733         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10734         if (afm != null) {
10735             afm.requestAutofill(this);
10736         }
10737     }
10738 
10739     @Override
autofill(AutofillValue value)10740     public void autofill(AutofillValue value) {
10741         if (!value.isText() || !isTextEditable()) {
10742             Log.w(LOG_TAG, value + " could not be autofilled into " + this);
10743             return;
10744         }
10745 
10746         final CharSequence autofilledValue = value.getTextValue();
10747 
10748         // First autofill it...
10749         setText(autofilledValue, mBufferType, true, 0);
10750 
10751         // ...then move cursor to the end.
10752         final CharSequence text = getText();
10753         if ((text instanceof Spannable)) {
10754             Selection.setSelection((Spannable) text, text.length());
10755         }
10756     }
10757 
10758     @Override
getAutofillType()10759     public @AutofillType int getAutofillType() {
10760         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
10761     }
10762 
10763     /**
10764      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
10765      * {@code char}s if longer.
10766      *
10767      * @return current text, {@code null} if the text is not editable
10768      *
10769      * @see View#getAutofillValue()
10770      */
10771     @Override
10772     @Nullable
getAutofillValue()10773     public AutofillValue getAutofillValue() {
10774         if (isTextEditable()) {
10775             final CharSequence text = TextUtils.trimToParcelableSize(getText());
10776             return AutofillValue.forText(text);
10777         }
10778         return null;
10779     }
10780 
10781     /** @hide */
10782     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)10783     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
10784         super.onInitializeAccessibilityEventInternal(event);
10785 
10786         final boolean isPassword = hasPasswordTransformationMethod();
10787         event.setPassword(isPassword);
10788 
10789         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
10790             event.setFromIndex(Selection.getSelectionStart(mText));
10791             event.setToIndex(Selection.getSelectionEnd(mText));
10792             event.setItemCount(mText.length());
10793         }
10794     }
10795 
10796     /** @hide */
10797     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)10798     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
10799         super.onInitializeAccessibilityNodeInfoInternal(info);
10800 
10801         final boolean isPassword = hasPasswordTransformationMethod();
10802         info.setPassword(isPassword);
10803         info.setText(getTextForAccessibility());
10804         info.setHintText(mHint);
10805         info.setShowingHintText(isShowingHint());
10806 
10807         if (mBufferType == BufferType.EDITABLE) {
10808             info.setEditable(true);
10809             if (isEnabled()) {
10810                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
10811             }
10812         }
10813 
10814         if (mEditor != null) {
10815             info.setInputType(mEditor.mInputType);
10816 
10817             if (mEditor.mError != null) {
10818                 info.setContentInvalid(true);
10819                 info.setError(mEditor.mError);
10820             }
10821         }
10822 
10823         if (!TextUtils.isEmpty(mText)) {
10824             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
10825             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
10826             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
10827                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
10828                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
10829                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
10830                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
10831             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
10832             info.setAvailableExtraData(
10833                     Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
10834         }
10835 
10836         if (isFocused()) {
10837             if (canCopy()) {
10838                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
10839             }
10840             if (canPaste()) {
10841                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
10842             }
10843             if (canCut()) {
10844                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
10845             }
10846             if (canShare()) {
10847                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
10848                         ACCESSIBILITY_ACTION_SHARE,
10849                         getResources().getString(com.android.internal.R.string.share)));
10850             }
10851             if (canProcessText()) {  // also implies mEditor is not null.
10852                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
10853             }
10854         }
10855 
10856         // Check for known input filter types.
10857         final int numFilters = mFilters.length;
10858         for (int i = 0; i < numFilters; i++) {
10859             final InputFilter filter = mFilters[i];
10860             if (filter instanceof InputFilter.LengthFilter) {
10861                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
10862             }
10863         }
10864 
10865         if (!isSingleLine()) {
10866             info.setMultiLine(true);
10867         }
10868     }
10869 
10870     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)10871     public void addExtraDataToAccessibilityNodeInfo(
10872             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
10873         // The only extra data we support requires arguments.
10874         if (arguments == null) {
10875             return;
10876         }
10877         if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
10878             int positionInfoStartIndex = arguments.getInt(
10879                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
10880             int positionInfoLength = arguments.getInt(
10881                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
10882             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
10883                     || (positionInfoStartIndex >= mText.length())) {
10884                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
10885                 return;
10886             }
10887             RectF[] boundingRects = new RectF[positionInfoLength];
10888             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
10889             populateCharacterBounds(builder, positionInfoStartIndex,
10890                     positionInfoStartIndex + positionInfoLength,
10891                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
10892             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
10893             for (int i = 0; i < positionInfoLength; i++) {
10894                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
10895                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
10896                     RectF bounds = cursorAnchorInfo
10897                             .getCharacterBounds(positionInfoStartIndex + i);
10898                     if (bounds != null) {
10899                         mapRectFromViewToScreenCoords(bounds, true);
10900                         boundingRects[i] = bounds;
10901                     }
10902                 }
10903             }
10904             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
10905         }
10906     }
10907 
10908     /**
10909      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
10910      *
10911      * @param builder The builder to populate
10912      * @param startIndex The starting character index to populate
10913      * @param endIndex The ending character index to populate
10914      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
10915      * content
10916      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
10917      * @hide
10918      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)10919     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
10920             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
10921             float viewportToContentVerticalOffset) {
10922         final int minLine = mLayout.getLineForOffset(startIndex);
10923         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
10924         for (int line = minLine; line <= maxLine; ++line) {
10925             final int lineStart = mLayout.getLineStart(line);
10926             final int lineEnd = mLayout.getLineEnd(line);
10927             final int offsetStart = Math.max(lineStart, startIndex);
10928             final int offsetEnd = Math.min(lineEnd, endIndex);
10929             final boolean ltrLine =
10930                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
10931             final float[] widths = new float[offsetEnd - offsetStart];
10932             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
10933             final float top = mLayout.getLineTop(line);
10934             final float bottom = mLayout.getLineBottom(line);
10935             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
10936                 final float charWidth = widths[offset - offsetStart];
10937                 final boolean isRtl = mLayout.isRtlCharAt(offset);
10938                 final float primary = mLayout.getPrimaryHorizontal(offset);
10939                 final float secondary = mLayout.getSecondaryHorizontal(offset);
10940                 // TODO: This doesn't work perfectly for text with custom styles and
10941                 // TAB chars.
10942                 final float left;
10943                 final float right;
10944                 if (ltrLine) {
10945                     if (isRtl) {
10946                         left = secondary - charWidth;
10947                         right = secondary;
10948                     } else {
10949                         left = primary;
10950                         right = primary + charWidth;
10951                     }
10952                 } else {
10953                     if (!isRtl) {
10954                         left = secondary;
10955                         right = secondary + charWidth;
10956                     } else {
10957                         left = primary - charWidth;
10958                         right = primary;
10959                     }
10960                 }
10961                 // TODO: Check top-right and bottom-left as well.
10962                 final float localLeft = left + viewportToContentHorizontalOffset;
10963                 final float localRight = right + viewportToContentHorizontalOffset;
10964                 final float localTop = top + viewportToContentVerticalOffset;
10965                 final float localBottom = bottom + viewportToContentVerticalOffset;
10966                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
10967                 final boolean isBottomRightVisible =
10968                         isPositionVisible(localRight, localBottom);
10969                 int characterBoundsFlags = 0;
10970                 if (isTopLeftVisible || isBottomRightVisible) {
10971                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
10972                 }
10973                 if (!isTopLeftVisible || !isBottomRightVisible) {
10974                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
10975                 }
10976                 if (isRtl) {
10977                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
10978                 }
10979                 // Here offset is the index in Java chars.
10980                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
10981                         localBottom, characterBoundsFlags);
10982             }
10983         }
10984     }
10985 
10986     /**
10987      * @hide
10988      */
isPositionVisible(final float positionX, final float positionY)10989     public boolean isPositionVisible(final float positionX, final float positionY) {
10990         synchronized (TEMP_POSITION) {
10991             final float[] position = TEMP_POSITION;
10992             position[0] = positionX;
10993             position[1] = positionY;
10994             View view = this;
10995 
10996             while (view != null) {
10997                 if (view != this) {
10998                     // Local scroll is already taken into account in positionX/Y
10999                     position[0] -= view.getScrollX();
11000                     position[1] -= view.getScrollY();
11001                 }
11002 
11003                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
11004                         || position[1] > view.getHeight()) {
11005                     return false;
11006                 }
11007 
11008                 if (!view.getMatrix().isIdentity()) {
11009                     view.getMatrix().mapPoints(position);
11010                 }
11011 
11012                 position[0] += view.getLeft();
11013                 position[1] += view.getTop();
11014 
11015                 final ViewParent parent = view.getParent();
11016                 if (parent instanceof View) {
11017                     view = (View) parent;
11018                 } else {
11019                     // We've reached the ViewRoot, stop iterating
11020                     view = null;
11021                 }
11022             }
11023         }
11024 
11025         // We've been able to walk up the view hierarchy and the position was never clipped
11026         return true;
11027     }
11028 
11029     /**
11030      * Performs an accessibility action after it has been offered to the
11031      * delegate.
11032      *
11033      * @hide
11034      */
11035     @Override
performAccessibilityActionInternal(int action, Bundle arguments)11036     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
11037         if (mEditor != null
11038                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
11039             return true;
11040         }
11041         switch (action) {
11042             case AccessibilityNodeInfo.ACTION_CLICK: {
11043                 return performAccessibilityActionClick(arguments);
11044             }
11045             case AccessibilityNodeInfo.ACTION_COPY: {
11046                 if (isFocused() && canCopy()) {
11047                     if (onTextContextMenuItem(ID_COPY)) {
11048                         return true;
11049                     }
11050                 }
11051             } return false;
11052             case AccessibilityNodeInfo.ACTION_PASTE: {
11053                 if (isFocused() && canPaste()) {
11054                     if (onTextContextMenuItem(ID_PASTE)) {
11055                         return true;
11056                     }
11057                 }
11058             } return false;
11059             case AccessibilityNodeInfo.ACTION_CUT: {
11060                 if (isFocused() && canCut()) {
11061                     if (onTextContextMenuItem(ID_CUT)) {
11062                         return true;
11063                     }
11064                 }
11065             } return false;
11066             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
11067                 ensureIterableTextForAccessibilitySelectable();
11068                 CharSequence text = getIterableTextForAccessibility();
11069                 if (text == null) {
11070                     return false;
11071                 }
11072                 final int start = (arguments != null) ? arguments.getInt(
11073                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
11074                 final int end = (arguments != null) ? arguments.getInt(
11075                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
11076                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
11077                     // No arguments clears the selection.
11078                     if (start == end && end == -1) {
11079                         Selection.removeSelection((Spannable) text);
11080                         return true;
11081                     }
11082                     if (start >= 0 && start <= end && end <= text.length()) {
11083                         Selection.setSelection((Spannable) text, start, end);
11084                         // Make sure selection mode is engaged.
11085                         if (mEditor != null) {
11086                             mEditor.startSelectionActionModeAsync(false);
11087                         }
11088                         return true;
11089                     }
11090                 }
11091             } return false;
11092             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
11093             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
11094                 ensureIterableTextForAccessibilitySelectable();
11095                 return super.performAccessibilityActionInternal(action, arguments);
11096             }
11097             case ACCESSIBILITY_ACTION_SHARE: {
11098                 if (isFocused() && canShare()) {
11099                     if (onTextContextMenuItem(ID_SHARE)) {
11100                         return true;
11101                     }
11102                 }
11103             } return false;
11104             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
11105                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
11106                     return false;
11107                 }
11108                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
11109                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
11110                 setText(text);
11111                 if (mText != null) {
11112                     int updatedTextLength = mText.length();
11113                     if (updatedTextLength > 0) {
11114                         Selection.setSelection(mSpannable, updatedTextLength);
11115                     }
11116                 }
11117             } return true;
11118             default: {
11119                 return super.performAccessibilityActionInternal(action, arguments);
11120             }
11121         }
11122     }
11123 
performAccessibilityActionClick(Bundle arguments)11124     private boolean performAccessibilityActionClick(Bundle arguments) {
11125         boolean handled = false;
11126 
11127         if (!isEnabled()) {
11128             return false;
11129         }
11130 
11131         if (isClickable() || isLongClickable()) {
11132             // Simulate View.onTouchEvent for an ACTION_UP event
11133             if (isFocusable() && !isFocused()) {
11134                 requestFocus();
11135             }
11136 
11137             performClick();
11138             handled = true;
11139         }
11140 
11141         // Show the IME, except when selecting in read-only text.
11142         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
11143                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
11144             final InputMethodManager imm = InputMethodManager.peekInstance();
11145             viewClicked(imm);
11146             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
11147                 handled |= imm.showSoftInput(this, 0);
11148             }
11149         }
11150 
11151         return handled;
11152     }
11153 
hasSpannableText()11154     private boolean hasSpannableText() {
11155         return mText != null && mText instanceof Spannable;
11156     }
11157 
11158     /** @hide */
11159     @Override
sendAccessibilityEventInternal(int eventType)11160     public void sendAccessibilityEventInternal(int eventType) {
11161         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
11162             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
11163         }
11164 
11165         super.sendAccessibilityEventInternal(eventType);
11166     }
11167 
11168     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)11169     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
11170         // Do not send scroll events since first they are not interesting for
11171         // accessibility and second such events a generated too frequently.
11172         // For details see the implementation of bringTextIntoView().
11173         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
11174             return;
11175         }
11176         super.sendAccessibilityEventUnchecked(event);
11177     }
11178 
11179     /**
11180      * Returns the text that should be exposed to accessibility services.
11181      * <p>
11182      * This approximates what is displayed visually. If the user has specified
11183      * that accessibility services should speak passwords, this method will
11184      * bypass any password transformation method and return unobscured text.
11185      *
11186      * @return the text that should be exposed to accessibility services, may
11187      *         be {@code null} if no text is set
11188      */
11189     @Nullable
getTextForAccessibility()11190     private CharSequence getTextForAccessibility() {
11191         // If the text is empty, we must be showing the hint text.
11192         if (TextUtils.isEmpty(mText)) {
11193             return mHint;
11194         }
11195 
11196         // Otherwise, return whatever text is being displayed.
11197         return TextUtils.trimToParcelableSize(mTransformed);
11198     }
11199 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)11200     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
11201             int fromIndex, int removedCount, int addedCount) {
11202         AccessibilityEvent event =
11203                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
11204         event.setFromIndex(fromIndex);
11205         event.setRemovedCount(removedCount);
11206         event.setAddedCount(addedCount);
11207         event.setBeforeText(beforeText);
11208         sendAccessibilityEventUnchecked(event);
11209     }
11210 
11211     /**
11212      * Returns whether this text view is a current input method target.  The
11213      * default implementation just checks with {@link InputMethodManager}.
11214      * @return True if the TextView is a current input method target; false otherwise.
11215      */
isInputMethodTarget()11216     public boolean isInputMethodTarget() {
11217         InputMethodManager imm = InputMethodManager.peekInstance();
11218         return imm != null && imm.isActive(this);
11219     }
11220 
11221     static final int ID_SELECT_ALL = android.R.id.selectAll;
11222     static final int ID_UNDO = android.R.id.undo;
11223     static final int ID_REDO = android.R.id.redo;
11224     static final int ID_CUT = android.R.id.cut;
11225     static final int ID_COPY = android.R.id.copy;
11226     static final int ID_PASTE = android.R.id.paste;
11227     static final int ID_SHARE = android.R.id.shareText;
11228     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
11229     static final int ID_REPLACE = android.R.id.replaceText;
11230     static final int ID_ASSIST = android.R.id.textAssist;
11231     static final int ID_AUTOFILL = android.R.id.autofill;
11232 
11233     /**
11234      * Called when a context menu option for the text view is selected.  Currently
11235      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
11236      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
11237      *
11238      * @return true if the context menu item action was performed.
11239      */
onTextContextMenuItem(int id)11240     public boolean onTextContextMenuItem(int id) {
11241         int min = 0;
11242         int max = mText.length();
11243 
11244         if (isFocused()) {
11245             final int selStart = getSelectionStart();
11246             final int selEnd = getSelectionEnd();
11247 
11248             min = Math.max(0, Math.min(selStart, selEnd));
11249             max = Math.max(0, Math.max(selStart, selEnd));
11250         }
11251 
11252         switch (id) {
11253             case ID_SELECT_ALL:
11254                 final boolean hadSelection = hasSelection();
11255                 selectAllText();
11256                 if (mEditor != null && hadSelection) {
11257                     mEditor.invalidateActionModeAsync();
11258                 }
11259                 return true;
11260 
11261             case ID_UNDO:
11262                 if (mEditor != null) {
11263                     mEditor.undo();
11264                 }
11265                 return true;  // Returns true even if nothing was undone.
11266 
11267             case ID_REDO:
11268                 if (mEditor != null) {
11269                     mEditor.redo();
11270                 }
11271                 return true;  // Returns true even if nothing was undone.
11272 
11273             case ID_PASTE:
11274                 paste(min, max, true /* withFormatting */);
11275                 return true;
11276 
11277             case ID_PASTE_AS_PLAIN_TEXT:
11278                 paste(min, max, false /* withFormatting */);
11279                 return true;
11280 
11281             case ID_CUT:
11282                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
11283                 if (setPrimaryClip(cutData)) {
11284                     deleteText_internal(min, max);
11285                 } else {
11286                     Toast.makeText(getContext(),
11287                             com.android.internal.R.string.failed_to_copy_to_clipboard,
11288                             Toast.LENGTH_SHORT).show();
11289                 }
11290                 return true;
11291 
11292             case ID_COPY:
11293                 // For link action mode in a non-selectable/non-focusable TextView,
11294                 // make sure that we set the appropriate min/max.
11295                 final int selStart = getSelectionStart();
11296                 final int selEnd = getSelectionEnd();
11297                 min = Math.max(0, Math.min(selStart, selEnd));
11298                 max = Math.max(0, Math.max(selStart, selEnd));
11299                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
11300                 if (setPrimaryClip(copyData)) {
11301                     stopTextActionMode();
11302                 } else {
11303                     Toast.makeText(getContext(),
11304                             com.android.internal.R.string.failed_to_copy_to_clipboard,
11305                             Toast.LENGTH_SHORT).show();
11306                 }
11307                 return true;
11308 
11309             case ID_REPLACE:
11310                 if (mEditor != null) {
11311                     mEditor.replace();
11312                 }
11313                 return true;
11314 
11315             case ID_SHARE:
11316                 shareSelectedText();
11317                 return true;
11318 
11319             case ID_AUTOFILL:
11320                 requestAutofill();
11321                 stopTextActionMode();
11322                 return true;
11323         }
11324         return false;
11325     }
11326 
getTransformedText(int start, int end)11327     CharSequence getTransformedText(int start, int end) {
11328         return removeSuggestionSpans(mTransformed.subSequence(start, end));
11329     }
11330 
11331     @Override
performLongClick()11332     public boolean performLongClick() {
11333         boolean handled = false;
11334         boolean performedHapticFeedback = false;
11335 
11336         if (mEditor != null) {
11337             mEditor.mIsBeingLongClicked = true;
11338         }
11339 
11340         if (super.performLongClick()) {
11341             handled = true;
11342             performedHapticFeedback = true;
11343         }
11344 
11345         if (mEditor != null) {
11346             handled |= mEditor.performLongClick(handled);
11347             mEditor.mIsBeingLongClicked = false;
11348         }
11349 
11350         if (handled) {
11351             if (!performedHapticFeedback) {
11352               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
11353             }
11354             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
11355         } else {
11356             MetricsLogger.action(
11357                     mContext,
11358                     MetricsEvent.TEXT_LONGPRESS,
11359                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
11360         }
11361 
11362         return handled;
11363     }
11364 
11365     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)11366     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
11367         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
11368         if (mEditor != null) {
11369             mEditor.onScrollChanged();
11370         }
11371     }
11372 
11373     /**
11374      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
11375      * by the IME or by the spell checker as the user types. This is done by adding
11376      * {@link SuggestionSpan}s to the text.
11377      *
11378      * When suggestions are enabled (default), this list of suggestions will be displayed when the
11379      * user asks for them on these parts of the text. This value depends on the inputType of this
11380      * TextView.
11381      *
11382      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
11383      *
11384      * In addition, the type variation must be one of
11385      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
11386      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
11387      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
11388      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
11389      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
11390      *
11391      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
11392      *
11393      * @return true if the suggestions popup window is enabled, based on the inputType.
11394      */
isSuggestionsEnabled()11395     public boolean isSuggestionsEnabled() {
11396         if (mEditor == null) return false;
11397         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
11398             return false;
11399         }
11400         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
11401 
11402         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
11403         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
11404                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
11405                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
11406                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
11407                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
11408     }
11409 
11410     /**
11411      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
11412      * selection is initiated in this View.
11413      *
11414      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
11415      * Paste, Replace and Share actions, depending on what this View supports.
11416      *
11417      * <p>A custom implementation can add new entries in the default menu in its
11418      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
11419      * method. The default actions can also be removed from the menu using
11420      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
11421      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
11422      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
11423      *
11424      * <p>Returning false from
11425      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
11426      * will prevent the action mode from being started.
11427      *
11428      * <p>Action click events should be handled by the custom implementation of
11429      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
11430      * android.view.MenuItem)}.
11431      *
11432      * <p>Note that text selection mode is not started when a TextView receives focus and the
11433      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
11434      * that case, to allow for quick replacement.
11435      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)11436     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
11437         createEditorIfNeeded();
11438         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
11439     }
11440 
11441     /**
11442      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
11443      *
11444      * @return The current custom selection callback.
11445      */
getCustomSelectionActionModeCallback()11446     public ActionMode.Callback getCustomSelectionActionModeCallback() {
11447         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
11448     }
11449 
11450     /**
11451      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
11452      * insertion is initiated in this View.
11453      * The standard implementation populates the menu with a subset of Select All,
11454      * Paste and Replace actions, depending on what this View supports.
11455      *
11456      * <p>A custom implementation can add new entries in the default menu in its
11457      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
11458      * android.view.Menu)} method. The default actions can also be removed from the menu using
11459      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
11460      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
11461      *
11462      * <p>Returning false from
11463      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
11464      * android.view.Menu)} will prevent the action mode from being started.</p>
11465      *
11466      * <p>Action click events should be handled by the custom implementation of
11467      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
11468      * android.view.MenuItem)}.</p>
11469      *
11470      * <p>Note that text insertion mode is not started when a TextView receives focus and the
11471      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
11472      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)11473     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
11474         createEditorIfNeeded();
11475         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
11476     }
11477 
11478     /**
11479      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
11480      *
11481      * @return The current custom insertion callback.
11482      */
getCustomInsertionActionModeCallback()11483     public ActionMode.Callback getCustomInsertionActionModeCallback() {
11484         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
11485     }
11486 
11487     /**
11488      * Sets the {@link TextClassifier} for this TextView.
11489      */
setTextClassifier(@ullable TextClassifier textClassifier)11490     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
11491         mTextClassifier = textClassifier;
11492     }
11493 
11494     /**
11495      * Returns the {@link TextClassifier} used by this TextView.
11496      * If no TextClassifier has been set, this TextView uses the default set by the
11497      * {@link TextClassificationManager}.
11498      */
11499     @NonNull
getTextClassifier()11500     public TextClassifier getTextClassifier() {
11501         if (mTextClassifier == null) {
11502             final TextClassificationManager tcm =
11503                     mContext.getSystemService(TextClassificationManager.class);
11504             if (tcm != null) {
11505                 return tcm.getTextClassifier();
11506             }
11507             return TextClassifier.NO_OP;
11508         }
11509         return mTextClassifier;
11510     }
11511 
11512     /**
11513      * Returns a session-aware text classifier.
11514      * This method creates one if none already exists or the current one is destroyed.
11515      */
11516     @NonNull
getTextClassificationSession()11517     TextClassifier getTextClassificationSession() {
11518         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
11519             final TextClassificationManager tcm =
11520                     mContext.getSystemService(TextClassificationManager.class);
11521             if (tcm != null) {
11522                 final String widgetType;
11523                 if (isTextEditable()) {
11524                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
11525                 } else if (isTextSelectable()) {
11526                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
11527                 } else {
11528                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
11529                 }
11530                 final TextClassificationContext textClassificationContext =
11531                         new TextClassificationContext.Builder(
11532                                 mContext.getPackageName(), widgetType)
11533                                 .build();
11534                 if (mTextClassifier != null) {
11535                     mTextClassificationSession = tcm.createTextClassificationSession(
11536                             textClassificationContext, mTextClassifier);
11537                 } else {
11538                     mTextClassificationSession = tcm.createTextClassificationSession(
11539                             textClassificationContext);
11540                 }
11541             } else {
11542                 mTextClassificationSession = TextClassifier.NO_OP;
11543             }
11544         }
11545         return mTextClassificationSession;
11546     }
11547 
11548     /**
11549      * Returns true if this TextView uses a no-op TextClassifier.
11550      */
usesNoOpTextClassifier()11551     boolean usesNoOpTextClassifier() {
11552         return getTextClassifier() == TextClassifier.NO_OP;
11553     }
11554 
11555 
11556     /**
11557      * Starts an ActionMode for the specified TextLinkSpan.
11558      *
11559      * @return Whether or not we're attempting to start the action mode.
11560      * @hide
11561      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)11562     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
11563         Preconditions.checkNotNull(clickedSpan);
11564 
11565         if (!(mText instanceof Spanned)) {
11566             return false;
11567         }
11568 
11569         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
11570         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
11571 
11572         if (start < 0 || end > mText.length() || start >= end) {
11573             return false;
11574         }
11575 
11576         createEditorIfNeeded();
11577         mEditor.startLinkActionModeAsync(start, end);
11578         return true;
11579     }
11580 
11581     /**
11582      * Handles a click on the specified TextLinkSpan.
11583      *
11584      * @return Whether or not the click is being handled.
11585      * @hide
11586      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)11587     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
11588         Preconditions.checkNotNull(clickedSpan);
11589         if (mText instanceof Spanned) {
11590             final Spanned spanned = (Spanned) mText;
11591             final int start = spanned.getSpanStart(clickedSpan);
11592             final int end = spanned.getSpanEnd(clickedSpan);
11593             if (start >= 0 && end <= mText.length() && start < end) {
11594                 final TextClassification.Request request = new TextClassification.Request.Builder(
11595                         mText, start, end)
11596                         .setDefaultLocales(getTextLocales())
11597                         .build();
11598                 final Supplier<TextClassification> supplier = () ->
11599                         getTextClassifier().classifyText(request);
11600                 final Consumer<TextClassification> consumer = classification -> {
11601                     if (classification != null) {
11602                         if (!classification.getActions().isEmpty()) {
11603                             try {
11604                                 classification.getActions().get(0).getActionIntent().send();
11605                             } catch (PendingIntent.CanceledException e) {
11606                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
11607                             }
11608                         } else {
11609                             Log.d(LOG_TAG, "No link action to perform");
11610                         }
11611                     } else {
11612                         // classification == null
11613                         Log.d(LOG_TAG, "Timeout while classifying text");
11614                     }
11615                 };
11616                 CompletableFuture.supplyAsync(supplier)
11617                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
11618                         .thenAccept(consumer);
11619                 return true;
11620             }
11621         }
11622         return false;
11623     }
11624 
11625     /**
11626      * @hide
11627      */
stopTextActionMode()11628     protected void stopTextActionMode() {
11629         if (mEditor != null) {
11630             mEditor.stopTextActionMode();
11631         }
11632     }
11633 
11634     /** @hide */
hideFloatingToolbar(int durationMs)11635     public void hideFloatingToolbar(int durationMs) {
11636         if (mEditor != null) {
11637             mEditor.hideFloatingToolbar(durationMs);
11638         }
11639     }
11640 
canUndo()11641     boolean canUndo() {
11642         return mEditor != null && mEditor.canUndo();
11643     }
11644 
canRedo()11645     boolean canRedo() {
11646         return mEditor != null && mEditor.canRedo();
11647     }
11648 
canCut()11649     boolean canCut() {
11650         if (hasPasswordTransformationMethod()) {
11651             return false;
11652         }
11653 
11654         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
11655                 && mEditor.mKeyListener != null) {
11656             return true;
11657         }
11658 
11659         return false;
11660     }
11661 
canCopy()11662     boolean canCopy() {
11663         if (hasPasswordTransformationMethod()) {
11664             return false;
11665         }
11666 
11667         if (mText.length() > 0 && hasSelection() && mEditor != null) {
11668             return true;
11669         }
11670 
11671         return false;
11672     }
11673 
canShare()11674     boolean canShare() {
11675         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
11676             return false;
11677         }
11678         return canCopy();
11679     }
11680 
isDeviceProvisioned()11681     boolean isDeviceProvisioned() {
11682         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
11683             mDeviceProvisionedState = Settings.Global.getInt(
11684                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
11685                     ? DEVICE_PROVISIONED_YES
11686                     : DEVICE_PROVISIONED_NO;
11687         }
11688         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
11689     }
11690 
canPaste()11691     boolean canPaste() {
11692         return (mText instanceof Editable
11693                 && mEditor != null && mEditor.mKeyListener != null
11694                 && getSelectionStart() >= 0
11695                 && getSelectionEnd() >= 0
11696                 && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11697                         .hasPrimaryClip());
11698     }
11699 
canPasteAsPlainText()11700     boolean canPasteAsPlainText() {
11701         if (!canPaste()) {
11702             return false;
11703         }
11704 
11705         final ClipData clipData =
11706                 ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11707                         .getPrimaryClip();
11708         final ClipDescription description = clipData.getDescription();
11709         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
11710         final CharSequence text = clipData.getItemAt(0).getText();
11711         if (isPlainType && (text instanceof Spanned)) {
11712             Spanned spanned = (Spanned) text;
11713             if (TextUtils.hasStyleSpan(spanned)) {
11714                 return true;
11715             }
11716         }
11717         return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
11718     }
11719 
canProcessText()11720     boolean canProcessText() {
11721         if (getId() == View.NO_ID) {
11722             return false;
11723         }
11724         return canShare();
11725     }
11726 
canSelectAllText()11727     boolean canSelectAllText() {
11728         return canSelectText() && !hasPasswordTransformationMethod()
11729                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
11730     }
11731 
selectAllText()11732     boolean selectAllText() {
11733         if (mEditor != null) {
11734             // Hide the toolbar before changing the selection to avoid flickering.
11735             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
11736         }
11737         final int length = mText.length();
11738         Selection.setSelection(mSpannable, 0, length);
11739         return length > 0;
11740     }
11741 
replaceSelectionWithText(CharSequence text)11742     void replaceSelectionWithText(CharSequence text) {
11743         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
11744     }
11745 
11746     /**
11747      * Paste clipboard content between min and max positions.
11748      */
paste(int min, int max, boolean withFormatting)11749     private void paste(int min, int max, boolean withFormatting) {
11750         ClipboardManager clipboard =
11751                 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11752         ClipData clip = clipboard.getPrimaryClip();
11753         if (clip != null) {
11754             boolean didFirst = false;
11755             for (int i = 0; i < clip.getItemCount(); i++) {
11756                 final CharSequence paste;
11757                 if (withFormatting) {
11758                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
11759                 } else {
11760                     // Get an item as text and remove all spans by toString().
11761                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
11762                     paste = (text instanceof Spanned) ? text.toString() : text;
11763                 }
11764                 if (paste != null) {
11765                     if (!didFirst) {
11766                         Selection.setSelection(mSpannable, max);
11767                         ((Editable) mText).replace(min, max, paste);
11768                         didFirst = true;
11769                     } else {
11770                         ((Editable) mText).insert(getSelectionEnd(), "\n");
11771                         ((Editable) mText).insert(getSelectionEnd(), paste);
11772                     }
11773                 }
11774             }
11775             sLastCutCopyOrTextChangedTime = 0;
11776         }
11777     }
11778 
shareSelectedText()11779     private void shareSelectedText() {
11780         String selectedText = getSelectedText();
11781         if (selectedText != null && !selectedText.isEmpty()) {
11782             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
11783             sharingIntent.setType("text/plain");
11784             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
11785             selectedText = TextUtils.trimToParcelableSize(selectedText);
11786             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
11787             getContext().startActivity(Intent.createChooser(sharingIntent, null));
11788             Selection.setSelection(mSpannable, getSelectionEnd());
11789         }
11790     }
11791 
11792     @CheckResult
setPrimaryClip(ClipData clip)11793     private boolean setPrimaryClip(ClipData clip) {
11794         ClipboardManager clipboard =
11795                 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11796         try {
11797             clipboard.setPrimaryClip(clip);
11798         } catch (Throwable t) {
11799             return false;
11800         }
11801         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
11802         return true;
11803     }
11804 
11805     /**
11806      * Get the character offset closest to the specified absolute position. A typical use case is to
11807      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
11808      *
11809      * @param x The horizontal absolute position of a point on screen
11810      * @param y The vertical absolute position of a point on screen
11811      * @return the character offset for the character whose position is closest to the specified
11812      *  position. Returns -1 if there is no layout.
11813      */
getOffsetForPosition(float x, float y)11814     public int getOffsetForPosition(float x, float y) {
11815         if (getLayout() == null) return -1;
11816         final int line = getLineAtCoordinate(y);
11817         final int offset = getOffsetAtCoordinate(line, x);
11818         return offset;
11819     }
11820 
convertToLocalHorizontalCoordinate(float x)11821     float convertToLocalHorizontalCoordinate(float x) {
11822         x -= getTotalPaddingLeft();
11823         // Clamp the position to inside of the view.
11824         x = Math.max(0.0f, x);
11825         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
11826         x += getScrollX();
11827         return x;
11828     }
11829 
getLineAtCoordinate(float y)11830     int getLineAtCoordinate(float y) {
11831         y -= getTotalPaddingTop();
11832         // Clamp the position to inside of the view.
11833         y = Math.max(0.0f, y);
11834         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
11835         y += getScrollY();
11836         return getLayout().getLineForVertical((int) y);
11837     }
11838 
getLineAtCoordinateUnclamped(float y)11839     int getLineAtCoordinateUnclamped(float y) {
11840         y -= getTotalPaddingTop();
11841         y += getScrollY();
11842         return getLayout().getLineForVertical((int) y);
11843     }
11844 
getOffsetAtCoordinate(int line, float x)11845     int getOffsetAtCoordinate(int line, float x) {
11846         x = convertToLocalHorizontalCoordinate(x);
11847         return getLayout().getOffsetForHorizontal(line, x);
11848     }
11849 
11850     @Override
onDragEvent(DragEvent event)11851     public boolean onDragEvent(DragEvent event) {
11852         switch (event.getAction()) {
11853             case DragEvent.ACTION_DRAG_STARTED:
11854                 return mEditor != null && mEditor.hasInsertionController();
11855 
11856             case DragEvent.ACTION_DRAG_ENTERED:
11857                 TextView.this.requestFocus();
11858                 return true;
11859 
11860             case DragEvent.ACTION_DRAG_LOCATION:
11861                 if (mText instanceof Spannable) {
11862                     final int offset = getOffsetForPosition(event.getX(), event.getY());
11863                     Selection.setSelection(mSpannable, offset);
11864                 }
11865                 return true;
11866 
11867             case DragEvent.ACTION_DROP:
11868                 if (mEditor != null) mEditor.onDrop(event);
11869                 return true;
11870 
11871             case DragEvent.ACTION_DRAG_ENDED:
11872             case DragEvent.ACTION_DRAG_EXITED:
11873             default:
11874                 return true;
11875         }
11876     }
11877 
isInBatchEditMode()11878     boolean isInBatchEditMode() {
11879         if (mEditor == null) return false;
11880         final Editor.InputMethodState ims = mEditor.mInputMethodState;
11881         if (ims != null) {
11882             return ims.mBatchEditNesting > 0;
11883         }
11884         return mEditor.mInBatchEditControllers;
11885     }
11886 
11887     @Override
onRtlPropertiesChanged(int layoutDirection)11888     public void onRtlPropertiesChanged(int layoutDirection) {
11889         super.onRtlPropertiesChanged(layoutDirection);
11890 
11891         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
11892         if (mTextDir != newTextDir) {
11893             mTextDir = newTextDir;
11894             if (mLayout != null) {
11895                 checkForRelayout();
11896             }
11897         }
11898     }
11899 
11900     /**
11901      * Returns the current {@link TextDirectionHeuristic}.
11902      *
11903      * @return the current {@link TextDirectionHeuristic}.
11904      * @hide
11905      */
getTextDirectionHeuristic()11906     protected TextDirectionHeuristic getTextDirectionHeuristic() {
11907         if (hasPasswordTransformationMethod()) {
11908             // passwords fields should be LTR
11909             return TextDirectionHeuristics.LTR;
11910         }
11911 
11912         if (mEditor != null
11913                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
11914                     == EditorInfo.TYPE_CLASS_PHONE) {
11915             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
11916             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
11917             // RTL digits.
11918             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
11919             final String zero = symbols.getDigitStrings()[0];
11920             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
11921             // direction.
11922             final int firstCodepoint = zero.codePointAt(0);
11923             final byte digitDirection = Character.getDirectionality(firstCodepoint);
11924             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
11925                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
11926                 return TextDirectionHeuristics.RTL;
11927             } else {
11928                 return TextDirectionHeuristics.LTR;
11929             }
11930         }
11931 
11932         // Always need to resolve layout direction first
11933         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
11934 
11935         // Now, we can select the heuristic
11936         switch (getTextDirection()) {
11937             default:
11938             case TEXT_DIRECTION_FIRST_STRONG:
11939                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
11940                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
11941             case TEXT_DIRECTION_ANY_RTL:
11942                 return TextDirectionHeuristics.ANYRTL_LTR;
11943             case TEXT_DIRECTION_LTR:
11944                 return TextDirectionHeuristics.LTR;
11945             case TEXT_DIRECTION_RTL:
11946                 return TextDirectionHeuristics.RTL;
11947             case TEXT_DIRECTION_LOCALE:
11948                 return TextDirectionHeuristics.LOCALE;
11949             case TEXT_DIRECTION_FIRST_STRONG_LTR:
11950                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
11951             case TEXT_DIRECTION_FIRST_STRONG_RTL:
11952                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
11953         }
11954     }
11955 
11956     /**
11957      * @hide
11958      */
11959     @Override
onResolveDrawables(int layoutDirection)11960     public void onResolveDrawables(int layoutDirection) {
11961         // No need to resolve twice
11962         if (mLastLayoutDirection == layoutDirection) {
11963             return;
11964         }
11965         mLastLayoutDirection = layoutDirection;
11966 
11967         // Resolve drawables
11968         if (mDrawables != null) {
11969             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
11970                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
11971                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
11972                 applyCompoundDrawableTint();
11973             }
11974         }
11975     }
11976 
11977     /**
11978      * Prepares a drawable for display by propagating layout direction and
11979      * drawable state.
11980      *
11981      * @param dr the drawable to prepare
11982      */
prepareDrawableForDisplay(@ullable Drawable dr)11983     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
11984         if (dr == null) {
11985             return;
11986         }
11987 
11988         dr.setLayoutDirection(getLayoutDirection());
11989 
11990         if (dr.isStateful()) {
11991             dr.setState(getDrawableState());
11992             dr.jumpToCurrentState();
11993         }
11994     }
11995 
11996     /**
11997      * @hide
11998      */
resetResolvedDrawables()11999     protected void resetResolvedDrawables() {
12000         super.resetResolvedDrawables();
12001         mLastLayoutDirection = -1;
12002     }
12003 
12004     /**
12005      * @hide
12006      */
viewClicked(InputMethodManager imm)12007     protected void viewClicked(InputMethodManager imm) {
12008         if (imm != null) {
12009             imm.viewClicked(this);
12010         }
12011     }
12012 
12013     /**
12014      * Deletes the range of text [start, end[.
12015      * @hide
12016      */
deleteText_internal(int start, int end)12017     protected void deleteText_internal(int start, int end) {
12018         ((Editable) mText).delete(start, end);
12019     }
12020 
12021     /**
12022      * Replaces the range of text [start, end[ by replacement text
12023      * @hide
12024      */
replaceText_internal(int start, int end, CharSequence text)12025     protected void replaceText_internal(int start, int end, CharSequence text) {
12026         ((Editable) mText).replace(start, end, text);
12027     }
12028 
12029     /**
12030      * Sets a span on the specified range of text
12031      * @hide
12032      */
setSpan_internal(Object span, int start, int end, int flags)12033     protected void setSpan_internal(Object span, int start, int end, int flags) {
12034         ((Editable) mText).setSpan(span, start, end, flags);
12035     }
12036 
12037     /**
12038      * Moves the cursor to the specified offset position in text
12039      * @hide
12040      */
setCursorPosition_internal(int start, int end)12041     protected void setCursorPosition_internal(int start, int end) {
12042         Selection.setSelection(((Editable) mText), start, end);
12043     }
12044 
12045     /**
12046      * An Editor should be created as soon as any of the editable-specific fields (grouped
12047      * inside the Editor object) is assigned to a non-default value.
12048      * This method will create the Editor if needed.
12049      *
12050      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
12051      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
12052      * Editor for backward compatibility, as soon as one of these fields is assigned.
12053      *
12054      * Also note that for performance reasons, the mEditor is created when needed, but not
12055      * reset when no more edit-specific fields are needed.
12056      */
createEditorIfNeeded()12057     private void createEditorIfNeeded() {
12058         if (mEditor == null) {
12059             mEditor = new Editor(this);
12060         }
12061     }
12062 
12063     /**
12064      * @hide
12065      */
12066     @Override
getIterableTextForAccessibility()12067     public CharSequence getIterableTextForAccessibility() {
12068         return mText;
12069     }
12070 
ensureIterableTextForAccessibilitySelectable()12071     private void ensureIterableTextForAccessibilitySelectable() {
12072         if (!(mText instanceof Spannable)) {
12073             setText(mText, BufferType.SPANNABLE);
12074         }
12075     }
12076 
12077     /**
12078      * @hide
12079      */
12080     @Override
getIteratorForGranularity(int granularity)12081     public TextSegmentIterator getIteratorForGranularity(int granularity) {
12082         switch (granularity) {
12083             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
12084                 Spannable text = (Spannable) getIterableTextForAccessibility();
12085                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
12086                     AccessibilityIterators.LineTextSegmentIterator iterator =
12087                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
12088                     iterator.initialize(text, getLayout());
12089                     return iterator;
12090                 }
12091             } break;
12092             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
12093                 Spannable text = (Spannable) getIterableTextForAccessibility();
12094                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
12095                     AccessibilityIterators.PageTextSegmentIterator iterator =
12096                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
12097                     iterator.initialize(this);
12098                     return iterator;
12099                 }
12100             } break;
12101         }
12102         return super.getIteratorForGranularity(granularity);
12103     }
12104 
12105     /**
12106      * @hide
12107      */
12108     @Override
getAccessibilitySelectionStart()12109     public int getAccessibilitySelectionStart() {
12110         return getSelectionStart();
12111     }
12112 
12113     /**
12114      * @hide
12115      */
isAccessibilitySelectionExtendable()12116     public boolean isAccessibilitySelectionExtendable() {
12117         return true;
12118     }
12119 
12120     /**
12121      * @hide
12122      */
12123     @Override
getAccessibilitySelectionEnd()12124     public int getAccessibilitySelectionEnd() {
12125         return getSelectionEnd();
12126     }
12127 
12128     /**
12129      * @hide
12130      */
12131     @Override
setAccessibilitySelection(int start, int end)12132     public void setAccessibilitySelection(int start, int end) {
12133         if (getAccessibilitySelectionStart() == start
12134                 && getAccessibilitySelectionEnd() == end) {
12135             return;
12136         }
12137         CharSequence text = getIterableTextForAccessibility();
12138         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
12139             Selection.setSelection((Spannable) text, start, end);
12140         } else {
12141             Selection.removeSelection((Spannable) text);
12142         }
12143         // Hide all selection controllers used for adjusting selection
12144         // since we are doing so explicitlty by other means and these
12145         // controllers interact with how selection behaves.
12146         if (mEditor != null) {
12147             mEditor.hideCursorAndSpanControllers();
12148             mEditor.stopTextActionMode();
12149         }
12150     }
12151 
12152     /** @hide */
12153     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)12154     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
12155         super.encodeProperties(stream);
12156 
12157         TruncateAt ellipsize = getEllipsize();
12158         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
12159         stream.addProperty("text:textSize", getTextSize());
12160         stream.addProperty("text:scaledTextSize", getScaledTextSize());
12161         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
12162         stream.addProperty("text:selectionStart", getSelectionStart());
12163         stream.addProperty("text:selectionEnd", getSelectionEnd());
12164         stream.addProperty("text:curTextColor", mCurTextColor);
12165         stream.addProperty("text:text", mText == null ? null : mText.toString());
12166         stream.addProperty("text:gravity", mGravity);
12167     }
12168 
12169     /**
12170      * User interface state that is stored by TextView for implementing
12171      * {@link View#onSaveInstanceState}.
12172      */
12173     public static class SavedState extends BaseSavedState {
12174         int selStart = -1;
12175         int selEnd = -1;
12176         CharSequence text;
12177         boolean frozenWithFocus;
12178         CharSequence error;
12179         ParcelableParcel editorState;  // Optional state from Editor.
12180 
SavedState(Parcelable superState)12181         SavedState(Parcelable superState) {
12182             super(superState);
12183         }
12184 
12185         @Override
writeToParcel(Parcel out, int flags)12186         public void writeToParcel(Parcel out, int flags) {
12187             super.writeToParcel(out, flags);
12188             out.writeInt(selStart);
12189             out.writeInt(selEnd);
12190             out.writeInt(frozenWithFocus ? 1 : 0);
12191             TextUtils.writeToParcel(text, out, flags);
12192 
12193             if (error == null) {
12194                 out.writeInt(0);
12195             } else {
12196                 out.writeInt(1);
12197                 TextUtils.writeToParcel(error, out, flags);
12198             }
12199 
12200             if (editorState == null) {
12201                 out.writeInt(0);
12202             } else {
12203                 out.writeInt(1);
12204                 editorState.writeToParcel(out, flags);
12205             }
12206         }
12207 
12208         @Override
toString()12209         public String toString() {
12210             String str = "TextView.SavedState{"
12211                     + Integer.toHexString(System.identityHashCode(this))
12212                     + " start=" + selStart + " end=" + selEnd;
12213             if (text != null) {
12214                 str += " text=" + text;
12215             }
12216             return str + "}";
12217         }
12218 
12219         @SuppressWarnings("hiding")
12220         public static final Parcelable.Creator<SavedState> CREATOR =
12221                 new Parcelable.Creator<SavedState>() {
12222                     public SavedState createFromParcel(Parcel in) {
12223                         return new SavedState(in);
12224                     }
12225 
12226                     public SavedState[] newArray(int size) {
12227                         return new SavedState[size];
12228                     }
12229                 };
12230 
SavedState(Parcel in)12231         private SavedState(Parcel in) {
12232             super(in);
12233             selStart = in.readInt();
12234             selEnd = in.readInt();
12235             frozenWithFocus = (in.readInt() != 0);
12236             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
12237 
12238             if (in.readInt() != 0) {
12239                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
12240             }
12241 
12242             if (in.readInt() != 0) {
12243                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
12244             }
12245         }
12246     }
12247 
12248     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
12249         private char[] mChars;
12250         private int mStart, mLength;
12251 
CharWrapper(char[] chars, int start, int len)12252         public CharWrapper(char[] chars, int start, int len) {
12253             mChars = chars;
12254             mStart = start;
12255             mLength = len;
12256         }
12257 
set(char[] chars, int start, int len)12258         /* package */ void set(char[] chars, int start, int len) {
12259             mChars = chars;
12260             mStart = start;
12261             mLength = len;
12262         }
12263 
length()12264         public int length() {
12265             return mLength;
12266         }
12267 
charAt(int off)12268         public char charAt(int off) {
12269             return mChars[off + mStart];
12270         }
12271 
12272         @Override
toString()12273         public String toString() {
12274             return new String(mChars, mStart, mLength);
12275         }
12276 
subSequence(int start, int end)12277         public CharSequence subSequence(int start, int end) {
12278             if (start < 0 || end < 0 || start > mLength || end > mLength) {
12279                 throw new IndexOutOfBoundsException(start + ", " + end);
12280             }
12281 
12282             return new String(mChars, start + mStart, end - start);
12283         }
12284 
getChars(int start, int end, char[] buf, int off)12285         public void getChars(int start, int end, char[] buf, int off) {
12286             if (start < 0 || end < 0 || start > mLength || end > mLength) {
12287                 throw new IndexOutOfBoundsException(start + ", " + end);
12288             }
12289 
12290             System.arraycopy(mChars, start + mStart, buf, off, end - start);
12291         }
12292 
12293         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)12294         public void drawText(BaseCanvas c, int start, int end,
12295                              float x, float y, Paint p) {
12296             c.drawText(mChars, start + mStart, end - start, x, y, p);
12297         }
12298 
12299         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)12300         public void drawTextRun(BaseCanvas c, int start, int end,
12301                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
12302             int count = end - start;
12303             int contextCount = contextEnd - contextStart;
12304             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
12305                     contextCount, x, y, isRtl, p);
12306         }
12307 
measureText(int start, int end, Paint p)12308         public float measureText(int start, int end, Paint p) {
12309             return p.measureText(mChars, start + mStart, end - start);
12310         }
12311 
getTextWidths(int start, int end, float[] widths, Paint p)12312         public int getTextWidths(int start, int end, float[] widths, Paint p) {
12313             return p.getTextWidths(mChars, start + mStart, end - start, widths);
12314         }
12315 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)12316         public float getTextRunAdvances(int start, int end, int contextStart,
12317                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
12318                 Paint p) {
12319             int count = end - start;
12320             int contextCount = contextEnd - contextStart;
12321             return p.getTextRunAdvances(mChars, start + mStart, count,
12322                     contextStart + mStart, contextCount, isRtl, advances,
12323                     advancesIndex);
12324         }
12325 
getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p)12326         public int getTextRunCursor(int contextStart, int contextEnd, int dir,
12327                 int offset, int cursorOpt, Paint p) {
12328             int contextCount = contextEnd - contextStart;
12329             return p.getTextRunCursor(mChars, contextStart + mStart,
12330                     contextCount, dir, offset + mStart, cursorOpt);
12331         }
12332     }
12333 
12334     private static final class Marquee {
12335         // TODO: Add an option to configure this
12336         private static final float MARQUEE_DELTA_MAX = 0.07f;
12337         private static final int MARQUEE_DELAY = 1200;
12338         private static final int MARQUEE_DP_PER_SECOND = 30;
12339 
12340         private static final byte MARQUEE_STOPPED = 0x0;
12341         private static final byte MARQUEE_STARTING = 0x1;
12342         private static final byte MARQUEE_RUNNING = 0x2;
12343 
12344         private final WeakReference<TextView> mView;
12345         private final Choreographer mChoreographer;
12346 
12347         private byte mStatus = MARQUEE_STOPPED;
12348         private final float mPixelsPerMs;
12349         private float mMaxScroll;
12350         private float mMaxFadeScroll;
12351         private float mGhostStart;
12352         private float mGhostOffset;
12353         private float mFadeStop;
12354         private int mRepeatLimit;
12355 
12356         private float mScroll;
12357         private long mLastAnimationMs;
12358 
Marquee(TextView v)12359         Marquee(TextView v) {
12360             final float density = v.getContext().getResources().getDisplayMetrics().density;
12361             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
12362             mView = new WeakReference<TextView>(v);
12363             mChoreographer = Choreographer.getInstance();
12364         }
12365 
12366         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
12367             @Override
12368             public void doFrame(long frameTimeNanos) {
12369                 tick();
12370             }
12371         };
12372 
12373         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
12374             @Override
12375             public void doFrame(long frameTimeNanos) {
12376                 mStatus = MARQUEE_RUNNING;
12377                 mLastAnimationMs = mChoreographer.getFrameTime();
12378                 tick();
12379             }
12380         };
12381 
12382         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
12383             @Override
12384             public void doFrame(long frameTimeNanos) {
12385                 if (mStatus == MARQUEE_RUNNING) {
12386                     if (mRepeatLimit >= 0) {
12387                         mRepeatLimit--;
12388                     }
12389                     start(mRepeatLimit);
12390                 }
12391             }
12392         };
12393 
tick()12394         void tick() {
12395             if (mStatus != MARQUEE_RUNNING) {
12396                 return;
12397             }
12398 
12399             mChoreographer.removeFrameCallback(mTickCallback);
12400 
12401             final TextView textView = mView.get();
12402             if (textView != null && (textView.isFocused() || textView.isSelected())) {
12403                 long currentMs = mChoreographer.getFrameTime();
12404                 long deltaMs = currentMs - mLastAnimationMs;
12405                 mLastAnimationMs = currentMs;
12406                 float deltaPx = deltaMs * mPixelsPerMs;
12407                 mScroll += deltaPx;
12408                 if (mScroll > mMaxScroll) {
12409                     mScroll = mMaxScroll;
12410                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
12411                 } else {
12412                     mChoreographer.postFrameCallback(mTickCallback);
12413                 }
12414                 textView.invalidate();
12415             }
12416         }
12417 
stop()12418         void stop() {
12419             mStatus = MARQUEE_STOPPED;
12420             mChoreographer.removeFrameCallback(mStartCallback);
12421             mChoreographer.removeFrameCallback(mRestartCallback);
12422             mChoreographer.removeFrameCallback(mTickCallback);
12423             resetScroll();
12424         }
12425 
resetScroll()12426         private void resetScroll() {
12427             mScroll = 0.0f;
12428             final TextView textView = mView.get();
12429             if (textView != null) textView.invalidate();
12430         }
12431 
start(int repeatLimit)12432         void start(int repeatLimit) {
12433             if (repeatLimit == 0) {
12434                 stop();
12435                 return;
12436             }
12437             mRepeatLimit = repeatLimit;
12438             final TextView textView = mView.get();
12439             if (textView != null && textView.mLayout != null) {
12440                 mStatus = MARQUEE_STARTING;
12441                 mScroll = 0.0f;
12442                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
12443                         - textView.getCompoundPaddingRight();
12444                 final float lineWidth = textView.mLayout.getLineWidth(0);
12445                 final float gap = textWidth / 3.0f;
12446                 mGhostStart = lineWidth - textWidth + gap;
12447                 mMaxScroll = mGhostStart + textWidth;
12448                 mGhostOffset = lineWidth + gap;
12449                 mFadeStop = lineWidth + textWidth / 6.0f;
12450                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
12451 
12452                 textView.invalidate();
12453                 mChoreographer.postFrameCallback(mStartCallback);
12454             }
12455         }
12456 
getGhostOffset()12457         float getGhostOffset() {
12458             return mGhostOffset;
12459         }
12460 
getScroll()12461         float getScroll() {
12462             return mScroll;
12463         }
12464 
getMaxFadeScroll()12465         float getMaxFadeScroll() {
12466             return mMaxFadeScroll;
12467         }
12468 
shouldDrawLeftFade()12469         boolean shouldDrawLeftFade() {
12470             return mScroll <= mFadeStop;
12471         }
12472 
shouldDrawGhost()12473         boolean shouldDrawGhost() {
12474             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
12475         }
12476 
isRunning()12477         boolean isRunning() {
12478             return mStatus == MARQUEE_RUNNING;
12479         }
12480 
isStopped()12481         boolean isStopped() {
12482             return mStatus == MARQUEE_STOPPED;
12483         }
12484     }
12485 
12486     private class ChangeWatcher implements TextWatcher, SpanWatcher {
12487 
12488         private CharSequence mBeforeText;
12489 
beforeTextChanged(CharSequence buffer, int start, int before, int after)12490         public void beforeTextChanged(CharSequence buffer, int start,
12491                                       int before, int after) {
12492             if (DEBUG_EXTRACT) {
12493                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
12494                         + " before=" + before + " after=" + after + ": " + buffer);
12495             }
12496 
12497             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
12498                 mBeforeText = mTransformed.toString();
12499             }
12500 
12501             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
12502         }
12503 
onTextChanged(CharSequence buffer, int start, int before, int after)12504         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
12505             if (DEBUG_EXTRACT) {
12506                 Log.v(LOG_TAG, "onTextChanged start=" + start
12507                         + " before=" + before + " after=" + after + ": " + buffer);
12508             }
12509             TextView.this.handleTextChanged(buffer, start, before, after);
12510 
12511             if (AccessibilityManager.getInstance(mContext).isEnabled()
12512                     && (isFocused() || isSelected() && isShown())) {
12513                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
12514                 mBeforeText = null;
12515             }
12516         }
12517 
afterTextChanged(Editable buffer)12518         public void afterTextChanged(Editable buffer) {
12519             if (DEBUG_EXTRACT) {
12520                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
12521             }
12522             TextView.this.sendAfterTextChanged(buffer);
12523 
12524             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
12525                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
12526             }
12527         }
12528 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)12529         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
12530             if (DEBUG_EXTRACT) {
12531                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
12532                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
12533             }
12534             TextView.this.spanChange(buf, what, s, st, e, en);
12535         }
12536 
onSpanAdded(Spannable buf, Object what, int s, int e)12537         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
12538             if (DEBUG_EXTRACT) {
12539                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
12540             }
12541             TextView.this.spanChange(buf, what, -1, s, -1, e);
12542         }
12543 
onSpanRemoved(Spannable buf, Object what, int s, int e)12544         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
12545             if (DEBUG_EXTRACT) {
12546                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
12547             }
12548             TextView.this.spanChange(buf, what, s, -1, e, -1);
12549         }
12550     }
12551 }
12552