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