• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
20 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
21 import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT;
22 import static android.view.ContentInfo.SOURCE_AUTOFILL;
23 import static android.view.ContentInfo.SOURCE_CLIPBOARD;
24 import static android.view.ContentInfo.SOURCE_PROCESS_TEXT;
25 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY;
26 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
27 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
28 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
29 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
30 
31 import android.R;
32 import android.annotation.CallSuper;
33 import android.annotation.CheckResult;
34 import android.annotation.ColorInt;
35 import android.annotation.DrawableRes;
36 import android.annotation.FloatRange;
37 import android.annotation.IntDef;
38 import android.annotation.IntRange;
39 import android.annotation.NonNull;
40 import android.annotation.Nullable;
41 import android.annotation.Px;
42 import android.annotation.RequiresPermission;
43 import android.annotation.Size;
44 import android.annotation.StringRes;
45 import android.annotation.StyleRes;
46 import android.annotation.TestApi;
47 import android.annotation.XmlRes;
48 import android.app.Activity;
49 import android.app.PendingIntent;
50 import android.app.assist.AssistStructure;
51 import android.app.compat.CompatChanges;
52 import android.compat.annotation.ChangeId;
53 import android.compat.annotation.EnabledSince;
54 import android.compat.annotation.UnsupportedAppUsage;
55 import android.content.ClipData;
56 import android.content.ClipDescription;
57 import android.content.ClipboardManager;
58 import android.content.Context;
59 import android.content.Intent;
60 import android.content.UndoManager;
61 import android.content.pm.PackageManager;
62 import android.content.res.ColorStateList;
63 import android.content.res.CompatibilityInfo;
64 import android.content.res.Configuration;
65 import android.content.res.Resources;
66 import android.content.res.TypedArray;
67 import android.content.res.XmlResourceParser;
68 import android.graphics.BaseCanvas;
69 import android.graphics.BlendMode;
70 import android.graphics.Canvas;
71 import android.graphics.Insets;
72 import android.graphics.Paint;
73 import android.graphics.Paint.FontMetricsInt;
74 import android.graphics.Path;
75 import android.graphics.PorterDuff;
76 import android.graphics.Rect;
77 import android.graphics.RectF;
78 import android.graphics.Typeface;
79 import android.graphics.drawable.Drawable;
80 import android.graphics.fonts.FontStyle;
81 import android.graphics.fonts.FontVariationAxis;
82 import android.graphics.text.LineBreakConfig;
83 import android.icu.text.DecimalFormatSymbols;
84 import android.os.AsyncTask;
85 import android.os.Build;
86 import android.os.Build.VERSION_CODES;
87 import android.os.Bundle;
88 import android.os.Handler;
89 import android.os.LocaleList;
90 import android.os.Parcel;
91 import android.os.Parcelable;
92 import android.os.ParcelableParcel;
93 import android.os.Process;
94 import android.os.SystemClock;
95 import android.os.UserHandle;
96 import android.provider.Settings;
97 import android.text.BoringLayout;
98 import android.text.DynamicLayout;
99 import android.text.Editable;
100 import android.text.GetChars;
101 import android.text.GraphicsOperations;
102 import android.text.InputFilter;
103 import android.text.InputType;
104 import android.text.Layout;
105 import android.text.ParcelableSpan;
106 import android.text.PrecomputedText;
107 import android.text.Selection;
108 import android.text.SpanWatcher;
109 import android.text.Spannable;
110 import android.text.SpannableStringBuilder;
111 import android.text.Spanned;
112 import android.text.SpannedString;
113 import android.text.StaticLayout;
114 import android.text.TextDirectionHeuristic;
115 import android.text.TextDirectionHeuristics;
116 import android.text.TextPaint;
117 import android.text.TextUtils;
118 import android.text.TextUtils.TruncateAt;
119 import android.text.TextWatcher;
120 import android.text.method.AllCapsTransformationMethod;
121 import android.text.method.ArrowKeyMovementMethod;
122 import android.text.method.DateKeyListener;
123 import android.text.method.DateTimeKeyListener;
124 import android.text.method.DialerKeyListener;
125 import android.text.method.DigitsKeyListener;
126 import android.text.method.KeyListener;
127 import android.text.method.LinkMovementMethod;
128 import android.text.method.MetaKeyKeyListener;
129 import android.text.method.MovementMethod;
130 import android.text.method.PasswordTransformationMethod;
131 import android.text.method.SingleLineTransformationMethod;
132 import android.text.method.TextKeyListener;
133 import android.text.method.TimeKeyListener;
134 import android.text.method.TransformationMethod;
135 import android.text.method.TransformationMethod2;
136 import android.text.method.WordIterator;
137 import android.text.style.CharacterStyle;
138 import android.text.style.ClickableSpan;
139 import android.text.style.ParagraphStyle;
140 import android.text.style.SpellCheckSpan;
141 import android.text.style.SuggestionSpan;
142 import android.text.style.URLSpan;
143 import android.text.style.UpdateAppearance;
144 import android.text.util.Linkify;
145 import android.util.AttributeSet;
146 import android.util.DisplayMetrics;
147 import android.util.IntArray;
148 import android.util.Log;
149 import android.util.SparseIntArray;
150 import android.util.TypedValue;
151 import android.view.AccessibilityIterators.TextSegmentIterator;
152 import android.view.ActionMode;
153 import android.view.Choreographer;
154 import android.view.ContentInfo;
155 import android.view.ContextMenu;
156 import android.view.DragEvent;
157 import android.view.Gravity;
158 import android.view.HapticFeedbackConstants;
159 import android.view.InputDevice;
160 import android.view.KeyCharacterMap;
161 import android.view.KeyEvent;
162 import android.view.MotionEvent;
163 import android.view.PointerIcon;
164 import android.view.View;
165 import android.view.ViewConfiguration;
166 import android.view.ViewDebug;
167 import android.view.ViewGroup.LayoutParams;
168 import android.view.ViewHierarchyEncoder;
169 import android.view.ViewParent;
170 import android.view.ViewRootImpl;
171 import android.view.ViewStructure;
172 import android.view.ViewTreeObserver;
173 import android.view.accessibility.AccessibilityEvent;
174 import android.view.accessibility.AccessibilityManager;
175 import android.view.accessibility.AccessibilityNodeInfo;
176 import android.view.animation.AnimationUtils;
177 import android.view.autofill.AutofillManager;
178 import android.view.autofill.AutofillValue;
179 import android.view.contentcapture.ContentCaptureManager;
180 import android.view.contentcapture.ContentCaptureSession;
181 import android.view.inputmethod.BaseInputConnection;
182 import android.view.inputmethod.CompletionInfo;
183 import android.view.inputmethod.CorrectionInfo;
184 import android.view.inputmethod.CursorAnchorInfo;
185 import android.view.inputmethod.EditorInfo;
186 import android.view.inputmethod.ExtractedText;
187 import android.view.inputmethod.ExtractedTextRequest;
188 import android.view.inputmethod.InputConnection;
189 import android.view.inputmethod.InputMethodManager;
190 import android.view.inspector.InspectableProperty;
191 import android.view.inspector.InspectableProperty.EnumEntry;
192 import android.view.inspector.InspectableProperty.FlagEntry;
193 import android.view.textclassifier.TextClassification;
194 import android.view.textclassifier.TextClassificationContext;
195 import android.view.textclassifier.TextClassificationManager;
196 import android.view.textclassifier.TextClassifier;
197 import android.view.textclassifier.TextLinks;
198 import android.view.textservice.SpellCheckerSubtype;
199 import android.view.textservice.TextServicesManager;
200 import android.view.translation.TranslationRequestValue;
201 import android.view.translation.TranslationSpec;
202 import android.view.translation.UiTranslationController;
203 import android.view.translation.ViewTranslationCallback;
204 import android.view.translation.ViewTranslationRequest;
205 import android.widget.RemoteViews.RemoteView;
206 
207 import com.android.internal.accessibility.util.AccessibilityUtils;
208 import com.android.internal.annotations.VisibleForTesting;
209 import com.android.internal.inputmethod.EditableInputConnection;
210 import com.android.internal.logging.MetricsLogger;
211 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
212 import com.android.internal.util.ArrayUtils;
213 import com.android.internal.util.FastMath;
214 import com.android.internal.util.Preconditions;
215 
216 import libcore.util.EmptyArray;
217 
218 import org.xmlpull.v1.XmlPullParserException;
219 
220 import java.io.IOException;
221 import java.lang.annotation.Retention;
222 import java.lang.annotation.RetentionPolicy;
223 import java.lang.ref.WeakReference;
224 import java.util.ArrayList;
225 import java.util.Arrays;
226 import java.util.Locale;
227 import java.util.Objects;
228 import java.util.concurrent.CompletableFuture;
229 import java.util.concurrent.TimeUnit;
230 import java.util.function.Consumer;
231 import java.util.function.Supplier;
232 
233 /**
234  * A user interface element that displays text to the user.
235  * To provide user-editable text, see {@link EditText}.
236  * <p>
237  * The following code sample shows a typical use, with an XML layout
238  * and code to modify the contents of the text view:
239  * </p>
240 
241  * <pre>
242  * &lt;LinearLayout
243        xmlns:android="http://schemas.android.com/apk/res/android"
244        android:layout_width="match_parent"
245        android:layout_height="match_parent"&gt;
246  *    &lt;TextView
247  *        android:id="@+id/text_view_id"
248  *        android:layout_height="wrap_content"
249  *        android:layout_width="wrap_content"
250  *        android:text="@string/hello" /&gt;
251  * &lt;/LinearLayout&gt;
252  * </pre>
253  * <p>
254  * This code sample demonstrates how to modify the contents of the text view
255  * defined in the previous XML layout:
256  * </p>
257  * <pre>
258  * public class MainActivity extends Activity {
259  *
260  *    protected void onCreate(Bundle savedInstanceState) {
261  *         super.onCreate(savedInstanceState);
262  *         setContentView(R.layout.activity_main);
263  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
264  *         helloTextView.setText(R.string.user_greeting);
265  *     }
266  * }
267  * </pre>
268  * <p>
269  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
270  * </p>
271  * <p>
272  * <b>XML attributes</b>
273  * <p>
274  * See {@link android.R.styleable#TextView TextView Attributes},
275  * {@link android.R.styleable#View View Attributes}
276  *
277  * @attr ref android.R.styleable#TextView_text
278  * @attr ref android.R.styleable#TextView_bufferType
279  * @attr ref android.R.styleable#TextView_hint
280  * @attr ref android.R.styleable#TextView_textColor
281  * @attr ref android.R.styleable#TextView_textColorHighlight
282  * @attr ref android.R.styleable#TextView_textColorHint
283  * @attr ref android.R.styleable#TextView_textAppearance
284  * @attr ref android.R.styleable#TextView_textColorLink
285  * @attr ref android.R.styleable#TextView_textFontWeight
286  * @attr ref android.R.styleable#TextView_textSize
287  * @attr ref android.R.styleable#TextView_textScaleX
288  * @attr ref android.R.styleable#TextView_fontFamily
289  * @attr ref android.R.styleable#TextView_typeface
290  * @attr ref android.R.styleable#TextView_textStyle
291  * @attr ref android.R.styleable#TextView_cursorVisible
292  * @attr ref android.R.styleable#TextView_maxLines
293  * @attr ref android.R.styleable#TextView_maxHeight
294  * @attr ref android.R.styleable#TextView_lines
295  * @attr ref android.R.styleable#TextView_height
296  * @attr ref android.R.styleable#TextView_minLines
297  * @attr ref android.R.styleable#TextView_minHeight
298  * @attr ref android.R.styleable#TextView_maxEms
299  * @attr ref android.R.styleable#TextView_maxWidth
300  * @attr ref android.R.styleable#TextView_ems
301  * @attr ref android.R.styleable#TextView_width
302  * @attr ref android.R.styleable#TextView_minEms
303  * @attr ref android.R.styleable#TextView_minWidth
304  * @attr ref android.R.styleable#TextView_gravity
305  * @attr ref android.R.styleable#TextView_scrollHorizontally
306  * @attr ref android.R.styleable#TextView_password
307  * @attr ref android.R.styleable#TextView_singleLine
308  * @attr ref android.R.styleable#TextView_selectAllOnFocus
309  * @attr ref android.R.styleable#TextView_includeFontPadding
310  * @attr ref android.R.styleable#TextView_maxLength
311  * @attr ref android.R.styleable#TextView_shadowColor
312  * @attr ref android.R.styleable#TextView_shadowDx
313  * @attr ref android.R.styleable#TextView_shadowDy
314  * @attr ref android.R.styleable#TextView_shadowRadius
315  * @attr ref android.R.styleable#TextView_autoLink
316  * @attr ref android.R.styleable#TextView_linksClickable
317  * @attr ref android.R.styleable#TextView_numeric
318  * @attr ref android.R.styleable#TextView_digits
319  * @attr ref android.R.styleable#TextView_phoneNumber
320  * @attr ref android.R.styleable#TextView_inputMethod
321  * @attr ref android.R.styleable#TextView_capitalize
322  * @attr ref android.R.styleable#TextView_autoText
323  * @attr ref android.R.styleable#TextView_editable
324  * @attr ref android.R.styleable#TextView_freezesText
325  * @attr ref android.R.styleable#TextView_ellipsize
326  * @attr ref android.R.styleable#TextView_drawableTop
327  * @attr ref android.R.styleable#TextView_drawableBottom
328  * @attr ref android.R.styleable#TextView_drawableRight
329  * @attr ref android.R.styleable#TextView_drawableLeft
330  * @attr ref android.R.styleable#TextView_drawableStart
331  * @attr ref android.R.styleable#TextView_drawableEnd
332  * @attr ref android.R.styleable#TextView_drawablePadding
333  * @attr ref android.R.styleable#TextView_drawableTint
334  * @attr ref android.R.styleable#TextView_drawableTintMode
335  * @attr ref android.R.styleable#TextView_lineSpacingExtra
336  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
337  * @attr ref android.R.styleable#TextView_justificationMode
338  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
339  * @attr ref android.R.styleable#TextView_inputType
340  * @attr ref android.R.styleable#TextView_imeOptions
341  * @attr ref android.R.styleable#TextView_privateImeOptions
342  * @attr ref android.R.styleable#TextView_imeActionLabel
343  * @attr ref android.R.styleable#TextView_imeActionId
344  * @attr ref android.R.styleable#TextView_editorExtras
345  * @attr ref android.R.styleable#TextView_elegantTextHeight
346  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
347  * @attr ref android.R.styleable#TextView_letterSpacing
348  * @attr ref android.R.styleable#TextView_fontFeatureSettings
349  * @attr ref android.R.styleable#TextView_fontVariationSettings
350  * @attr ref android.R.styleable#TextView_breakStrategy
351  * @attr ref android.R.styleable#TextView_hyphenationFrequency
352  * @attr ref android.R.styleable#TextView_lineBreakStyle
353  * @attr ref android.R.styleable#TextView_lineBreakWordStyle
354  * @attr ref android.R.styleable#TextView_autoSizeTextType
355  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
356  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
357  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
358  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
359  * @attr ref android.R.styleable#TextView_textCursorDrawable
360  * @attr ref android.R.styleable#TextView_textSelectHandle
361  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
362  * @attr ref android.R.styleable#TextView_textSelectHandleRight
363  * @attr ref android.R.styleable#TextView_allowUndo
364  * @attr ref android.R.styleable#TextView_enabled
365  */
366 @RemoteView
367 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
368     static final String LOG_TAG = "TextView";
369     static final boolean DEBUG_EXTRACT = false;
370     static final boolean DEBUG_CURSOR = false;
371 
372     private static final float[] TEMP_POSITION = new float[2];
373 
374     // Enum for the "typeface" XML parameter.
375     // TODO: How can we get this from the XML instead of hardcoding it here?
376     /** @hide */
377     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
378     @Retention(RetentionPolicy.SOURCE)
379     public @interface XMLTypefaceAttr{}
380     private static final int DEFAULT_TYPEFACE = -1;
381     private static final int SANS = 1;
382     private static final int SERIF = 2;
383     private static final int MONOSPACE = 3;
384 
385     // Enum for the "ellipsize" XML parameter.
386     private static final int ELLIPSIZE_NOT_SET = -1;
387     private static final int ELLIPSIZE_NONE = 0;
388     private static final int ELLIPSIZE_START = 1;
389     private static final int ELLIPSIZE_MIDDLE = 2;
390     private static final int ELLIPSIZE_END = 3;
391     private static final int ELLIPSIZE_MARQUEE = 4;
392 
393     // Bitfield for the "numeric" XML parameter.
394     // TODO: How can we get this from the XML instead of hardcoding it here?
395     private static final int SIGNED = 2;
396     private static final int DECIMAL = 4;
397 
398     /**
399      * Draw marquee text with fading edges as usual
400      */
401     private static final int MARQUEE_FADE_NORMAL = 0;
402 
403     /**
404      * Draw marquee text as ellipsize end while inactive instead of with the fade.
405      * (Useful for devices where the fade can be expensive if overdone)
406      */
407     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
408 
409     /**
410      * Draw marquee text with fading edges because it is currently active/animating.
411      */
412     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
413 
414     @UnsupportedAppUsage
415     private static final int LINES = 1;
416     private static final int EMS = LINES;
417     private static final int PIXELS = 2;
418 
419     // Maximum text length for single line input.
420     private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
421     private InputFilter.LengthFilter mSingleLineLengthFilter = null;
422 
423     private static final RectF TEMP_RECTF = new RectF();
424 
425     /** @hide */
426     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
427     private static final int ANIMATED_SCROLL_GAP = 250;
428 
429     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
430     private static final Spanned EMPTY_SPANNED = new SpannedString("");
431 
432     private static final int CHANGE_WATCHER_PRIORITY = 100;
433 
434     // New state used to change background based on whether this TextView is multiline.
435     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
436 
437     // Accessibility action to share selected text.
438     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
439 
440     /**
441      * @hide
442      */
443     // Accessibility action start id for "process text" actions.
444     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
445 
446     /** Accessibility action start id for "smart" actions. @hide */
447     static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000;
448 
449     /**
450      * @hide
451      */
452     @TestApi
453     public static final int PROCESS_TEXT_REQUEST_CODE = 100;
454 
455     /**
456      *  Return code of {@link #doKeyDown}.
457      */
458     private static final int KEY_EVENT_NOT_HANDLED = 0;
459     private static final int KEY_EVENT_HANDLED = -1;
460     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
461     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
462 
463     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
464 
465     // The default value of the line break style.
466     private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE;
467 
468     // The default value of the line break word style.
469     private static final int DEFAULT_LINE_BREAK_WORD_STYLE =
470             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
471 
472     /**
473      * This change ID enables the fallback text line spacing (line height) for BoringLayout.
474      * @hide
475      */
476     @ChangeId
477     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
478     public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id
479 
480     /**
481      * This change ID enables the fallback text line spacing (line height) for StaticLayout.
482      * @hide
483      */
484     @ChangeId
485     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P)
486     public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id
487 
488     // System wide time for last cut, copy or text changed action.
489     static long sLastCutCopyOrTextChangedTime;
490 
491     private ColorStateList mTextColor;
492     private ColorStateList mHintTextColor;
493     private ColorStateList mLinkTextColor;
494     @ViewDebug.ExportedProperty(category = "text")
495 
496     /**
497      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
498      */
499     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
500     private int mCurTextColor;
501 
502     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
503     private int mCurHintTextColor;
504     private boolean mFreezesText;
505 
506     @UnsupportedAppUsage
507     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
508     @UnsupportedAppUsage
509     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
510 
511     @UnsupportedAppUsage
512     private float mShadowRadius;
513     @UnsupportedAppUsage
514     private float mShadowDx;
515     @UnsupportedAppUsage
516     private float mShadowDy;
517     private int mShadowColor;
518 
519     private boolean mPreDrawRegistered;
520     private boolean mPreDrawListenerDetached;
521 
522     private TextClassifier mTextClassifier;
523     private TextClassifier mTextClassificationSession;
524     private TextClassificationContext mTextClassificationContext;
525 
526     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
527     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
528     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
529     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
530     // into by the user holding the movement key down) then we shouldn't prevent the focus from
531     // changing.
532     private boolean mPreventDefaultMovement;
533 
534     private TextUtils.TruncateAt mEllipsize;
535 
536     // A flag to indicate the cursor was hidden by IME.
537     private boolean mImeIsConsumingInput;
538 
539     // Whether cursor is visible without regard to {@link mImeConsumesInput}.
540     // {@code true} is the default value.
541     private boolean mCursorVisibleFromAttr = true;
542 
543     static class Drawables {
544         static final int LEFT = 0;
545         static final int TOP = 1;
546         static final int RIGHT = 2;
547         static final int BOTTOM = 3;
548 
549         static final int DRAWABLE_NONE = -1;
550         static final int DRAWABLE_RIGHT = 0;
551         static final int DRAWABLE_LEFT = 1;
552 
553         final Rect mCompoundRect = new Rect();
554 
555         final Drawable[] mShowing = new Drawable[4];
556 
557         ColorStateList mTintList;
558         BlendMode mBlendMode;
559         boolean mHasTint;
560         boolean mHasTintMode;
561 
562         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
563         Drawable mDrawableLeftInitial, mDrawableRightInitial;
564 
565         boolean mIsRtlCompatibilityMode;
566         boolean mOverride;
567 
568         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
569                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
570 
571         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
572                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
573 
574         int mDrawablePadding;
575 
576         int mDrawableSaved = DRAWABLE_NONE;
577 
Drawables(Context context)578         public Drawables(Context context) {
579             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
580             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
581                     || !context.getApplicationInfo().hasRtlSupport();
582             mOverride = false;
583         }
584 
585         /**
586          * @return {@code true} if this object contains metadata that needs to
587          *         be retained, {@code false} otherwise
588          */
589         public boolean hasMetadata() {
590             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
591         }
592 
593         /**
594          * Updates the list of displayed drawables to account for the current
595          * layout direction.
596          *
597          * @param layoutDirection the current layout direction
598          * @return {@code true} if the displayed drawables changed
599          */
600         public boolean resolveWithLayoutDirection(int layoutDirection) {
601             final Drawable previousLeft = mShowing[Drawables.LEFT];
602             final Drawable previousRight = mShowing[Drawables.RIGHT];
603 
604             // First reset "left" and "right" drawables to their initial values
605             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
606             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
607 
608             if (mIsRtlCompatibilityMode) {
609                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
610                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
611                     mShowing[Drawables.LEFT] = mDrawableStart;
612                     mDrawableSizeLeft = mDrawableSizeStart;
613                     mDrawableHeightLeft = mDrawableHeightStart;
614                 }
615                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
616                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
617                     mShowing[Drawables.RIGHT] = mDrawableEnd;
618                     mDrawableSizeRight = mDrawableSizeEnd;
619                     mDrawableHeightRight = mDrawableHeightEnd;
620                 }
621             } else {
622                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
623                 // drawable if and only if they have been defined
624                 switch(layoutDirection) {
625                     case LAYOUT_DIRECTION_RTL:
626                         if (mOverride) {
627                             mShowing[Drawables.RIGHT] = mDrawableStart;
628                             mDrawableSizeRight = mDrawableSizeStart;
629                             mDrawableHeightRight = mDrawableHeightStart;
630 
631                             mShowing[Drawables.LEFT] = mDrawableEnd;
632                             mDrawableSizeLeft = mDrawableSizeEnd;
633                             mDrawableHeightLeft = mDrawableHeightEnd;
634                         }
635                         break;
636 
637                     case LAYOUT_DIRECTION_LTR:
638                     default:
639                         if (mOverride) {
640                             mShowing[Drawables.LEFT] = mDrawableStart;
641                             mDrawableSizeLeft = mDrawableSizeStart;
642                             mDrawableHeightLeft = mDrawableHeightStart;
643 
644                             mShowing[Drawables.RIGHT] = mDrawableEnd;
645                             mDrawableSizeRight = mDrawableSizeEnd;
646                             mDrawableHeightRight = mDrawableHeightEnd;
647                         }
648                         break;
649                 }
650             }
651 
652             applyErrorDrawableIfNeeded(layoutDirection);
653 
654             return mShowing[Drawables.LEFT] != previousLeft
655                     || mShowing[Drawables.RIGHT] != previousRight;
656         }
657 
658         public void setErrorDrawable(Drawable dr, TextView tv) {
659             if (mDrawableError != dr && mDrawableError != null) {
660                 mDrawableError.setCallback(null);
661             }
662             mDrawableError = dr;
663 
664             if (mDrawableError != null) {
665                 final Rect compoundRect = mCompoundRect;
666                 final int[] state = tv.getDrawableState();
667 
668                 mDrawableError.setState(state);
669                 mDrawableError.copyBounds(compoundRect);
670                 mDrawableError.setCallback(tv);
671                 mDrawableSizeError = compoundRect.width();
672                 mDrawableHeightError = compoundRect.height();
673             } else {
674                 mDrawableSizeError = mDrawableHeightError = 0;
675             }
676         }
677 
678         private void applyErrorDrawableIfNeeded(int layoutDirection) {
679             // first restore the initial state if needed
680             switch (mDrawableSaved) {
681                 case DRAWABLE_LEFT:
682                     mShowing[Drawables.LEFT] = mDrawableTemp;
683                     mDrawableSizeLeft = mDrawableSizeTemp;
684                     mDrawableHeightLeft = mDrawableHeightTemp;
685                     break;
686                 case DRAWABLE_RIGHT:
687                     mShowing[Drawables.RIGHT] = mDrawableTemp;
688                     mDrawableSizeRight = mDrawableSizeTemp;
689                     mDrawableHeightRight = mDrawableHeightTemp;
690                     break;
691                 case DRAWABLE_NONE:
692                 default:
693             }
694             // then, if needed, assign the Error drawable to the correct location
695             if (mDrawableError != null) {
696                 switch(layoutDirection) {
697                     case LAYOUT_DIRECTION_RTL:
698                         mDrawableSaved = DRAWABLE_LEFT;
699 
700                         mDrawableTemp = mShowing[Drawables.LEFT];
701                         mDrawableSizeTemp = mDrawableSizeLeft;
702                         mDrawableHeightTemp = mDrawableHeightLeft;
703 
704                         mShowing[Drawables.LEFT] = mDrawableError;
705                         mDrawableSizeLeft = mDrawableSizeError;
706                         mDrawableHeightLeft = mDrawableHeightError;
707                         break;
708                     case LAYOUT_DIRECTION_LTR:
709                     default:
710                         mDrawableSaved = DRAWABLE_RIGHT;
711 
712                         mDrawableTemp = mShowing[Drawables.RIGHT];
713                         mDrawableSizeTemp = mDrawableSizeRight;
714                         mDrawableHeightTemp = mDrawableHeightRight;
715 
716                         mShowing[Drawables.RIGHT] = mDrawableError;
717                         mDrawableSizeRight = mDrawableSizeError;
718                         mDrawableHeightRight = mDrawableHeightError;
719                         break;
720                 }
721             }
722         }
723     }
724 
725     @UnsupportedAppUsage
726     Drawables mDrawables;
727 
728     @UnsupportedAppUsage
729     private CharWrapper mCharWrapper;
730 
731     @UnsupportedAppUsage(trackingBug = 124050217)
732     private Marquee mMarquee;
733     @UnsupportedAppUsage
734     private boolean mRestartMarquee;
735 
736     private int mMarqueeRepeatLimit = 3;
737 
738     private int mLastLayoutDirection = -1;
739 
740     /**
741      * On some devices the fading edges add a performance penalty if used
742      * extensively in the same layout. This mode indicates how the marquee
743      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
744      */
745     @UnsupportedAppUsage
746     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
747 
748     /**
749      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
750      * the layout that should be used when the mode switches.
751      */
752     @UnsupportedAppUsage
753     private Layout mSavedMarqueeModeLayout;
754 
755     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
756     @ViewDebug.ExportedProperty(category = "text")
757     @UnsupportedAppUsage
758     private @Nullable CharSequence mText;
759     private @Nullable Spannable mSpannable;
760     private @Nullable PrecomputedText mPrecomputed;
761 
762     @UnsupportedAppUsage
763     private CharSequence mTransformed;
764     @UnsupportedAppUsage
765     private BufferType mBufferType = BufferType.NORMAL;
766 
767     private CharSequence mHint;
768     @UnsupportedAppUsage
769     private Layout mHintLayout;
770 
771     private MovementMethod mMovement;
772 
773     private TransformationMethod mTransformation;
774     @UnsupportedAppUsage
775     private boolean mAllowTransformationLengthChange;
776     @UnsupportedAppUsage
777     private ChangeWatcher mChangeWatcher;
778 
779     @UnsupportedAppUsage(trackingBug = 123769451)
780     private ArrayList<TextWatcher> mListeners;
781 
782     // display attributes
783     @UnsupportedAppUsage
784     private final TextPaint mTextPaint;
785     @UnsupportedAppUsage
786     private boolean mUserSetTextScaleX;
787     @UnsupportedAppUsage
788     private Layout mLayout;
789     private boolean mLocalesChanged = false;
790     private int mTextSizeUnit = -1;
791     private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
792     private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
793 
794     // This is used to reflect the current user preference for changing font weight and making text
795     // more bold.
796     private int mFontWeightAdjustment;
797     private Typeface mOriginalTypeface;
798 
799     // True if setKeyListener() has been explicitly called
800     private boolean mListenerChanged = false;
801     // True if internationalized input should be used for numbers and date and time.
802     private final boolean mUseInternationalizedInput;
803 
804     // Fallback fonts that end up getting used should be allowed to affect line spacing.
805     private static final int FALLBACK_LINE_SPACING_NONE = 0;
806     private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1;
807     private static final int FALLBACK_LINE_SPACING_ALL = 2;
808 
809     private int mUseFallbackLineSpacing;
810     // True if the view text can be padded for compat reasons, when the view is translated.
811     private final boolean mUseTextPaddingForUiTranslation;
812 
813     @ViewDebug.ExportedProperty(category = "text")
814     @UnsupportedAppUsage
815     private int mGravity = Gravity.TOP | Gravity.START;
816     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
817     private boolean mHorizontallyScrolling;
818 
819     private int mAutoLinkMask;
820     private boolean mLinksClickable = true;
821 
822     @UnsupportedAppUsage
823     private float mSpacingMult = 1.0f;
824     @UnsupportedAppUsage
825     private float mSpacingAdd = 0.0f;
826 
827     private int mBreakStrategy;
828     private int mHyphenationFrequency;
829     private int mJustificationMode;
830 
831     @UnsupportedAppUsage
832     private int mMaximum = Integer.MAX_VALUE;
833     @UnsupportedAppUsage
834     private int mMaxMode = LINES;
835     @UnsupportedAppUsage
836     private int mMinimum = 0;
837     @UnsupportedAppUsage
838     private int mMinMode = LINES;
839 
840     @UnsupportedAppUsage
841     private int mOldMaximum = mMaximum;
842     @UnsupportedAppUsage
843     private int mOldMaxMode = mMaxMode;
844 
845     @UnsupportedAppUsage
846     private int mMaxWidth = Integer.MAX_VALUE;
847     @UnsupportedAppUsage
848     private int mMaxWidthMode = PIXELS;
849     @UnsupportedAppUsage
850     private int mMinWidth = 0;
851     @UnsupportedAppUsage
852     private int mMinWidthMode = PIXELS;
853 
854     @UnsupportedAppUsage
855     private boolean mSingleLine;
856     @UnsupportedAppUsage
857     private int mDesiredHeightAtMeasure = -1;
858     @UnsupportedAppUsage
859     private boolean mIncludePad = true;
860     private int mDeferScroll = -1;
861 
862     // tmp primitives, so we don't alloc them on each draw
863     private Rect mTempRect;
864     private long mLastScroll;
865     private Scroller mScroller;
866     private TextPaint mTempTextPaint;
867 
868     @UnsupportedAppUsage
869     private BoringLayout.Metrics mBoring;
870     @UnsupportedAppUsage
871     private BoringLayout.Metrics mHintBoring;
872     @UnsupportedAppUsage
873     private BoringLayout mSavedLayout;
874     @UnsupportedAppUsage
875     private BoringLayout mSavedHintLayout;
876 
877     @UnsupportedAppUsage
878     private TextDirectionHeuristic mTextDir;
879 
880     private InputFilter[] mFilters = NO_FILTERS;
881 
882     /**
883      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
884      * the same as {@link Process#myUserHandle()}.
885      *
886      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
887      * other apps may need to set this so that the system can use right user's resources and
888      * services such as input methods and spell checkers.</p>
889      *
890      * @see #setTextOperationUser(UserHandle)
891      */
892     @Nullable
893     private UserHandle mTextOperationUser;
894 
895     private volatile Locale mCurrentSpellCheckerLocaleCache;
896 
897     // It is possible to have a selection even when mEditor is null (programmatically set, like when
898     // a link is pressed). These highlight-related fields do not go in mEditor.
899     @UnsupportedAppUsage
900     int mHighlightColor = 0x6633B5E5;
901     private Path mHighlightPath;
902     @UnsupportedAppUsage
903     private final Paint mHighlightPaint;
904     @UnsupportedAppUsage
905     private boolean mHighlightPathBogus = true;
906 
907     // Although these fields are specific to editable text, they are not added to Editor because
908     // they are defined by the TextView's style and are theme-dependent.
909     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
910     int mCursorDrawableRes;
911     private Drawable mCursorDrawable;
912     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
913     // by removing it, but we would break apps targeting <= P that use it by reflection.
914     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
915     int mTextSelectHandleLeftRes;
916     private Drawable mTextSelectHandleLeft;
917     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
918     // by removing it, but we would break apps targeting <= P that use it by reflection.
919     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
920     int mTextSelectHandleRightRes;
921     private Drawable mTextSelectHandleRight;
922     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
923     // by removing it, but we would break apps targeting <= P that use it by reflection.
924     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
925     int mTextSelectHandleRes;
926     private Drawable mTextSelectHandle;
927     int mTextEditSuggestionItemLayout;
928     int mTextEditSuggestionContainerLayout;
929     int mTextEditSuggestionHighlightStyle;
930 
931     private static final int NO_POINTER_ID = -1;
932     /**
933      * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among
934      * TextView and the handle views which are rendered on popup windows.
935      */
936     private int mPrimePointerId = NO_POINTER_ID;
937 
938     /**
939      * Whether the prime pointer is from the event delivered to selection handle or insertion
940      * handle.
941      */
942     private boolean mIsPrimePointerFromHandleView;
943 
944     /**
945      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
946      * See {@link #createEditorIfNeeded()}.
947      */
948     @UnsupportedAppUsage
949     private Editor mEditor;
950 
951     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
952     private static final int DEVICE_PROVISIONED_NO = 1;
953     private static final int DEVICE_PROVISIONED_YES = 2;
954 
955     /**
956      * Some special options such as sharing selected text should only be shown if the device
957      * is provisioned. Only check the provisioned state once for a given view instance.
958      */
959     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
960 
961     /**
962      * The TextView does not auto-size text (default).
963      */
964     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
965 
966     /**
967      * The TextView scales text size both horizontally and vertically to fit within the
968      * container.
969      */
970     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
971 
972     /** @hide */
973     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
974             AUTO_SIZE_TEXT_TYPE_NONE,
975             AUTO_SIZE_TEXT_TYPE_UNIFORM
976     })
977     @Retention(RetentionPolicy.SOURCE)
978     public @interface AutoSizeTextType {}
979     // Default minimum size for auto-sizing text in scaled pixels.
980     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
981     // Default maximum size for auto-sizing text in scaled pixels.
982     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
983     // Default value for the step size in pixels.
984     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
985     // Use this to specify that any of the auto-size configuration int values have not been set.
986     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
987     // Auto-size text type.
988     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
989     // Specify if auto-size text is needed.
990     private boolean mNeedsAutoSizeText = false;
991     // Step size for auto-sizing in pixels.
992     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
993     // Minimum text size for auto-sizing in pixels.
994     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
995     // Maximum text size for auto-sizing in pixels.
996     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
997     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
998     // when auto-sizing text.
999     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
1000     // Specifies whether auto-size should use the provided auto size steps set or if it should
1001     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
1002     // mAutoSizeStepGranularityInPx.
1003     private boolean mHasPresetAutoSizeValues = false;
1004 
1005     // Autofill-related attributes
1006     //
1007     // Indicates whether the text was set statically or dynamically, so it can be used to
1008     // sanitize autofill requests.
1009     private boolean mTextSetFromXmlOrResourceId = false;
1010     // Resource id used to set the text.
1011     private @StringRes int mTextId = Resources.ID_NULL;
1012     // Resource id used to set the hint.
1013     private @StringRes int mHintId = Resources.ID_NULL;
1014     //
1015     // End of autofill-related attributes
1016 
1017     /**
1018      * Kick-start the font cache for the zygote process (to pay the cost of
1019      * initializing freetype for our default font only once).
1020      * @hide
1021      */
1022     public static void preloadFontCache() {
1023         if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
1024             return;
1025         }
1026         Paint p = new Paint();
1027         p.setAntiAlias(true);
1028         // Ensure that the Typeface is loaded here.
1029         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
1030         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
1031         // since Paint.measureText can not be called without Typeface static initializer.
1032         p.setTypeface(Typeface.DEFAULT);
1033         // We don't care about the result, just the side-effect of measuring.
1034         p.measureText("H");
1035     }
1036 
1037     /**
1038      * Interface definition for a callback to be invoked when an action is
1039      * performed on the editor.
1040      */
1041     public interface OnEditorActionListener {
1042         /**
1043          * Called when an action is being performed.
1044          *
1045          * @param v The view that was clicked.
1046          * @param actionId Identifier of the action.  This will be either the
1047          * identifier you supplied, or {@link EditorInfo#IME_NULL
1048          * EditorInfo.IME_NULL} if being called due to the enter key
1049          * being pressed.
1050          * @param event If triggered by an enter key, this is the event;
1051          * otherwise, this is null.
1052          * @return Return true if you have consumed the action, else false.
1053          */
1054         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
1055     }
1056 
1057     public TextView(Context context) {
1058         this(context, null);
1059     }
1060 
1061     public TextView(Context context, @Nullable AttributeSet attrs) {
1062         this(context, attrs, com.android.internal.R.attr.textViewStyle);
1063     }
1064 
1065     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
1066         this(context, attrs, defStyleAttr, 0);
1067     }
1068 
1069     @SuppressWarnings("deprecation")
1070     public TextView(
1071             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1072         super(context, attrs, defStyleAttr, defStyleRes);
1073 
1074         // TextView is important by default, unless app developer overrode attribute.
1075         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
1076             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
1077         }
1078         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
1079             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
1080         }
1081 
1082         setTextInternal("");
1083 
1084         final Resources res = getResources();
1085         final CompatibilityInfo compat = res.getCompatibilityInfo();
1086 
1087         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
1088         mTextPaint.density = res.getDisplayMetrics().density;
1089         mTextPaint.setCompatibilityScaling(compat.applicationScale);
1090 
1091         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1092         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
1093 
1094         mMovement = getDefaultMovementMethod();
1095 
1096         mTransformation = null;
1097 
1098         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
1099         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
1100         attributes.mTextSize = 15;
1101         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1102         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1103         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1104 
1105         final Resources.Theme theme = context.getTheme();
1106 
1107         /*
1108          * Look the appearance up without checking first if it exists because
1109          * almost every TextView has one and it greatly simplifies the logic
1110          * to be able to parse the appearance first and then let specific tags
1111          * for this View override it.
1112          */
1113         TypedArray a = theme.obtainStyledAttributes(attrs,
1114                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1115         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1116                 attrs, a, defStyleAttr, defStyleRes);
1117         TypedArray appearance = null;
1118         int ap = a.getResourceId(
1119                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1120         a.recycle();
1121         if (ap != -1) {
1122             appearance = theme.obtainStyledAttributes(
1123                     ap, com.android.internal.R.styleable.TextAppearance);
1124             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1125                     null, appearance, 0, ap);
1126         }
1127         if (appearance != null) {
1128             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1129             attributes.mFontFamilyExplicit = false;
1130             appearance.recycle();
1131         }
1132 
1133         boolean editable = getDefaultEditable();
1134         CharSequence inputMethod = null;
1135         int numeric = 0;
1136         CharSequence digits = null;
1137         boolean phone = false;
1138         boolean autotext = false;
1139         int autocap = -1;
1140         int buffertype = 0;
1141         boolean selectallonfocus = false;
1142         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1143                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1144         ColorStateList drawableTint = null;
1145         BlendMode drawableTintMode = null;
1146         int drawablePadding = 0;
1147         int ellipsize = ELLIPSIZE_NOT_SET;
1148         boolean singleLine = false;
1149         int maxlength = -1;
1150         CharSequence text = "";
1151         CharSequence hint = null;
1152         boolean password = false;
1153         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1154         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1155         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1156         int inputType = EditorInfo.TYPE_NULL;
1157         a = theme.obtainStyledAttributes(
1158                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1159         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1160                 defStyleAttr, defStyleRes);
1161         int firstBaselineToTopHeight = -1;
1162         int lastBaselineToBottomHeight = -1;
1163         int lineHeight = -1;
1164 
1165         readTextAppearance(context, a, attributes, true /* styleArray */);
1166 
1167         int n = a.getIndexCount();
1168 
1169         // Must set id in a temporary variable because it will be reset by setText()
1170         boolean textIsSetFromXml = false;
1171         for (int i = 0; i < n; i++) {
1172             int attr = a.getIndex(i);
1173 
1174             switch (attr) {
1175                 case com.android.internal.R.styleable.TextView_editable:
1176                     editable = a.getBoolean(attr, editable);
1177                     break;
1178 
1179                 case com.android.internal.R.styleable.TextView_inputMethod:
1180                     inputMethod = a.getText(attr);
1181                     break;
1182 
1183                 case com.android.internal.R.styleable.TextView_numeric:
1184                     numeric = a.getInt(attr, numeric);
1185                     break;
1186 
1187                 case com.android.internal.R.styleable.TextView_digits:
1188                     digits = a.getText(attr);
1189                     break;
1190 
1191                 case com.android.internal.R.styleable.TextView_phoneNumber:
1192                     phone = a.getBoolean(attr, phone);
1193                     break;
1194 
1195                 case com.android.internal.R.styleable.TextView_autoText:
1196                     autotext = a.getBoolean(attr, autotext);
1197                     break;
1198 
1199                 case com.android.internal.R.styleable.TextView_capitalize:
1200                     autocap = a.getInt(attr, autocap);
1201                     break;
1202 
1203                 case com.android.internal.R.styleable.TextView_bufferType:
1204                     buffertype = a.getInt(attr, buffertype);
1205                     break;
1206 
1207                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1208                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1209                     break;
1210 
1211                 case com.android.internal.R.styleable.TextView_autoLink:
1212                     mAutoLinkMask = a.getInt(attr, 0);
1213                     break;
1214 
1215                 case com.android.internal.R.styleable.TextView_linksClickable:
1216                     mLinksClickable = a.getBoolean(attr, true);
1217                     break;
1218 
1219                 case com.android.internal.R.styleable.TextView_drawableLeft:
1220                     drawableLeft = a.getDrawable(attr);
1221                     break;
1222 
1223                 case com.android.internal.R.styleable.TextView_drawableTop:
1224                     drawableTop = a.getDrawable(attr);
1225                     break;
1226 
1227                 case com.android.internal.R.styleable.TextView_drawableRight:
1228                     drawableRight = a.getDrawable(attr);
1229                     break;
1230 
1231                 case com.android.internal.R.styleable.TextView_drawableBottom:
1232                     drawableBottom = a.getDrawable(attr);
1233                     break;
1234 
1235                 case com.android.internal.R.styleable.TextView_drawableStart:
1236                     drawableStart = a.getDrawable(attr);
1237                     break;
1238 
1239                 case com.android.internal.R.styleable.TextView_drawableEnd:
1240                     drawableEnd = a.getDrawable(attr);
1241                     break;
1242 
1243                 case com.android.internal.R.styleable.TextView_drawableTint:
1244                     drawableTint = a.getColorStateList(attr);
1245                     break;
1246 
1247                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1248                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1249                             drawableTintMode);
1250                     break;
1251 
1252                 case com.android.internal.R.styleable.TextView_drawablePadding:
1253                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1254                     break;
1255 
1256                 case com.android.internal.R.styleable.TextView_maxLines:
1257                     setMaxLines(a.getInt(attr, -1));
1258                     break;
1259 
1260                 case com.android.internal.R.styleable.TextView_maxHeight:
1261                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1262                     break;
1263 
1264                 case com.android.internal.R.styleable.TextView_lines:
1265                     setLines(a.getInt(attr, -1));
1266                     break;
1267 
1268                 case com.android.internal.R.styleable.TextView_height:
1269                     setHeight(a.getDimensionPixelSize(attr, -1));
1270                     break;
1271 
1272                 case com.android.internal.R.styleable.TextView_minLines:
1273                     setMinLines(a.getInt(attr, -1));
1274                     break;
1275 
1276                 case com.android.internal.R.styleable.TextView_minHeight:
1277                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1278                     break;
1279 
1280                 case com.android.internal.R.styleable.TextView_maxEms:
1281                     setMaxEms(a.getInt(attr, -1));
1282                     break;
1283 
1284                 case com.android.internal.R.styleable.TextView_maxWidth:
1285                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1286                     break;
1287 
1288                 case com.android.internal.R.styleable.TextView_ems:
1289                     setEms(a.getInt(attr, -1));
1290                     break;
1291 
1292                 case com.android.internal.R.styleable.TextView_width:
1293                     setWidth(a.getDimensionPixelSize(attr, -1));
1294                     break;
1295 
1296                 case com.android.internal.R.styleable.TextView_minEms:
1297                     setMinEms(a.getInt(attr, -1));
1298                     break;
1299 
1300                 case com.android.internal.R.styleable.TextView_minWidth:
1301                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1302                     break;
1303 
1304                 case com.android.internal.R.styleable.TextView_gravity:
1305                     setGravity(a.getInt(attr, -1));
1306                     break;
1307 
1308                 case com.android.internal.R.styleable.TextView_hint:
1309                     mHintId = a.getResourceId(attr, Resources.ID_NULL);
1310                     hint = a.getText(attr);
1311                     break;
1312 
1313                 case com.android.internal.R.styleable.TextView_text:
1314                     textIsSetFromXml = true;
1315                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1316                     text = a.getText(attr);
1317                     break;
1318 
1319                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1320                     if (a.getBoolean(attr, false)) {
1321                         setHorizontallyScrolling(true);
1322                     }
1323                     break;
1324 
1325                 case com.android.internal.R.styleable.TextView_singleLine:
1326                     singleLine = a.getBoolean(attr, singleLine);
1327                     break;
1328 
1329                 case com.android.internal.R.styleable.TextView_ellipsize:
1330                     ellipsize = a.getInt(attr, ellipsize);
1331                     break;
1332 
1333                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1334                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1335                     break;
1336 
1337                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1338                     if (!a.getBoolean(attr, true)) {
1339                         setIncludeFontPadding(false);
1340                     }
1341                     break;
1342 
1343                 case com.android.internal.R.styleable.TextView_cursorVisible:
1344                     if (!a.getBoolean(attr, true)) {
1345                         setCursorVisible(false);
1346                     }
1347                     break;
1348 
1349                 case com.android.internal.R.styleable.TextView_maxLength:
1350                     maxlength = a.getInt(attr, -1);
1351                     break;
1352 
1353                 case com.android.internal.R.styleable.TextView_textScaleX:
1354                     setTextScaleX(a.getFloat(attr, 1.0f));
1355                     break;
1356 
1357                 case com.android.internal.R.styleable.TextView_freezesText:
1358                     mFreezesText = a.getBoolean(attr, false);
1359                     break;
1360 
1361                 case com.android.internal.R.styleable.TextView_enabled:
1362                     setEnabled(a.getBoolean(attr, isEnabled()));
1363                     break;
1364 
1365                 case com.android.internal.R.styleable.TextView_password:
1366                     password = a.getBoolean(attr, password);
1367                     break;
1368 
1369                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1370                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1371                     break;
1372 
1373                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1374                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1375                     break;
1376 
1377                 case com.android.internal.R.styleable.TextView_inputType:
1378                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1379                     break;
1380 
1381                 case com.android.internal.R.styleable.TextView_allowUndo:
1382                     createEditorIfNeeded();
1383                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1384                     break;
1385 
1386                 case com.android.internal.R.styleable.TextView_imeOptions:
1387                     createEditorIfNeeded();
1388                     mEditor.createInputContentTypeIfNeeded();
1389                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1390                             mEditor.mInputContentType.imeOptions);
1391                     break;
1392 
1393                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1394                     createEditorIfNeeded();
1395                     mEditor.createInputContentTypeIfNeeded();
1396                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1397                     break;
1398 
1399                 case com.android.internal.R.styleable.TextView_imeActionId:
1400                     createEditorIfNeeded();
1401                     mEditor.createInputContentTypeIfNeeded();
1402                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1403                             mEditor.mInputContentType.imeActionId);
1404                     break;
1405 
1406                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1407                     setPrivateImeOptions(a.getString(attr));
1408                     break;
1409 
1410                 case com.android.internal.R.styleable.TextView_editorExtras:
1411                     try {
1412                         setInputExtras(a.getResourceId(attr, 0));
1413                     } catch (XmlPullParserException e) {
1414                         Log.w(LOG_TAG, "Failure reading input extras", e);
1415                     } catch (IOException e) {
1416                         Log.w(LOG_TAG, "Failure reading input extras", e);
1417                     }
1418                     break;
1419 
1420                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1421                     mCursorDrawableRes = a.getResourceId(attr, 0);
1422                     break;
1423 
1424                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1425                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1426                     break;
1427 
1428                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1429                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1430                     break;
1431 
1432                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1433                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1434                     break;
1435 
1436                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1437                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1438                     break;
1439 
1440                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1441                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1442                     break;
1443 
1444                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1445                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1446                     break;
1447 
1448                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1449                     setTextIsSelectable(a.getBoolean(attr, false));
1450                     break;
1451 
1452                 case com.android.internal.R.styleable.TextView_breakStrategy:
1453                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1454                     break;
1455 
1456                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1457                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1458                     break;
1459 
1460                 case com.android.internal.R.styleable.TextView_lineBreakStyle:
1461                     mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE);
1462                     break;
1463 
1464                 case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
1465                     mLineBreakWordStyle = a.getInt(attr,
1466                             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
1467                     break;
1468 
1469                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1470                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1471                     break;
1472 
1473                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1474                     autoSizeStepGranularityInPx = a.getDimension(attr,
1475                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1476                     break;
1477 
1478                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1479                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1480                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1481                     break;
1482 
1483                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1484                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1485                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1486                     break;
1487 
1488                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1489                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1490                     if (autoSizeStepSizeArrayResId > 0) {
1491                         final TypedArray autoSizePresetTextSizes = a.getResources()
1492                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1493                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1494                         autoSizePresetTextSizes.recycle();
1495                     }
1496                     break;
1497                 case com.android.internal.R.styleable.TextView_justificationMode:
1498                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1499                     break;
1500 
1501                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1502                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1503                     break;
1504 
1505                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1506                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1507                     break;
1508 
1509                 case com.android.internal.R.styleable.TextView_lineHeight:
1510                     lineHeight = a.getDimensionPixelSize(attr, -1);
1511                     break;
1512             }
1513         }
1514 
1515         a.recycle();
1516 
1517         BufferType bufferType = BufferType.EDITABLE;
1518 
1519         final int variation =
1520                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1521         final boolean passwordInputType = variation
1522                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1523         final boolean webPasswordInputType = variation
1524                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1525         final boolean numberPasswordInputType = variation
1526                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1527 
1528         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1529         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1530         if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
1531             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL;
1532         } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) {
1533             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
1534         } else {
1535             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
1536         }
1537         // TODO(b/179693024): Use a ChangeId instead.
1538         mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R;
1539 
1540         if (inputMethod != null) {
1541             Class<?> c;
1542 
1543             try {
1544                 c = Class.forName(inputMethod.toString());
1545             } catch (ClassNotFoundException ex) {
1546                 throw new RuntimeException(ex);
1547             }
1548 
1549             try {
1550                 createEditorIfNeeded();
1551                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1552             } catch (InstantiationException ex) {
1553                 throw new RuntimeException(ex);
1554             } catch (IllegalAccessException ex) {
1555                 throw new RuntimeException(ex);
1556             }
1557             try {
1558                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1559                         ? inputType
1560                         : mEditor.mKeyListener.getInputType();
1561             } catch (IncompatibleClassChangeError e) {
1562                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1563             }
1564         } else if (digits != null) {
1565             createEditorIfNeeded();
1566             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1567             // If no input type was specified, we will default to generic
1568             // text, since we can't tell the IME about the set of digits
1569             // that was selected.
1570             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1571                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1572         } else if (inputType != EditorInfo.TYPE_NULL) {
1573             setInputType(inputType, true);
1574             // If set, the input type overrides what was set using the deprecated singleLine flag.
1575             singleLine = !isMultilineInputType(inputType);
1576         } else if (phone) {
1577             createEditorIfNeeded();
1578             mEditor.mKeyListener = DialerKeyListener.getInstance();
1579             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1580         } else if (numeric != 0) {
1581             createEditorIfNeeded();
1582             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1583                     null,  // locale
1584                     (numeric & SIGNED) != 0,
1585                     (numeric & DECIMAL) != 0);
1586             inputType = mEditor.mKeyListener.getInputType();
1587             mEditor.mInputType = inputType;
1588         } else if (autotext || autocap != -1) {
1589             TextKeyListener.Capitalize cap;
1590 
1591             inputType = EditorInfo.TYPE_CLASS_TEXT;
1592 
1593             switch (autocap) {
1594                 case 1:
1595                     cap = TextKeyListener.Capitalize.SENTENCES;
1596                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1597                     break;
1598 
1599                 case 2:
1600                     cap = TextKeyListener.Capitalize.WORDS;
1601                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1602                     break;
1603 
1604                 case 3:
1605                     cap = TextKeyListener.Capitalize.CHARACTERS;
1606                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1607                     break;
1608 
1609                 default:
1610                     cap = TextKeyListener.Capitalize.NONE;
1611                     break;
1612             }
1613 
1614             createEditorIfNeeded();
1615             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1616             mEditor.mInputType = inputType;
1617         } else if (editable) {
1618             createEditorIfNeeded();
1619             mEditor.mKeyListener = TextKeyListener.getInstance();
1620             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1621         } else if (isTextSelectable()) {
1622             // Prevent text changes from keyboard.
1623             if (mEditor != null) {
1624                 mEditor.mKeyListener = null;
1625                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1626             }
1627             bufferType = BufferType.SPANNABLE;
1628             // So that selection can be changed using arrow keys and touch is handled.
1629             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1630         } else {
1631             if (mEditor != null) mEditor.mKeyListener = null;
1632 
1633             switch (buffertype) {
1634                 case 0:
1635                     bufferType = BufferType.NORMAL;
1636                     break;
1637                 case 1:
1638                     bufferType = BufferType.SPANNABLE;
1639                     break;
1640                 case 2:
1641                     bufferType = BufferType.EDITABLE;
1642                     break;
1643             }
1644         }
1645 
1646         if (mEditor != null) {
1647             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1648                     numberPasswordInputType);
1649         }
1650 
1651         if (selectallonfocus) {
1652             createEditorIfNeeded();
1653             mEditor.mSelectAllOnFocus = true;
1654 
1655             if (bufferType == BufferType.NORMAL) {
1656                 bufferType = BufferType.SPANNABLE;
1657             }
1658         }
1659 
1660         // Set up the tint (if needed) before setting the drawables so that it
1661         // gets applied correctly.
1662         if (drawableTint != null || drawableTintMode != null) {
1663             if (mDrawables == null) {
1664                 mDrawables = new Drawables(context);
1665             }
1666             if (drawableTint != null) {
1667                 mDrawables.mTintList = drawableTint;
1668                 mDrawables.mHasTint = true;
1669             }
1670             if (drawableTintMode != null) {
1671                 mDrawables.mBlendMode = drawableTintMode;
1672                 mDrawables.mHasTintMode = true;
1673             }
1674         }
1675 
1676         // This call will save the initial left/right drawables
1677         setCompoundDrawablesWithIntrinsicBounds(
1678                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1679         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1680         setCompoundDrawablePadding(drawablePadding);
1681 
1682         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1683         // of lines of height are unchanged for multi-line TextViews.
1684         setInputTypeSingleLine(singleLine);
1685         applySingleLine(singleLine, singleLine, singleLine,
1686                 // Does not apply automated max length filter since length filter will be resolved
1687                 // later in this function.
1688                 false
1689         );
1690 
1691         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1692             ellipsize = ELLIPSIZE_END;
1693         }
1694 
1695         switch (ellipsize) {
1696             case ELLIPSIZE_START:
1697                 setEllipsize(TextUtils.TruncateAt.START);
1698                 break;
1699             case ELLIPSIZE_MIDDLE:
1700                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1701                 break;
1702             case ELLIPSIZE_END:
1703                 setEllipsize(TextUtils.TruncateAt.END);
1704                 break;
1705             case ELLIPSIZE_MARQUEE:
1706                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1707                     setHorizontalFadingEdgeEnabled(true);
1708                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1709                 } else {
1710                     setHorizontalFadingEdgeEnabled(false);
1711                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1712                 }
1713                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1714                 break;
1715         }
1716 
1717         final boolean isPassword = password || passwordInputType || webPasswordInputType
1718                 || numberPasswordInputType;
1719         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1720                 && (mEditor.mInputType
1721                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1722                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1723         if (isMonospaceEnforced) {
1724             attributes.mTypefaceIndex = MONOSPACE;
1725         }
1726 
1727         mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment;
1728         applyTextAppearance(attributes);
1729 
1730         if (isPassword) {
1731             setTransformationMethod(PasswordTransformationMethod.getInstance());
1732         }
1733 
1734         // For addressing b/145128646
1735         // For the performance reason, we limit characters for single line text field.
1736         if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) {
1737             mSingleLineLengthFilter = new InputFilter.LengthFilter(
1738                 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
1739         }
1740 
1741         if (mSingleLineLengthFilter != null) {
1742             setFilters(new InputFilter[] { mSingleLineLengthFilter });
1743         } else if (maxlength >= 0) {
1744             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1745         } else {
1746             setFilters(NO_FILTERS);
1747         }
1748 
1749         setText(text, bufferType);
1750         if (mText == null) {
1751             mText = "";
1752         }
1753         if (mTransformed == null) {
1754             mTransformed = "";
1755         }
1756 
1757         if (textIsSetFromXml) {
1758             mTextSetFromXmlOrResourceId = true;
1759         }
1760 
1761         if (hint != null) setHint(hint);
1762 
1763         /*
1764          * Views are not normally clickable unless specified to be.
1765          * However, TextViews that have input or movement methods *are*
1766          * clickable by default. By setting clickable here, we implicitly set focusable as well
1767          * if not overridden by the developer.
1768          */
1769         a = context.obtainStyledAttributes(
1770                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1771         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1772         boolean clickable = canInputOrMove || isClickable();
1773         boolean longClickable = canInputOrMove || isLongClickable();
1774         int focusable = getFocusable();
1775 
1776         n = a.getIndexCount();
1777         for (int i = 0; i < n; i++) {
1778             int attr = a.getIndex(i);
1779 
1780             switch (attr) {
1781                 case com.android.internal.R.styleable.View_focusable:
1782                     TypedValue val = new TypedValue();
1783                     if (a.getValue(attr, val)) {
1784                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1785                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1786                                 : val.data;
1787                     }
1788                     break;
1789 
1790                 case com.android.internal.R.styleable.View_clickable:
1791                     clickable = a.getBoolean(attr, clickable);
1792                     break;
1793 
1794                 case com.android.internal.R.styleable.View_longClickable:
1795                     longClickable = a.getBoolean(attr, longClickable);
1796                     break;
1797             }
1798         }
1799         a.recycle();
1800 
1801         // Some apps were relying on the undefined behavior of focusable winning over
1802         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1803         // when starting with EditText and setting only focusable=false). To keep those apps from
1804         // breaking, re-apply the focusable attribute here.
1805         if (focusable != getFocusable()) {
1806             setFocusable(focusable);
1807         }
1808         setClickable(clickable);
1809         setLongClickable(longClickable);
1810 
1811         if (mEditor != null) mEditor.prepareCursorControllers();
1812 
1813         // If not explicitly specified this view is important for accessibility.
1814         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1815             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1816         }
1817 
1818         if (supportsAutoSizeText()) {
1819             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1820                 // If uniform auto-size has been specified but preset values have not been set then
1821                 // replace the auto-size configuration values that have not been specified with the
1822                 // defaults.
1823                 if (!mHasPresetAutoSizeValues) {
1824                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1825 
1826                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1827                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1828                                 TypedValue.COMPLEX_UNIT_SP,
1829                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1830                                 displayMetrics);
1831                     }
1832 
1833                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1834                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1835                                 TypedValue.COMPLEX_UNIT_SP,
1836                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1837                                 displayMetrics);
1838                     }
1839 
1840                     if (autoSizeStepGranularityInPx
1841                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1842                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1843                     }
1844 
1845                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1846                             autoSizeMaxTextSizeInPx,
1847                             autoSizeStepGranularityInPx);
1848                 }
1849 
1850                 setupAutoSizeText();
1851             }
1852         } else {
1853             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1854         }
1855 
1856         if (firstBaselineToTopHeight >= 0) {
1857             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1858         }
1859         if (lastBaselineToBottomHeight >= 0) {
1860             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1861         }
1862         if (lineHeight >= 0) {
1863             setLineHeight(lineHeight);
1864         }
1865     }
1866 
1867     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)1868     private void setTextInternal(@Nullable CharSequence text) {
1869         mText = text;
1870         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
1871         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
1872     }
1873 
1874     /**
1875      * Specify whether this widget should automatically scale the text to try to perfectly fit
1876      * within the layout bounds by using the default auto-size configuration.
1877      *
1878      * @param autoSizeTextType the type of auto-size. Must be one of
1879      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1880      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1881      *
1882      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1883      *
1884      * @attr ref android.R.styleable#TextView_autoSizeTextType
1885      *
1886      * @see #getAutoSizeTextType()
1887      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1888     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1889         if (supportsAutoSizeText()) {
1890             switch (autoSizeTextType) {
1891                 case AUTO_SIZE_TEXT_TYPE_NONE:
1892                     clearAutoSizeConfiguration();
1893                     break;
1894                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1895                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1896                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1897                             TypedValue.COMPLEX_UNIT_SP,
1898                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1899                             displayMetrics);
1900                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1901                             TypedValue.COMPLEX_UNIT_SP,
1902                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1903                             displayMetrics);
1904 
1905                     validateAndSetAutoSizeTextTypeUniformConfiguration(
1906                             autoSizeMinTextSizeInPx,
1907                             autoSizeMaxTextSizeInPx,
1908                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1909                     if (setupAutoSizeText()) {
1910                         autoSizeText();
1911                         invalidate();
1912                     }
1913                     break;
1914                 default:
1915                     throw new IllegalArgumentException(
1916                             "Unknown auto-size text type: " + autoSizeTextType);
1917             }
1918         }
1919     }
1920 
1921     /**
1922      * Specify whether this widget should automatically scale the text to try to perfectly fit
1923      * within the layout bounds. If all the configuration params are valid the type of auto-size is
1924      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1925      *
1926      * @param autoSizeMinTextSize the minimum text size available for auto-size
1927      * @param autoSizeMaxTextSize the maximum text size available for auto-size
1928      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1929      *                                the minimum and maximum text size in order to build the set of
1930      *                                text sizes the system uses to choose from when auto-sizing
1931      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1932      *             possible dimension units
1933      *
1934      * @throws IllegalArgumentException if any of the configuration params are invalid.
1935      *
1936      * @attr ref android.R.styleable#TextView_autoSizeTextType
1937      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1938      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1939      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1940      *
1941      * @see #setAutoSizeTextTypeWithDefaults(int)
1942      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1943      * @see #getAutoSizeMinTextSize()
1944      * @see #getAutoSizeMaxTextSize()
1945      * @see #getAutoSizeStepGranularity()
1946      * @see #getAutoSizeTextAvailableSizes()
1947      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1948     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1949             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1950         if (supportsAutoSizeText()) {
1951             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1952             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1953                     unit, autoSizeMinTextSize, displayMetrics);
1954             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1955                     unit, autoSizeMaxTextSize, displayMetrics);
1956             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1957                     unit, autoSizeStepGranularity, displayMetrics);
1958 
1959             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1960                     autoSizeMaxTextSizeInPx,
1961                     autoSizeStepGranularityInPx);
1962 
1963             if (setupAutoSizeText()) {
1964                 autoSizeText();
1965                 invalidate();
1966             }
1967         }
1968     }
1969 
1970     /**
1971      * Specify whether this widget should automatically scale the text to try to perfectly fit
1972      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1973      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1974      *
1975      * @param presetSizes an {@code int} array of sizes in pixels
1976      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1977      *             the possible dimension units
1978      *
1979      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1980      *
1981      * @attr ref android.R.styleable#TextView_autoSizeTextType
1982      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1983      *
1984      * @see #setAutoSizeTextTypeWithDefaults(int)
1985      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1986      * @see #getAutoSizeMinTextSize()
1987      * @see #getAutoSizeMaxTextSize()
1988      * @see #getAutoSizeTextAvailableSizes()
1989      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1990     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1991         if (supportsAutoSizeText()) {
1992             final int presetSizesLength = presetSizes.length;
1993             if (presetSizesLength > 0) {
1994                 int[] presetSizesInPx = new int[presetSizesLength];
1995 
1996                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1997                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1998                 } else {
1999                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2000                     // Convert all to sizes to pixels.
2001                     for (int i = 0; i < presetSizesLength; i++) {
2002                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
2003                             presetSizes[i], displayMetrics));
2004                     }
2005                 }
2006 
2007                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
2008                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
2009                     throw new IllegalArgumentException("None of the preset sizes is valid: "
2010                             + Arrays.toString(presetSizes));
2011                 }
2012             } else {
2013                 mHasPresetAutoSizeValues = false;
2014             }
2015 
2016             if (setupAutoSizeText()) {
2017                 autoSizeText();
2018                 invalidate();
2019             }
2020         }
2021     }
2022 
2023     /**
2024      * Returns the type of auto-size set for this widget.
2025      *
2026      * @return an {@code int} corresponding to one of the auto-size types:
2027      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
2028      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
2029      *
2030      * @attr ref android.R.styleable#TextView_autoSizeTextType
2031      *
2032      * @see #setAutoSizeTextTypeWithDefaults(int)
2033      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2034      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2035      */
2036     @InspectableProperty(enumMapping = {
2037             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
2038             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
2039     })
2040     @AutoSizeTextType
getAutoSizeTextType()2041     public int getAutoSizeTextType() {
2042         return mAutoSizeTextType;
2043     }
2044 
2045     /**
2046      * @return the current auto-size step granularity in pixels.
2047      *
2048      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
2049      *
2050      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2051      */
2052     @InspectableProperty
getAutoSizeStepGranularity()2053     public int getAutoSizeStepGranularity() {
2054         return Math.round(mAutoSizeStepGranularityInPx);
2055     }
2056 
2057     /**
2058      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
2059      *         if auto-size has not been configured this function returns {@code -1}.
2060      *
2061      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2062      *
2063      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2064      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2065      */
2066     @InspectableProperty
getAutoSizeMinTextSize()2067     public int getAutoSizeMinTextSize() {
2068         return Math.round(mAutoSizeMinTextSizeInPx);
2069     }
2070 
2071     /**
2072      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
2073      *         if auto-size has not been configured this function returns {@code -1}.
2074      *
2075      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2076      *
2077      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2078      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2079      */
2080     @InspectableProperty
getAutoSizeMaxTextSize()2081     public int getAutoSizeMaxTextSize() {
2082         return Math.round(mAutoSizeMaxTextSizeInPx);
2083     }
2084 
2085     /**
2086      * @return the current auto-size {@code int} sizes array (in pixels).
2087      *
2088      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2089      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2090      */
getAutoSizeTextAvailableSizes()2091     public int[] getAutoSizeTextAvailableSizes() {
2092         return mAutoSizeTextSizesInPx;
2093     }
2094 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)2095     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
2096         final int textSizesLength = textSizes.length();
2097         final int[] parsedSizes = new int[textSizesLength];
2098 
2099         if (textSizesLength > 0) {
2100             for (int i = 0; i < textSizesLength; i++) {
2101                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
2102             }
2103             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
2104             setupAutoSizeUniformPresetSizesConfiguration();
2105         }
2106     }
2107 
setupAutoSizeUniformPresetSizesConfiguration()2108     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
2109         final int sizesLength = mAutoSizeTextSizesInPx.length;
2110         mHasPresetAutoSizeValues = sizesLength > 0;
2111         if (mHasPresetAutoSizeValues) {
2112             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2113             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
2114             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
2115             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2116         }
2117         return mHasPresetAutoSizeValues;
2118     }
2119 
2120     /**
2121      * If all params are valid then save the auto-size configuration.
2122      *
2123      * @throws IllegalArgumentException if any of the params are invalid
2124      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2125     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
2126             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
2127         // First validate.
2128         if (autoSizeMinTextSizeInPx <= 0) {
2129             throw new IllegalArgumentException("Minimum auto-size text size ("
2130                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
2131         }
2132 
2133         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2134             throw new IllegalArgumentException("Maximum auto-size text size ("
2135                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2136                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2137         }
2138 
2139         if (autoSizeStepGranularityInPx <= 0) {
2140             throw new IllegalArgumentException("The auto-size step granularity ("
2141                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2142         }
2143 
2144         // All good, persist the configuration.
2145         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2146         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2147         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2148         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2149         mHasPresetAutoSizeValues = false;
2150     }
2151 
clearAutoSizeConfiguration()2152     private void clearAutoSizeConfiguration() {
2153         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2154         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2155         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2156         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2157         mAutoSizeTextSizesInPx = EmptyArray.INT;
2158         mNeedsAutoSizeText = false;
2159     }
2160 
2161     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)2162     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2163         final int presetValuesLength = presetValues.length;
2164         if (presetValuesLength == 0) {
2165             return presetValues;
2166         }
2167         Arrays.sort(presetValues);
2168 
2169         final IntArray uniqueValidSizes = new IntArray();
2170         for (int i = 0; i < presetValuesLength; i++) {
2171             final int currentPresetValue = presetValues[i];
2172 
2173             if (currentPresetValue > 0
2174                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2175                 uniqueValidSizes.add(currentPresetValue);
2176             }
2177         }
2178 
2179         return presetValuesLength == uniqueValidSizes.size()
2180             ? presetValues
2181             : uniqueValidSizes.toArray();
2182     }
2183 
setupAutoSizeText()2184     private boolean setupAutoSizeText() {
2185         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2186             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2187             // not have a predefined set of sizes or if the current sizes array is empty.
2188             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2189                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2190                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2191                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2192                 for (int i = 0; i < autoSizeValuesLength; i++) {
2193                     autoSizeTextSizesInPx[i] = Math.round(
2194                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2195                 }
2196                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2197             }
2198 
2199             mNeedsAutoSizeText = true;
2200         } else {
2201             mNeedsAutoSizeText = false;
2202         }
2203 
2204         return mNeedsAutoSizeText;
2205     }
2206 
parseDimensionArray(TypedArray dimens)2207     private int[] parseDimensionArray(TypedArray dimens) {
2208         if (dimens == null) {
2209             return null;
2210         }
2211         int[] result = new int[dimens.length()];
2212         for (int i = 0; i < result.length; i++) {
2213             result[i] = dimens.getDimensionPixelSize(i, 0);
2214         }
2215         return result;
2216     }
2217 
2218     /**
2219      * @hide
2220      */
2221     @TestApi
2222     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)2223     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
2224         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2225             if (resultCode == Activity.RESULT_OK && data != null) {
2226                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2227                 if (result != null) {
2228                     if (isTextEditable()) {
2229                         ClipData clip = ClipData.newPlainText("", result);
2230                         ContentInfo payload =
2231                                 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build();
2232                         performReceiveContent(payload);
2233                         if (mEditor != null) {
2234                             mEditor.refreshTextActionMode();
2235                         }
2236                     } else {
2237                         if (result.length() > 0) {
2238                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2239                                 .show();
2240                         }
2241                     }
2242                 }
2243             } else if (mSpannable != null) {
2244                 // Reset the selection.
2245                 Selection.setSelection(mSpannable, getSelectionEnd());
2246             }
2247         }
2248     }
2249 
2250     /**
2251      * Sets the Typeface taking into account the given attributes.
2252      *
2253      * @param typeface a typeface
2254      * @param familyName family name string, e.g. "serif"
2255      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2256      * @param style a typeface style
2257      * @param weight a weight value for the Typeface or -1 if not specified.
2258      */
setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2259     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2260             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2261             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2262         if (typeface == null && familyName != null) {
2263             // Lookup normal Typeface from system font map.
2264             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2265             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2266         } else if (typeface != null) {
2267             resolveStyleAndSetTypeface(typeface, style, weight);
2268         } else {  // both typeface and familyName is null.
2269             switch (typefaceIndex) {
2270                 case SANS:
2271                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2272                     break;
2273                 case SERIF:
2274                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2275                     break;
2276                 case MONOSPACE:
2277                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2278                     break;
2279                 case DEFAULT_TYPEFACE:
2280                 default:
2281                     resolveStyleAndSetTypeface(null, style, weight);
2282                     break;
2283             }
2284         }
2285     }
2286 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2287     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2288             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2289         if (weight >= 0) {
2290             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2291             final boolean italic = (style & Typeface.ITALIC) != 0;
2292             setTypeface(Typeface.create(typeface, weight, italic));
2293         } else {
2294             setTypeface(typeface, style);
2295         }
2296     }
2297 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2298     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2299         boolean hasRelativeDrawables = (start != null) || (end != null);
2300         if (hasRelativeDrawables) {
2301             Drawables dr = mDrawables;
2302             if (dr == null) {
2303                 mDrawables = dr = new Drawables(getContext());
2304             }
2305             mDrawables.mOverride = true;
2306             final Rect compoundRect = dr.mCompoundRect;
2307             int[] state = getDrawableState();
2308             if (start != null) {
2309                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2310                 start.setState(state);
2311                 start.copyBounds(compoundRect);
2312                 start.setCallback(this);
2313 
2314                 dr.mDrawableStart = start;
2315                 dr.mDrawableSizeStart = compoundRect.width();
2316                 dr.mDrawableHeightStart = compoundRect.height();
2317             } else {
2318                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2319             }
2320             if (end != null) {
2321                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2322                 end.setState(state);
2323                 end.copyBounds(compoundRect);
2324                 end.setCallback(this);
2325 
2326                 dr.mDrawableEnd = end;
2327                 dr.mDrawableSizeEnd = compoundRect.width();
2328                 dr.mDrawableHeightEnd = compoundRect.height();
2329             } else {
2330                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2331             }
2332             resetResolvedDrawables();
2333             resolveDrawables();
2334             applyCompoundDrawableTint();
2335         }
2336     }
2337 
2338     @android.view.RemotableViewMethod
2339     @Override
setEnabled(boolean enabled)2340     public void setEnabled(boolean enabled) {
2341         if (enabled == isEnabled()) {
2342             return;
2343         }
2344 
2345         if (!enabled) {
2346             // Hide the soft input if the currently active TextView is disabled
2347             InputMethodManager imm = getInputMethodManager();
2348             if (imm != null && imm.isActive(this)) {
2349                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2350             }
2351         }
2352 
2353         super.setEnabled(enabled);
2354 
2355         if (enabled) {
2356             // Make sure IME is updated with current editor info.
2357             InputMethodManager imm = getInputMethodManager();
2358             if (imm != null) imm.restartInput(this);
2359         }
2360 
2361         // Will change text color
2362         if (mEditor != null) {
2363             mEditor.invalidateTextDisplayList();
2364             mEditor.prepareCursorControllers();
2365 
2366             // start or stop the cursor blinking as appropriate
2367             mEditor.makeBlink();
2368         }
2369     }
2370 
2371     /**
2372      * Sets the typeface and style in which the text should be displayed,
2373      * and turns on the fake bold and italic bits in the Paint if the
2374      * Typeface that you provided does not have all the bits in the
2375      * style that you specified.
2376      *
2377      * @attr ref android.R.styleable#TextView_typeface
2378      * @attr ref android.R.styleable#TextView_textStyle
2379      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2380     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2381         if (style > 0) {
2382             if (tf == null) {
2383                 tf = Typeface.defaultFromStyle(style);
2384             } else {
2385                 tf = Typeface.create(tf, style);
2386             }
2387 
2388             setTypeface(tf);
2389             // now compute what (if any) algorithmic styling is needed
2390             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2391             int need = style & ~typefaceStyle;
2392             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2393             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2394         } else {
2395             mTextPaint.setFakeBoldText(false);
2396             mTextPaint.setTextSkewX(0);
2397             setTypeface(tf);
2398         }
2399     }
2400 
2401     /**
2402      * Subclasses override this to specify that they have a KeyListener
2403      * by default even if not specifically called for in the XML options.
2404      */
getDefaultEditable()2405     protected boolean getDefaultEditable() {
2406         return false;
2407     }
2408 
2409     /**
2410      * Subclasses override this to specify a default movement method.
2411      */
getDefaultMovementMethod()2412     protected MovementMethod getDefaultMovementMethod() {
2413         return null;
2414     }
2415 
2416     /**
2417      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2418      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2419      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2420      * the return value from this method to Spannable or Editable, respectively.
2421      *
2422      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2423      * should make your own copy first.</p>
2424      *
2425      * @return The text displayed by the text view.
2426      * @attr ref android.R.styleable#TextView_text
2427      */
2428     @ViewDebug.CapturedViewProperty
2429     @InspectableProperty
getText()2430     public CharSequence getText() {
2431         if (mUseTextPaddingForUiTranslation) {
2432             ViewTranslationCallback callback = getViewTranslationCallback();
2433             if (callback != null && callback instanceof TextViewTranslationCallback) {
2434                 TextViewTranslationCallback defaultCallback =
2435                         (TextViewTranslationCallback) callback;
2436                 if (defaultCallback.isTextPaddingEnabled()
2437                         && defaultCallback.isShowingTranslation()) {
2438                     return defaultCallback.getPaddedText(mText, mTransformed);
2439                 }
2440             }
2441         }
2442         return mText;
2443     }
2444 
2445     /**
2446      * Returns the length, in characters, of the text managed by this TextView
2447      * @return The length of the text managed by the TextView in characters.
2448      */
length()2449     public int length() {
2450         return mText.length();
2451     }
2452 
2453     /**
2454      * Return the text that TextView is displaying as an Editable object. If the text is not
2455      * editable, null is returned.
2456      *
2457      * @see #getText
2458      */
getEditableText()2459     public Editable getEditableText() {
2460         return (mText instanceof Editable) ? (Editable) mText : null;
2461     }
2462 
2463     /**
2464      * @hide
2465      */
2466     @VisibleForTesting
getTransformed()2467     public CharSequence getTransformed() {
2468         return mTransformed;
2469     }
2470 
2471     /**
2472      * Gets the vertical distance between lines of text, in pixels.
2473      * Note that markup within the text can cause individual lines
2474      * to be taller or shorter than this height, and the layout may
2475      * contain additional first-or last-line padding.
2476      * @return The height of one standard line in pixels.
2477      */
2478     @InspectableProperty
getLineHeight()2479     public int getLineHeight() {
2480         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2481     }
2482 
2483     /**
2484      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2485      * This value can be null if the text or width has recently changed.
2486      * @return The Layout that is currently being used to display the text.
2487      */
getLayout()2488     public final Layout getLayout() {
2489         return mLayout;
2490     }
2491 
2492     /**
2493      * @return the {@link android.text.Layout} that is currently being used to
2494      * display the hint text. This can be null.
2495      */
2496     @UnsupportedAppUsage
getHintLayout()2497     final Layout getHintLayout() {
2498         return mHintLayout;
2499     }
2500 
2501     /**
2502      * Retrieve the {@link android.content.UndoManager} that is currently associated
2503      * with this TextView.  By default there is no associated UndoManager, so null
2504      * is returned.  One can be associated with the TextView through
2505      * {@link #setUndoManager(android.content.UndoManager, String)}
2506      *
2507      * @hide
2508      */
getUndoManager()2509     public final UndoManager getUndoManager() {
2510         // TODO: Consider supporting a global undo manager.
2511         throw new UnsupportedOperationException("not implemented");
2512     }
2513 
2514 
2515     /**
2516      * @hide
2517      */
2518     @VisibleForTesting
getEditorForTesting()2519     public final Editor getEditorForTesting() {
2520         return mEditor;
2521     }
2522 
2523     /**
2524      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2525      * done, all edit operations on the TextView will result in appropriate
2526      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2527      * stack.
2528      *
2529      * @param undoManager The {@link android.content.UndoManager} to associate with
2530      * this TextView, or null to clear any existing association.
2531      * @param tag String tag identifying this particular TextView owner in the
2532      * UndoManager.  This is used to keep the correct association with the
2533      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2534      *
2535      * @hide
2536      */
setUndoManager(UndoManager undoManager, String tag)2537     public final void setUndoManager(UndoManager undoManager, String tag) {
2538         // TODO: Consider supporting a global undo manager. An implementation will need to:
2539         // * createEditorIfNeeded()
2540         // * Promote to BufferType.EDITABLE if needed.
2541         // * Update the UndoManager and UndoOwner.
2542         // Likewise it will need to be able to restore the default UndoManager.
2543         throw new UnsupportedOperationException("not implemented");
2544     }
2545 
2546     /**
2547      * Gets the current {@link KeyListener} for the TextView.
2548      * This will frequently be null for non-EditText TextViews.
2549      * @return the current key listener for this TextView.
2550      *
2551      * @attr ref android.R.styleable#TextView_numeric
2552      * @attr ref android.R.styleable#TextView_digits
2553      * @attr ref android.R.styleable#TextView_phoneNumber
2554      * @attr ref android.R.styleable#TextView_inputMethod
2555      * @attr ref android.R.styleable#TextView_capitalize
2556      * @attr ref android.R.styleable#TextView_autoText
2557      */
getKeyListener()2558     public final KeyListener getKeyListener() {
2559         return mEditor == null ? null : mEditor.mKeyListener;
2560     }
2561 
2562     /**
2563      * Sets the key listener to be used with this TextView.  This can be null
2564      * to disallow user input.  Note that this method has significant and
2565      * subtle interactions with soft keyboards and other input method:
2566      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2567      * for important details.  Calling this method will replace the current
2568      * content type of the text view with the content type returned by the
2569      * key listener.
2570      * <p>
2571      * Be warned that if you want a TextView with a key listener or movement
2572      * method not to be focusable, or if you want a TextView without a
2573      * key listener or movement method to be focusable, you must call
2574      * {@link #setFocusable} again after calling this to get the focusability
2575      * back the way you want it.
2576      *
2577      * @attr ref android.R.styleable#TextView_numeric
2578      * @attr ref android.R.styleable#TextView_digits
2579      * @attr ref android.R.styleable#TextView_phoneNumber
2580      * @attr ref android.R.styleable#TextView_inputMethod
2581      * @attr ref android.R.styleable#TextView_capitalize
2582      * @attr ref android.R.styleable#TextView_autoText
2583      */
setKeyListener(KeyListener input)2584     public void setKeyListener(KeyListener input) {
2585         mListenerChanged = true;
2586         setKeyListenerOnly(input);
2587         fixFocusableAndClickableSettings();
2588 
2589         if (input != null) {
2590             createEditorIfNeeded();
2591             setInputTypeFromEditor();
2592         } else {
2593             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2594         }
2595 
2596         InputMethodManager imm = getInputMethodManager();
2597         if (imm != null) imm.restartInput(this);
2598     }
2599 
setInputTypeFromEditor()2600     private void setInputTypeFromEditor() {
2601         try {
2602             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2603         } catch (IncompatibleClassChangeError e) {
2604             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2605         }
2606         // Change inputType, without affecting transformation.
2607         // No need to applySingleLine since mSingleLine is unchanged.
2608         setInputTypeSingleLine(mSingleLine);
2609     }
2610 
setKeyListenerOnly(KeyListener input)2611     private void setKeyListenerOnly(KeyListener input) {
2612         if (mEditor == null && input == null) return; // null is the default value
2613 
2614         createEditorIfNeeded();
2615         if (mEditor.mKeyListener != input) {
2616             mEditor.mKeyListener = input;
2617             if (input != null && !(mText instanceof Editable)) {
2618                 setText(mText);
2619             }
2620 
2621             setFilters((Editable) mText, mFilters);
2622         }
2623     }
2624 
2625     /**
2626      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2627      * which provides positioning, scrolling, and text selection functionality.
2628      * This will frequently be null for non-EditText TextViews.
2629      * @return the movement method being used for this TextView.
2630      * @see android.text.method.MovementMethod
2631      */
getMovementMethod()2632     public final MovementMethod getMovementMethod() {
2633         return mMovement;
2634     }
2635 
2636     /**
2637      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2638      * for this TextView. This can be null to disallow using the arrow keys to move the
2639      * cursor or scroll the view.
2640      * <p>
2641      * Be warned that if you want a TextView with a key listener or movement
2642      * method not to be focusable, or if you want a TextView without a
2643      * key listener or movement method to be focusable, you must call
2644      * {@link #setFocusable} again after calling this to get the focusability
2645      * back the way you want it.
2646      */
setMovementMethod(MovementMethod movement)2647     public final void setMovementMethod(MovementMethod movement) {
2648         if (mMovement != movement) {
2649             mMovement = movement;
2650 
2651             if (movement != null && mSpannable == null) {
2652                 setText(mText);
2653             }
2654 
2655             fixFocusableAndClickableSettings();
2656 
2657             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2658             // mMovement
2659             if (mEditor != null) mEditor.prepareCursorControllers();
2660         }
2661     }
2662 
fixFocusableAndClickableSettings()2663     private void fixFocusableAndClickableSettings() {
2664         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2665             setFocusable(FOCUSABLE);
2666             setClickable(true);
2667             setLongClickable(true);
2668         } else {
2669             setFocusable(FOCUSABLE_AUTO);
2670             setClickable(false);
2671             setLongClickable(false);
2672         }
2673     }
2674 
2675     /**
2676      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2677      * This is frequently null, except for single-line and password fields.
2678      * @return the current transformation method for this TextView.
2679      *
2680      * @attr ref android.R.styleable#TextView_password
2681      * @attr ref android.R.styleable#TextView_singleLine
2682      */
getTransformationMethod()2683     public final TransformationMethod getTransformationMethod() {
2684         return mTransformation;
2685     }
2686 
2687     /**
2688      * Sets the transformation that is applied to the text that this
2689      * TextView is displaying.
2690      *
2691      * @attr ref android.R.styleable#TextView_password
2692      * @attr ref android.R.styleable#TextView_singleLine
2693      */
setTransformationMethod(TransformationMethod method)2694     public final void setTransformationMethod(TransformationMethod method) {
2695         if (method == mTransformation) {
2696             // Avoid the setText() below if the transformation is
2697             // the same.
2698             return;
2699         }
2700         if (mTransformation != null) {
2701             if (mSpannable != null) {
2702                 mSpannable.removeSpan(mTransformation);
2703             }
2704         }
2705 
2706         mTransformation = method;
2707 
2708         if (method instanceof TransformationMethod2) {
2709             TransformationMethod2 method2 = (TransformationMethod2) method;
2710             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2711             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2712         } else {
2713             mAllowTransformationLengthChange = false;
2714         }
2715 
2716         setText(mText);
2717 
2718         if (hasPasswordTransformationMethod()) {
2719             notifyViewAccessibilityStateChangedIfNeeded(
2720                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2721         }
2722 
2723         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2724         // getTextDirectionHeuristic, needs reset
2725         mTextDir = getTextDirectionHeuristic();
2726     }
2727 
2728     /**
2729      * Returns the top padding of the view, plus space for the top
2730      * Drawable if any.
2731      */
getCompoundPaddingTop()2732     public int getCompoundPaddingTop() {
2733         final Drawables dr = mDrawables;
2734         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2735             return mPaddingTop;
2736         } else {
2737             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2738         }
2739     }
2740 
2741     /**
2742      * Returns the bottom padding of the view, plus space for the bottom
2743      * Drawable if any.
2744      */
getCompoundPaddingBottom()2745     public int getCompoundPaddingBottom() {
2746         final Drawables dr = mDrawables;
2747         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2748             return mPaddingBottom;
2749         } else {
2750             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2751         }
2752     }
2753 
2754     /**
2755      * Returns the left padding of the view, plus space for the left
2756      * Drawable if any.
2757      */
getCompoundPaddingLeft()2758     public int getCompoundPaddingLeft() {
2759         final Drawables dr = mDrawables;
2760         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2761             return mPaddingLeft;
2762         } else {
2763             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2764         }
2765     }
2766 
2767     /**
2768      * Returns the right padding of the view, plus space for the right
2769      * Drawable if any.
2770      */
getCompoundPaddingRight()2771     public int getCompoundPaddingRight() {
2772         final Drawables dr = mDrawables;
2773         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2774             return mPaddingRight;
2775         } else {
2776             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2777         }
2778     }
2779 
2780     /**
2781      * Returns the start padding of the view, plus space for the start
2782      * Drawable if any.
2783      */
getCompoundPaddingStart()2784     public int getCompoundPaddingStart() {
2785         resolveDrawables();
2786         switch(getLayoutDirection()) {
2787             default:
2788             case LAYOUT_DIRECTION_LTR:
2789                 return getCompoundPaddingLeft();
2790             case LAYOUT_DIRECTION_RTL:
2791                 return getCompoundPaddingRight();
2792         }
2793     }
2794 
2795     /**
2796      * Returns the end padding of the view, plus space for the end
2797      * Drawable if any.
2798      */
getCompoundPaddingEnd()2799     public int getCompoundPaddingEnd() {
2800         resolveDrawables();
2801         switch(getLayoutDirection()) {
2802             default:
2803             case LAYOUT_DIRECTION_LTR:
2804                 return getCompoundPaddingRight();
2805             case LAYOUT_DIRECTION_RTL:
2806                 return getCompoundPaddingLeft();
2807         }
2808     }
2809 
2810     /**
2811      * Returns the extended top padding of the view, including both the
2812      * top Drawable if any and any extra space to keep more than maxLines
2813      * of text from showing.  It is only valid to call this after measuring.
2814      */
getExtendedPaddingTop()2815     public int getExtendedPaddingTop() {
2816         if (mMaxMode != LINES) {
2817             return getCompoundPaddingTop();
2818         }
2819 
2820         if (mLayout == null) {
2821             assumeLayout();
2822         }
2823 
2824         if (mLayout.getLineCount() <= mMaximum) {
2825             return getCompoundPaddingTop();
2826         }
2827 
2828         int top = getCompoundPaddingTop();
2829         int bottom = getCompoundPaddingBottom();
2830         int viewht = getHeight() - top - bottom;
2831         int layoutht = mLayout.getLineTop(mMaximum);
2832 
2833         if (layoutht >= viewht) {
2834             return top;
2835         }
2836 
2837         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2838         if (gravity == Gravity.TOP) {
2839             return top;
2840         } else if (gravity == Gravity.BOTTOM) {
2841             return top + viewht - layoutht;
2842         } else { // (gravity == Gravity.CENTER_VERTICAL)
2843             return top + (viewht - layoutht) / 2;
2844         }
2845     }
2846 
2847     /**
2848      * Returns the extended bottom padding of the view, including both the
2849      * bottom Drawable if any and any extra space to keep more than maxLines
2850      * of text from showing.  It is only valid to call this after measuring.
2851      */
getExtendedPaddingBottom()2852     public int getExtendedPaddingBottom() {
2853         if (mMaxMode != LINES) {
2854             return getCompoundPaddingBottom();
2855         }
2856 
2857         if (mLayout == null) {
2858             assumeLayout();
2859         }
2860 
2861         if (mLayout.getLineCount() <= mMaximum) {
2862             return getCompoundPaddingBottom();
2863         }
2864 
2865         int top = getCompoundPaddingTop();
2866         int bottom = getCompoundPaddingBottom();
2867         int viewht = getHeight() - top - bottom;
2868         int layoutht = mLayout.getLineTop(mMaximum);
2869 
2870         if (layoutht >= viewht) {
2871             return bottom;
2872         }
2873 
2874         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2875         if (gravity == Gravity.TOP) {
2876             return bottom + viewht - layoutht;
2877         } else if (gravity == Gravity.BOTTOM) {
2878             return bottom;
2879         } else { // (gravity == Gravity.CENTER_VERTICAL)
2880             return bottom + (viewht - layoutht) / 2;
2881         }
2882     }
2883 
2884     /**
2885      * Returns the total left padding of the view, including the left
2886      * Drawable if any.
2887      */
getTotalPaddingLeft()2888     public int getTotalPaddingLeft() {
2889         return getCompoundPaddingLeft();
2890     }
2891 
2892     /**
2893      * Returns the total right padding of the view, including the right
2894      * Drawable if any.
2895      */
getTotalPaddingRight()2896     public int getTotalPaddingRight() {
2897         return getCompoundPaddingRight();
2898     }
2899 
2900     /**
2901      * Returns the total start padding of the view, including the start
2902      * Drawable if any.
2903      */
getTotalPaddingStart()2904     public int getTotalPaddingStart() {
2905         return getCompoundPaddingStart();
2906     }
2907 
2908     /**
2909      * Returns the total end padding of the view, including the end
2910      * Drawable if any.
2911      */
getTotalPaddingEnd()2912     public int getTotalPaddingEnd() {
2913         return getCompoundPaddingEnd();
2914     }
2915 
2916     /**
2917      * Returns the total top padding of the view, including the top
2918      * Drawable if any, the extra space to keep more than maxLines
2919      * from showing, and the vertical offset for gravity, if any.
2920      */
getTotalPaddingTop()2921     public int getTotalPaddingTop() {
2922         return getExtendedPaddingTop() + getVerticalOffset(true);
2923     }
2924 
2925     /**
2926      * Returns the total bottom padding of the view, including the bottom
2927      * Drawable if any, the extra space to keep more than maxLines
2928      * from showing, and the vertical offset for gravity, if any.
2929      */
getTotalPaddingBottom()2930     public int getTotalPaddingBottom() {
2931         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2932     }
2933 
2934     /**
2935      * Sets the Drawables (if any) to appear to the left of, above, to the
2936      * right of, and below the text. Use {@code null} if you do not want a
2937      * Drawable there. The Drawables must already have had
2938      * {@link Drawable#setBounds} called.
2939      * <p>
2940      * Calling this method will overwrite any Drawables previously set using
2941      * {@link #setCompoundDrawablesRelative} or related methods.
2942      *
2943      * @attr ref android.R.styleable#TextView_drawableLeft
2944      * @attr ref android.R.styleable#TextView_drawableTop
2945      * @attr ref android.R.styleable#TextView_drawableRight
2946      * @attr ref android.R.styleable#TextView_drawableBottom
2947      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2948     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2949             @Nullable Drawable right, @Nullable Drawable bottom) {
2950         Drawables dr = mDrawables;
2951 
2952         // We're switching to absolute, discard relative.
2953         if (dr != null) {
2954             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2955             dr.mDrawableStart = null;
2956             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2957             dr.mDrawableEnd = null;
2958             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2959             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2960         }
2961 
2962         final boolean drawables = left != null || top != null || right != null || bottom != null;
2963         if (!drawables) {
2964             // Clearing drawables...  can we free the data structure?
2965             if (dr != null) {
2966                 if (!dr.hasMetadata()) {
2967                     mDrawables = null;
2968                 } else {
2969                     // We need to retain the last set padding, so just clear
2970                     // out all of the fields in the existing structure.
2971                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2972                         if (dr.mShowing[i] != null) {
2973                             dr.mShowing[i].setCallback(null);
2974                         }
2975                         dr.mShowing[i] = null;
2976                     }
2977                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2978                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2979                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2980                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2981                 }
2982             }
2983         } else {
2984             if (dr == null) {
2985                 mDrawables = dr = new Drawables(getContext());
2986             }
2987 
2988             mDrawables.mOverride = false;
2989 
2990             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2991                 dr.mShowing[Drawables.LEFT].setCallback(null);
2992             }
2993             dr.mShowing[Drawables.LEFT] = left;
2994 
2995             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2996                 dr.mShowing[Drawables.TOP].setCallback(null);
2997             }
2998             dr.mShowing[Drawables.TOP] = top;
2999 
3000             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
3001                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3002             }
3003             dr.mShowing[Drawables.RIGHT] = right;
3004 
3005             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3006                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3007             }
3008             dr.mShowing[Drawables.BOTTOM] = bottom;
3009 
3010             final Rect compoundRect = dr.mCompoundRect;
3011             int[] state;
3012 
3013             state = getDrawableState();
3014 
3015             if (left != null) {
3016                 left.setState(state);
3017                 left.copyBounds(compoundRect);
3018                 left.setCallback(this);
3019                 dr.mDrawableSizeLeft = compoundRect.width();
3020                 dr.mDrawableHeightLeft = compoundRect.height();
3021             } else {
3022                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3023             }
3024 
3025             if (right != null) {
3026                 right.setState(state);
3027                 right.copyBounds(compoundRect);
3028                 right.setCallback(this);
3029                 dr.mDrawableSizeRight = compoundRect.width();
3030                 dr.mDrawableHeightRight = compoundRect.height();
3031             } else {
3032                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3033             }
3034 
3035             if (top != null) {
3036                 top.setState(state);
3037                 top.copyBounds(compoundRect);
3038                 top.setCallback(this);
3039                 dr.mDrawableSizeTop = compoundRect.height();
3040                 dr.mDrawableWidthTop = compoundRect.width();
3041             } else {
3042                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3043             }
3044 
3045             if (bottom != null) {
3046                 bottom.setState(state);
3047                 bottom.copyBounds(compoundRect);
3048                 bottom.setCallback(this);
3049                 dr.mDrawableSizeBottom = compoundRect.height();
3050                 dr.mDrawableWidthBottom = compoundRect.width();
3051             } else {
3052                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3053             }
3054         }
3055 
3056         // Save initial left/right drawables
3057         if (dr != null) {
3058             dr.mDrawableLeftInitial = left;
3059             dr.mDrawableRightInitial = right;
3060         }
3061 
3062         resetResolvedDrawables();
3063         resolveDrawables();
3064         applyCompoundDrawableTint();
3065         invalidate();
3066         requestLayout();
3067     }
3068 
3069     /**
3070      * Sets the Drawables (if any) to appear to the left of, above, to the
3071      * right of, and below the text. Use 0 if you do not want a Drawable there.
3072      * The Drawables' bounds will be set to their intrinsic bounds.
3073      * <p>
3074      * Calling this method will overwrite any Drawables previously set using
3075      * {@link #setCompoundDrawablesRelative} or related methods.
3076      *
3077      * @param left Resource identifier of the left Drawable.
3078      * @param top Resource identifier of the top Drawable.
3079      * @param right Resource identifier of the right Drawable.
3080      * @param bottom Resource identifier of the bottom Drawable.
3081      *
3082      * @attr ref android.R.styleable#TextView_drawableLeft
3083      * @attr ref android.R.styleable#TextView_drawableTop
3084      * @attr ref android.R.styleable#TextView_drawableRight
3085      * @attr ref android.R.styleable#TextView_drawableBottom
3086      */
3087     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)3088     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
3089             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
3090         final Context context = getContext();
3091         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
3092                 top != 0 ? context.getDrawable(top) : null,
3093                 right != 0 ? context.getDrawable(right) : null,
3094                 bottom != 0 ? context.getDrawable(bottom) : null);
3095     }
3096 
3097     /**
3098      * Sets the Drawables (if any) to appear to the left of, above, to the
3099      * right of, and below the text. Use {@code null} if you do not want a
3100      * Drawable there. The Drawables' bounds will be set to their intrinsic
3101      * bounds.
3102      * <p>
3103      * Calling this method will overwrite any Drawables previously set using
3104      * {@link #setCompoundDrawablesRelative} or related methods.
3105      *
3106      * @attr ref android.R.styleable#TextView_drawableLeft
3107      * @attr ref android.R.styleable#TextView_drawableTop
3108      * @attr ref android.R.styleable#TextView_drawableRight
3109      * @attr ref android.R.styleable#TextView_drawableBottom
3110      */
3111     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3112     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
3113             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
3114 
3115         if (left != null) {
3116             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
3117         }
3118         if (right != null) {
3119             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
3120         }
3121         if (top != null) {
3122             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3123         }
3124         if (bottom != null) {
3125             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3126         }
3127         setCompoundDrawables(left, top, right, bottom);
3128     }
3129 
3130     /**
3131      * Sets the Drawables (if any) to appear to the start of, above, to the end
3132      * of, and below the text. Use {@code null} if you do not want a Drawable
3133      * there. The Drawables must already have had {@link Drawable#setBounds}
3134      * called.
3135      * <p>
3136      * Calling this method will overwrite any Drawables previously set using
3137      * {@link #setCompoundDrawables} or related methods.
3138      *
3139      * @attr ref android.R.styleable#TextView_drawableStart
3140      * @attr ref android.R.styleable#TextView_drawableTop
3141      * @attr ref android.R.styleable#TextView_drawableEnd
3142      * @attr ref android.R.styleable#TextView_drawableBottom
3143      */
3144     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3145     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
3146             @Nullable Drawable end, @Nullable Drawable bottom) {
3147         Drawables dr = mDrawables;
3148 
3149         // We're switching to relative, discard absolute.
3150         if (dr != null) {
3151             if (dr.mShowing[Drawables.LEFT] != null) {
3152                 dr.mShowing[Drawables.LEFT].setCallback(null);
3153             }
3154             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3155             if (dr.mShowing[Drawables.RIGHT] != null) {
3156                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3157             }
3158             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3159             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3160             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3161         }
3162 
3163         final boolean drawables = start != null || top != null
3164                 || end != null || bottom != null;
3165 
3166         if (!drawables) {
3167             // Clearing drawables...  can we free the data structure?
3168             if (dr != null) {
3169                 if (!dr.hasMetadata()) {
3170                     mDrawables = null;
3171                 } else {
3172                     // We need to retain the last set padding, so just clear
3173                     // out all of the fields in the existing structure.
3174                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3175                     dr.mDrawableStart = null;
3176                     if (dr.mShowing[Drawables.TOP] != null) {
3177                         dr.mShowing[Drawables.TOP].setCallback(null);
3178                     }
3179                     dr.mShowing[Drawables.TOP] = null;
3180                     if (dr.mDrawableEnd != null) {
3181                         dr.mDrawableEnd.setCallback(null);
3182                     }
3183                     dr.mDrawableEnd = null;
3184                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3185                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3186                     }
3187                     dr.mShowing[Drawables.BOTTOM] = null;
3188                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3189                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3190                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3191                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3192                 }
3193             }
3194         } else {
3195             if (dr == null) {
3196                 mDrawables = dr = new Drawables(getContext());
3197             }
3198 
3199             mDrawables.mOverride = true;
3200 
3201             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3202                 dr.mDrawableStart.setCallback(null);
3203             }
3204             dr.mDrawableStart = start;
3205 
3206             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3207                 dr.mShowing[Drawables.TOP].setCallback(null);
3208             }
3209             dr.mShowing[Drawables.TOP] = top;
3210 
3211             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3212                 dr.mDrawableEnd.setCallback(null);
3213             }
3214             dr.mDrawableEnd = end;
3215 
3216             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3217                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3218             }
3219             dr.mShowing[Drawables.BOTTOM] = bottom;
3220 
3221             final Rect compoundRect = dr.mCompoundRect;
3222             int[] state;
3223 
3224             state = getDrawableState();
3225 
3226             if (start != null) {
3227                 start.setState(state);
3228                 start.copyBounds(compoundRect);
3229                 start.setCallback(this);
3230                 dr.mDrawableSizeStart = compoundRect.width();
3231                 dr.mDrawableHeightStart = compoundRect.height();
3232             } else {
3233                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3234             }
3235 
3236             if (end != null) {
3237                 end.setState(state);
3238                 end.copyBounds(compoundRect);
3239                 end.setCallback(this);
3240                 dr.mDrawableSizeEnd = compoundRect.width();
3241                 dr.mDrawableHeightEnd = compoundRect.height();
3242             } else {
3243                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3244             }
3245 
3246             if (top != null) {
3247                 top.setState(state);
3248                 top.copyBounds(compoundRect);
3249                 top.setCallback(this);
3250                 dr.mDrawableSizeTop = compoundRect.height();
3251                 dr.mDrawableWidthTop = compoundRect.width();
3252             } else {
3253                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3254             }
3255 
3256             if (bottom != null) {
3257                 bottom.setState(state);
3258                 bottom.copyBounds(compoundRect);
3259                 bottom.setCallback(this);
3260                 dr.mDrawableSizeBottom = compoundRect.height();
3261                 dr.mDrawableWidthBottom = compoundRect.width();
3262             } else {
3263                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3264             }
3265         }
3266 
3267         resetResolvedDrawables();
3268         resolveDrawables();
3269         invalidate();
3270         requestLayout();
3271     }
3272 
3273     /**
3274      * Sets the Drawables (if any) to appear to the start of, above, to the end
3275      * of, and below the text. Use 0 if you do not want a Drawable there. The
3276      * Drawables' bounds will be set to their intrinsic bounds.
3277      * <p>
3278      * Calling this method will overwrite any Drawables previously set using
3279      * {@link #setCompoundDrawables} or related methods.
3280      *
3281      * @param start Resource identifier of the start Drawable.
3282      * @param top Resource identifier of the top Drawable.
3283      * @param end Resource identifier of the end Drawable.
3284      * @param bottom Resource identifier of the bottom Drawable.
3285      *
3286      * @attr ref android.R.styleable#TextView_drawableStart
3287      * @attr ref android.R.styleable#TextView_drawableTop
3288      * @attr ref android.R.styleable#TextView_drawableEnd
3289      * @attr ref android.R.styleable#TextView_drawableBottom
3290      */
3291     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3292     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3293             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3294         final Context context = getContext();
3295         setCompoundDrawablesRelativeWithIntrinsicBounds(
3296                 start != 0 ? context.getDrawable(start) : null,
3297                 top != 0 ? context.getDrawable(top) : null,
3298                 end != 0 ? context.getDrawable(end) : null,
3299                 bottom != 0 ? context.getDrawable(bottom) : null);
3300     }
3301 
3302     /**
3303      * Sets the Drawables (if any) to appear to the start of, above, to the end
3304      * of, and below the text. Use {@code null} if you do not want a Drawable
3305      * there. The Drawables' bounds will be set to their intrinsic bounds.
3306      * <p>
3307      * Calling this method will overwrite any Drawables previously set using
3308      * {@link #setCompoundDrawables} or related methods.
3309      *
3310      * @attr ref android.R.styleable#TextView_drawableStart
3311      * @attr ref android.R.styleable#TextView_drawableTop
3312      * @attr ref android.R.styleable#TextView_drawableEnd
3313      * @attr ref android.R.styleable#TextView_drawableBottom
3314      */
3315     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3316     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3317             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3318 
3319         if (start != null) {
3320             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3321         }
3322         if (end != null) {
3323             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3324         }
3325         if (top != null) {
3326             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3327         }
3328         if (bottom != null) {
3329             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3330         }
3331         setCompoundDrawablesRelative(start, top, end, bottom);
3332     }
3333 
3334     /**
3335      * Returns drawables for the left, top, right, and bottom borders.
3336      *
3337      * @attr ref android.R.styleable#TextView_drawableLeft
3338      * @attr ref android.R.styleable#TextView_drawableTop
3339      * @attr ref android.R.styleable#TextView_drawableRight
3340      * @attr ref android.R.styleable#TextView_drawableBottom
3341      */
3342     @NonNull
getCompoundDrawables()3343     public Drawable[] getCompoundDrawables() {
3344         final Drawables dr = mDrawables;
3345         if (dr != null) {
3346             return dr.mShowing.clone();
3347         } else {
3348             return new Drawable[] { null, null, null, null };
3349         }
3350     }
3351 
3352     /**
3353      * Returns drawables for the start, top, end, and bottom borders.
3354      *
3355      * @attr ref android.R.styleable#TextView_drawableStart
3356      * @attr ref android.R.styleable#TextView_drawableTop
3357      * @attr ref android.R.styleable#TextView_drawableEnd
3358      * @attr ref android.R.styleable#TextView_drawableBottom
3359      */
3360     @NonNull
getCompoundDrawablesRelative()3361     public Drawable[] getCompoundDrawablesRelative() {
3362         final Drawables dr = mDrawables;
3363         if (dr != null) {
3364             return new Drawable[] {
3365                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3366                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3367             };
3368         } else {
3369             return new Drawable[] { null, null, null, null };
3370         }
3371     }
3372 
3373     /**
3374      * Sets the size of the padding between the compound drawables and
3375      * the text.
3376      *
3377      * @attr ref android.R.styleable#TextView_drawablePadding
3378      */
3379     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3380     public void setCompoundDrawablePadding(int pad) {
3381         Drawables dr = mDrawables;
3382         if (pad == 0) {
3383             if (dr != null) {
3384                 dr.mDrawablePadding = pad;
3385             }
3386         } else {
3387             if (dr == null) {
3388                 mDrawables = dr = new Drawables(getContext());
3389             }
3390             dr.mDrawablePadding = pad;
3391         }
3392 
3393         invalidate();
3394         requestLayout();
3395     }
3396 
3397     /**
3398      * Returns the padding between the compound drawables and the text.
3399      *
3400      * @attr ref android.R.styleable#TextView_drawablePadding
3401      */
3402     @InspectableProperty(name = "drawablePadding")
getCompoundDrawablePadding()3403     public int getCompoundDrawablePadding() {
3404         final Drawables dr = mDrawables;
3405         return dr != null ? dr.mDrawablePadding : 0;
3406     }
3407 
3408     /**
3409      * Applies a tint to the compound drawables. Does not modify the
3410      * current tint mode, which is {@link BlendMode#SRC_IN} by default.
3411      * <p>
3412      * Subsequent calls to
3413      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3414      * and related methods will automatically mutate the drawables and apply
3415      * the specified tint and tint mode using
3416      * {@link Drawable#setTintList(ColorStateList)}.
3417      *
3418      * @param tint the tint to apply, may be {@code null} to clear tint
3419      *
3420      * @attr ref android.R.styleable#TextView_drawableTint
3421      * @see #getCompoundDrawableTintList()
3422      * @see Drawable#setTintList(ColorStateList)
3423      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3424     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3425         if (mDrawables == null) {
3426             mDrawables = new Drawables(getContext());
3427         }
3428         mDrawables.mTintList = tint;
3429         mDrawables.mHasTint = true;
3430 
3431         applyCompoundDrawableTint();
3432     }
3433 
3434     /**
3435      * @return the tint applied to the compound drawables
3436      * @attr ref android.R.styleable#TextView_drawableTint
3437      * @see #setCompoundDrawableTintList(ColorStateList)
3438      */
3439     @InspectableProperty(name = "drawableTint")
getCompoundDrawableTintList()3440     public ColorStateList getCompoundDrawableTintList() {
3441         return mDrawables != null ? mDrawables.mTintList : null;
3442     }
3443 
3444     /**
3445      * Specifies the blending mode used to apply the tint specified by
3446      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3447      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3448      *
3449      * @param tintMode the blending mode used to apply the tint, may be
3450      *                 {@code null} to clear tint
3451      * @attr ref android.R.styleable#TextView_drawableTintMode
3452      * @see #setCompoundDrawableTintList(ColorStateList)
3453      * @see Drawable#setTintMode(PorterDuff.Mode)
3454      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3455     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3456         setCompoundDrawableTintBlendMode(tintMode != null
3457                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3458     }
3459 
3460     /**
3461      * Specifies the blending mode used to apply the tint specified by
3462      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3463      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3464      *
3465      * @param blendMode the blending mode used to apply the tint, may be
3466      *                 {@code null} to clear tint
3467      * @attr ref android.R.styleable#TextView_drawableTintMode
3468      * @see #setCompoundDrawableTintList(ColorStateList)
3469      * @see Drawable#setTintBlendMode(BlendMode)
3470      */
setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3471     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3472         if (mDrawables == null) {
3473             mDrawables = new Drawables(getContext());
3474         }
3475         mDrawables.mBlendMode = blendMode;
3476         mDrawables.mHasTintMode = true;
3477 
3478         applyCompoundDrawableTint();
3479     }
3480 
3481     /**
3482      * Returns the blending mode used to apply the tint to the compound
3483      * drawables, if specified.
3484      *
3485      * @return the blending mode used to apply the tint to the compound
3486      *         drawables
3487      * @attr ref android.R.styleable#TextView_drawableTintMode
3488      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3489      *
3490      */
3491     @InspectableProperty(name = "drawableTintMode")
getCompoundDrawableTintMode()3492     public PorterDuff.Mode getCompoundDrawableTintMode() {
3493         BlendMode mode = getCompoundDrawableTintBlendMode();
3494         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3495     }
3496 
3497     /**
3498      * Returns the blending mode used to apply the tint to the compound
3499      * drawables, if specified.
3500      *
3501      * @return the blending mode used to apply the tint to the compound
3502      *         drawables
3503      * @attr ref android.R.styleable#TextView_drawableTintMode
3504      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3505      */
3506     @InspectableProperty(name = "drawableBlendMode",
3507             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
getCompoundDrawableTintBlendMode()3508     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3509         return mDrawables != null ? mDrawables.mBlendMode : null;
3510     }
3511 
applyCompoundDrawableTint()3512     private void applyCompoundDrawableTint() {
3513         if (mDrawables == null) {
3514             return;
3515         }
3516 
3517         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3518             final ColorStateList tintList = mDrawables.mTintList;
3519             final BlendMode blendMode = mDrawables.mBlendMode;
3520             final boolean hasTint = mDrawables.mHasTint;
3521             final boolean hasTintMode = mDrawables.mHasTintMode;
3522             final int[] state = getDrawableState();
3523 
3524             for (Drawable dr : mDrawables.mShowing) {
3525                 if (dr == null) {
3526                     continue;
3527                 }
3528 
3529                 if (dr == mDrawables.mDrawableError) {
3530                     // From a developer's perspective, the error drawable isn't
3531                     // a compound drawable. Don't apply the generic compound
3532                     // drawable tint to it.
3533                     continue;
3534                 }
3535 
3536                 dr.mutate();
3537 
3538                 if (hasTint) {
3539                     dr.setTintList(tintList);
3540                 }
3541 
3542                 if (hasTintMode) {
3543                     dr.setTintBlendMode(blendMode);
3544                 }
3545 
3546                 // The drawable (or one of its children) may not have been
3547                 // stateful before applying the tint, so let's try again.
3548                 if (dr.isStateful()) {
3549                     dr.setState(state);
3550                 }
3551             }
3552         }
3553     }
3554 
3555     /**
3556      * @inheritDoc
3557      *
3558      * @see #setFirstBaselineToTopHeight(int)
3559      * @see #setLastBaselineToBottomHeight(int)
3560      */
3561     @Override
setPadding(int left, int top, int right, int bottom)3562     public void setPadding(int left, int top, int right, int bottom) {
3563         if (left != mPaddingLeft
3564                 || right != mPaddingRight
3565                 || top != mPaddingTop
3566                 ||  bottom != mPaddingBottom) {
3567             nullLayouts();
3568         }
3569 
3570         // the super call will requestLayout()
3571         super.setPadding(left, top, right, bottom);
3572         invalidate();
3573     }
3574 
3575     /**
3576      * @inheritDoc
3577      *
3578      * @see #setFirstBaselineToTopHeight(int)
3579      * @see #setLastBaselineToBottomHeight(int)
3580      */
3581     @Override
setPaddingRelative(int start, int top, int end, int bottom)3582     public void setPaddingRelative(int start, int top, int end, int bottom) {
3583         if (start != getPaddingStart()
3584                 || end != getPaddingEnd()
3585                 || top != mPaddingTop
3586                 || bottom != mPaddingBottom) {
3587             nullLayouts();
3588         }
3589 
3590         // the super call will requestLayout()
3591         super.setPaddingRelative(start, top, end, bottom);
3592         invalidate();
3593     }
3594 
3595     /**
3596      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3597      * the distance between the top of the TextView and first line's baseline.
3598      * <p>
3599      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3600      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3601      *
3602      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3603      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3604      * Moreover since this function sets the top padding, if the height of the TextView is less than
3605      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3606      * down and bottom will be clipped.
3607      *
3608      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3609      *      in pixels
3610      *
3611      * @see #getFirstBaselineToTopHeight()
3612      * @see #setLastBaselineToBottomHeight(int)
3613      * @see #setPadding(int, int, int, int)
3614      * @see #setPaddingRelative(int, int, int, int)
3615      *
3616      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3617      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3618     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3619         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3620 
3621         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3622         final int fontMetricsTop;
3623         if (getIncludeFontPadding()) {
3624             fontMetricsTop = fontMetrics.top;
3625         } else {
3626             fontMetricsTop = fontMetrics.ascent;
3627         }
3628 
3629         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3630         // in settings). At the moment, we don't.
3631 
3632         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3633             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3634             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3635         }
3636     }
3637 
3638     /**
3639      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3640      * the distance between the bottom of the TextView and the last line's baseline.
3641      * <p>
3642      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3643      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3644      *
3645      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3646      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3647      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3648      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3649      * clipped.
3650      *
3651      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3652      *      in pixels
3653      *
3654      * @see #getLastBaselineToBottomHeight()
3655      * @see #setFirstBaselineToTopHeight(int)
3656      * @see #setPadding(int, int, int, int)
3657      * @see #setPaddingRelative(int, int, int, int)
3658      *
3659      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3660      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3661     public void setLastBaselineToBottomHeight(
3662             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3663         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3664 
3665         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3666         final int fontMetricsBottom;
3667         if (getIncludeFontPadding()) {
3668             fontMetricsBottom = fontMetrics.bottom;
3669         } else {
3670             fontMetricsBottom = fontMetrics.descent;
3671         }
3672 
3673         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3674         // in settings). At the moment, we don't.
3675 
3676         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3677             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3678             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3679         }
3680     }
3681 
3682     /**
3683      * Returns the distance between the first text baseline and the top of this TextView.
3684      *
3685      * @see #setFirstBaselineToTopHeight(int)
3686      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3687      */
3688     @InspectableProperty
getFirstBaselineToTopHeight()3689     public int getFirstBaselineToTopHeight() {
3690         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3691     }
3692 
3693     /**
3694      * Returns the distance between the last text baseline and the bottom of this TextView.
3695      *
3696      * @see #setLastBaselineToBottomHeight(int)
3697      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3698      */
3699     @InspectableProperty
getLastBaselineToBottomHeight()3700     public int getLastBaselineToBottomHeight() {
3701         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3702     }
3703 
3704     /**
3705      * Gets the autolink mask of the text.
3706      *
3707      * See {@link Linkify#ALL} and peers for possible values.
3708      *
3709      * @attr ref android.R.styleable#TextView_autoLink
3710      */
3711     @InspectableProperty(name = "autoLink", flagMapping = {
3712             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3713             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3714             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3715             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3716     })
getAutoLinkMask()3717     public final int getAutoLinkMask() {
3718         return mAutoLinkMask;
3719     }
3720 
3721     /**
3722      * Sets the Drawable corresponding to the selection handle used for
3723      * positioning the cursor within text. The Drawable defaults to the value
3724      * of the textSelectHandle attribute.
3725      * Note that any change applied to the handle Drawable will not be visible
3726      * until the handle is hidden and then drawn again.
3727      *
3728      * @see #setTextSelectHandle(int)
3729      * @attr ref android.R.styleable#TextView_textSelectHandle
3730      */
3731     @android.view.RemotableViewMethod
setTextSelectHandle(@onNull Drawable textSelectHandle)3732     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3733         Preconditions.checkNotNull(textSelectHandle,
3734                 "The text select handle should not be null.");
3735         mTextSelectHandle = textSelectHandle;
3736         mTextSelectHandleRes = 0;
3737         if (mEditor != null) {
3738             mEditor.loadHandleDrawables(true /* overwrite */);
3739         }
3740     }
3741 
3742     /**
3743      * Sets the Drawable corresponding to the selection handle used for
3744      * positioning the cursor within text. The Drawable defaults to the value
3745      * of the textSelectHandle attribute.
3746      * Note that any change applied to the handle Drawable will not be visible
3747      * until the handle is hidden and then drawn again.
3748      *
3749      * @see #setTextSelectHandle(Drawable)
3750      * @attr ref android.R.styleable#TextView_textSelectHandle
3751      */
3752     @android.view.RemotableViewMethod
setTextSelectHandle(@rawableRes int textSelectHandle)3753     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3754         Preconditions.checkArgument(textSelectHandle != 0,
3755                 "The text select handle should be a valid drawable resource id.");
3756         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3757     }
3758 
3759     /**
3760      * Returns the Drawable corresponding to the selection handle used
3761      * for positioning the cursor within text.
3762      * Note that any change applied to the handle Drawable will not be visible
3763      * until the handle is hidden and then drawn again.
3764      *
3765      * @return the text select handle drawable
3766      *
3767      * @see #setTextSelectHandle(Drawable)
3768      * @see #setTextSelectHandle(int)
3769      * @attr ref android.R.styleable#TextView_textSelectHandle
3770      */
getTextSelectHandle()3771     @Nullable public Drawable getTextSelectHandle() {
3772         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3773             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3774         }
3775         return mTextSelectHandle;
3776     }
3777 
3778     /**
3779      * Sets the Drawable corresponding to the left handle used
3780      * for selecting text. The Drawable defaults to the value of the
3781      * textSelectHandleLeft attribute.
3782      * Note that any change applied to the handle Drawable will not be visible
3783      * until the handle is hidden and then drawn again.
3784      *
3785      * @see #setTextSelectHandleLeft(int)
3786      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3787      */
3788     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3789     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3790         Preconditions.checkNotNull(textSelectHandleLeft,
3791                 "The left text select handle should not be null.");
3792         mTextSelectHandleLeft = textSelectHandleLeft;
3793         mTextSelectHandleLeftRes = 0;
3794         if (mEditor != null) {
3795             mEditor.loadHandleDrawables(true /* overwrite */);
3796         }
3797     }
3798 
3799     /**
3800      * Sets the Drawable corresponding to the left handle used
3801      * for selecting text. The Drawable defaults to the value of the
3802      * textSelectHandleLeft attribute.
3803      * Note that any change applied to the handle Drawable will not be visible
3804      * until the handle is hidden and then drawn again.
3805      *
3806      * @see #setTextSelectHandleLeft(Drawable)
3807      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3808      */
3809     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3810     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
3811         Preconditions.checkArgument(textSelectHandleLeft != 0,
3812                 "The text select left handle should be a valid drawable resource id.");
3813         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
3814     }
3815 
3816     /**
3817      * Returns the Drawable corresponding to the left handle used
3818      * for selecting text.
3819      * Note that any change applied to the handle Drawable will not be visible
3820      * until the handle is hidden and then drawn again.
3821      *
3822      * @return the left text selection handle drawable
3823      *
3824      * @see #setTextSelectHandleLeft(Drawable)
3825      * @see #setTextSelectHandleLeft(int)
3826      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3827      */
getTextSelectHandleLeft()3828     @Nullable public Drawable getTextSelectHandleLeft() {
3829         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
3830             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
3831         }
3832         return mTextSelectHandleLeft;
3833     }
3834 
3835     /**
3836      * Sets the Drawable corresponding to the right handle used
3837      * for selecting text. The Drawable defaults to the value of the
3838      * textSelectHandleRight attribute.
3839      * Note that any change applied to the handle Drawable will not be visible
3840      * until the handle is hidden and then drawn again.
3841      *
3842      * @see #setTextSelectHandleRight(int)
3843      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3844      */
3845     @android.view.RemotableViewMethod
setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3846     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
3847         Preconditions.checkNotNull(textSelectHandleRight,
3848                 "The right text select handle should not be null.");
3849         mTextSelectHandleRight = textSelectHandleRight;
3850         mTextSelectHandleRightRes = 0;
3851         if (mEditor != null) {
3852             mEditor.loadHandleDrawables(true /* overwrite */);
3853         }
3854     }
3855 
3856     /**
3857      * Sets the Drawable corresponding to the right handle used
3858      * for selecting text. The Drawable defaults to the value of the
3859      * textSelectHandleRight attribute.
3860      * Note that any change applied to the handle Drawable will not be visible
3861      * until the handle is hidden and then drawn again.
3862      *
3863      * @see #setTextSelectHandleRight(Drawable)
3864      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3865      */
3866     @android.view.RemotableViewMethod
setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3867     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
3868         Preconditions.checkArgument(textSelectHandleRight != 0,
3869                 "The text select right handle should be a valid drawable resource id.");
3870         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
3871     }
3872 
3873     /**
3874      * Returns the Drawable corresponding to the right handle used
3875      * for selecting text.
3876      * Note that any change applied to the handle Drawable will not be visible
3877      * until the handle is hidden and then drawn again.
3878      *
3879      * @return the right text selection handle drawable
3880      *
3881      * @see #setTextSelectHandleRight(Drawable)
3882      * @see #setTextSelectHandleRight(int)
3883      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3884      */
getTextSelectHandleRight()3885     @Nullable public Drawable getTextSelectHandleRight() {
3886         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
3887             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
3888         }
3889         return mTextSelectHandleRight;
3890     }
3891 
3892     /**
3893      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3894      * value of the textCursorDrawable attribute.
3895      * Note that any change applied to the cursor Drawable will not be visible
3896      * until the cursor is hidden and then drawn again.
3897      *
3898      * @see #setTextCursorDrawable(int)
3899      * @attr ref android.R.styleable#TextView_textCursorDrawable
3900      */
setTextCursorDrawable(@ullable Drawable textCursorDrawable)3901     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
3902         mCursorDrawable = textCursorDrawable;
3903         mCursorDrawableRes = 0;
3904         if (mEditor != null) {
3905             mEditor.loadCursorDrawable();
3906         }
3907     }
3908 
3909     /**
3910      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3911      * value of the textCursorDrawable attribute.
3912      * Note that any change applied to the cursor Drawable will not be visible
3913      * until the cursor is hidden and then drawn again.
3914      *
3915      * @see #setTextCursorDrawable(Drawable)
3916      * @attr ref android.R.styleable#TextView_textCursorDrawable
3917      */
setTextCursorDrawable(@rawableRes int textCursorDrawable)3918     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
3919         setTextCursorDrawable(
3920                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
3921     }
3922 
3923     /**
3924      * Returns the Drawable corresponding to the text cursor.
3925      * Note that any change applied to the cursor Drawable will not be visible
3926      * until the cursor is hidden and then drawn again.
3927      *
3928      * @return the text cursor drawable
3929      *
3930      * @see #setTextCursorDrawable(Drawable)
3931      * @see #setTextCursorDrawable(int)
3932      * @attr ref android.R.styleable#TextView_textCursorDrawable
3933      */
getTextCursorDrawable()3934     @Nullable public Drawable getTextCursorDrawable() {
3935         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
3936             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
3937         }
3938         return mCursorDrawable;
3939     }
3940 
3941     /**
3942      * Sets the text appearance from the specified style resource.
3943      * <p>
3944      * Use a framework-defined {@code TextAppearance} style like
3945      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3946      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3947      * set of attributes that can be used in a custom style.
3948      *
3949      * @param resId the resource identifier of the style to apply
3950      * @attr ref android.R.styleable#TextView_textAppearance
3951      */
3952     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)3953     public void setTextAppearance(@StyleRes int resId) {
3954         setTextAppearance(mContext, resId);
3955     }
3956 
3957     /**
3958      * Sets the text color, size, style, hint color, and highlight color
3959      * from the specified TextAppearance resource.
3960      *
3961      * @deprecated Use {@link #setTextAppearance(int)} instead.
3962      */
3963     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)3964     public void setTextAppearance(Context context, @StyleRes int resId) {
3965         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3966         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
3967         readTextAppearance(context, ta, attributes, false /* styleArray */);
3968         ta.recycle();
3969         applyTextAppearance(attributes);
3970     }
3971 
3972     /**
3973      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
3974      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
3975      */
3976     private static class TextAppearanceAttributes {
3977         int mTextColorHighlight = 0;
3978         ColorStateList mTextColor = null;
3979         ColorStateList mTextColorHint = null;
3980         ColorStateList mTextColorLink = null;
3981         int mTextSize = -1;
3982         int mTextSizeUnit = -1;
3983         LocaleList mTextLocales = null;
3984         String mFontFamily = null;
3985         Typeface mFontTypeface = null;
3986         boolean mFontFamilyExplicit = false;
3987         int mTypefaceIndex = -1;
3988         int mTextStyle = 0;
3989         int mFontWeight = -1;
3990         boolean mAllCaps = false;
3991         int mShadowColor = 0;
3992         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
3993         boolean mHasElegant = false;
3994         boolean mElegant = false;
3995         boolean mHasFallbackLineSpacing = false;
3996         boolean mFallbackLineSpacing = false;
3997         boolean mHasLetterSpacing = false;
3998         float mLetterSpacing = 0;
3999         String mFontFeatureSettings = null;
4000         String mFontVariationSettings = null;
4001         boolean mHasLineBreakStyle = false;
4002         boolean mHasLineBreakWordStyle = false;
4003         int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
4004         int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
4005 
4006         @Override
toString()4007         public String toString() {
4008             return "TextAppearanceAttributes {\n"
4009                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
4010                     + "    mTextColor:" + mTextColor + "\n"
4011                     + "    mTextColorHint:" + mTextColorHint + "\n"
4012                     + "    mTextColorLink:" + mTextColorLink + "\n"
4013                     + "    mTextSize:" + mTextSize + "\n"
4014                     + "    mTextSizeUnit:" + mTextSizeUnit + "\n"
4015                     + "    mTextLocales:" + mTextLocales + "\n"
4016                     + "    mFontFamily:" + mFontFamily + "\n"
4017                     + "    mFontTypeface:" + mFontTypeface + "\n"
4018                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
4019                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
4020                     + "    mTextStyle:" + mTextStyle + "\n"
4021                     + "    mFontWeight:" + mFontWeight + "\n"
4022                     + "    mAllCaps:" + mAllCaps + "\n"
4023                     + "    mShadowColor:" + mShadowColor + "\n"
4024                     + "    mShadowDx:" + mShadowDx + "\n"
4025                     + "    mShadowDy:" + mShadowDy + "\n"
4026                     + "    mShadowRadius:" + mShadowRadius + "\n"
4027                     + "    mHasElegant:" + mHasElegant + "\n"
4028                     + "    mElegant:" + mElegant + "\n"
4029                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
4030                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
4031                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
4032                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
4033                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
4034                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
4035                     + "    mHasLineBreakStyle:" + mHasLineBreakStyle + "\n"
4036                     + "    mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n"
4037                     + "    mLineBreakStyle:" + mLineBreakStyle + "\n"
4038                     + "    mLineBreakWordStyle:" + mLineBreakWordStyle + "\n"
4039                     + "}";
4040         }
4041     }
4042 
4043     // Maps styleable attributes that exist both in TextView style and TextAppearance.
4044     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
4045     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)4046         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
4047                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)4048         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
4049                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)4050         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
4051                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)4052         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
4053                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)4054         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
4055                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)4056         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
4057                 com.android.internal.R.styleable.TextAppearance_textLocale);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)4058         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
4059                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)4060         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
4061                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)4062         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
4063                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4064         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
4065                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4066         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
4067                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4068         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
4069                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4070         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
4071                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4072         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
4073                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4074         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
4075                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4076         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
4077                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4078         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
4079                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4080         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
4081                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4082         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
4083                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4084         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
4085                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, com.android.internal.R.styleable.TextAppearance_lineBreakStyle)4086         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle,
4087                 com.android.internal.R.styleable.TextAppearance_lineBreakStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle)4088         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle,
4089                 com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle);
4090     }
4091 
4092     /**
4093      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
4094      * set. If the TypedArray contains a value that was already set in the given attributes, that
4095      * will be overridden.
4096      *
4097      * @param context The Context to be used
4098      * @param appearance The TypedArray to read properties from
4099      * @param attributes the TextAppearanceAttributes to fill in
4100      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
4101      *                   what attribute indexes will be used to read the properties.
4102      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)4103     private void readTextAppearance(Context context, TypedArray appearance,
4104             TextAppearanceAttributes attributes, boolean styleArray) {
4105         final int n = appearance.getIndexCount();
4106         for (int i = 0; i < n; i++) {
4107             final int attr = appearance.getIndex(i);
4108             int index = attr;
4109             // Translate style array index ids to TextAppearance ids.
4110             if (styleArray) {
4111                 index = sAppearanceValues.get(attr, -1);
4112                 if (index == -1) {
4113                     // This value is not part of a Text Appearance and should be ignored.
4114                     continue;
4115                 }
4116             }
4117             switch (index) {
4118                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
4119                     attributes.mTextColorHighlight =
4120                             appearance.getColor(attr, attributes.mTextColorHighlight);
4121                     break;
4122                 case com.android.internal.R.styleable.TextAppearance_textColor:
4123                     attributes.mTextColor = appearance.getColorStateList(attr);
4124                     break;
4125                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
4126                     attributes.mTextColorHint = appearance.getColorStateList(attr);
4127                     break;
4128                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
4129                     attributes.mTextColorLink = appearance.getColorStateList(attr);
4130                     break;
4131                 case com.android.internal.R.styleable.TextAppearance_textSize:
4132                     attributes.mTextSize =
4133                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
4134                     attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit();
4135                     break;
4136                 case com.android.internal.R.styleable.TextAppearance_textLocale:
4137                     final String localeString = appearance.getString(attr);
4138                     if (localeString != null) {
4139                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
4140                         if (!localeList.isEmpty()) {
4141                             attributes.mTextLocales = localeList;
4142                         }
4143                     }
4144                     break;
4145                 case com.android.internal.R.styleable.TextAppearance_typeface:
4146                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
4147                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4148                         attributes.mFontFamily = null;
4149                     }
4150                     break;
4151                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
4152                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
4153                         try {
4154                             attributes.mFontTypeface = appearance.getFont(attr);
4155                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
4156                             // Expected if it is not a font resource.
4157                         }
4158                     }
4159                     if (attributes.mFontTypeface == null) {
4160                         attributes.mFontFamily = appearance.getString(attr);
4161                     }
4162                     attributes.mFontFamilyExplicit = true;
4163                     break;
4164                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4165                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4166                     break;
4167                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4168                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4169                     break;
4170                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4171                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4172                     break;
4173                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4174                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4175                     break;
4176                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4177                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4178                     break;
4179                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4180                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4181                     break;
4182                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4183                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4184                     break;
4185                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4186                     attributes.mHasElegant = true;
4187                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4188                     break;
4189                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4190                     attributes.mHasFallbackLineSpacing = true;
4191                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4192                             attributes.mFallbackLineSpacing);
4193                     break;
4194                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4195                     attributes.mHasLetterSpacing = true;
4196                     attributes.mLetterSpacing =
4197                             appearance.getFloat(attr, attributes.mLetterSpacing);
4198                     break;
4199                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4200                     attributes.mFontFeatureSettings = appearance.getString(attr);
4201                     break;
4202                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4203                     attributes.mFontVariationSettings = appearance.getString(attr);
4204                     break;
4205                 case com.android.internal.R.styleable.TextAppearance_lineBreakStyle:
4206                     attributes.mHasLineBreakStyle = true;
4207                     attributes.mLineBreakStyle =
4208                             appearance.getInt(attr, attributes.mLineBreakStyle);
4209                     break;
4210                 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
4211                     attributes.mHasLineBreakWordStyle = true;
4212                     attributes.mLineBreakWordStyle =
4213                             appearance.getInt(attr, attributes.mLineBreakWordStyle);
4214                     break;
4215                 default:
4216             }
4217         }
4218     }
4219 
applyTextAppearance(TextAppearanceAttributes attributes)4220     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4221         if (attributes.mTextColor != null) {
4222             setTextColor(attributes.mTextColor);
4223         }
4224 
4225         if (attributes.mTextColorHint != null) {
4226             setHintTextColor(attributes.mTextColorHint);
4227         }
4228 
4229         if (attributes.mTextColorLink != null) {
4230             setLinkTextColor(attributes.mTextColorLink);
4231         }
4232 
4233         if (attributes.mTextColorHighlight != 0) {
4234             setHighlightColor(attributes.mTextColorHighlight);
4235         }
4236 
4237         if (attributes.mTextSize != -1) {
4238             mTextSizeUnit = attributes.mTextSizeUnit;
4239             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4240         }
4241 
4242         if (attributes.mTextLocales != null) {
4243             setTextLocales(attributes.mTextLocales);
4244         }
4245 
4246         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4247             attributes.mFontFamily = null;
4248         }
4249         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4250                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4251 
4252         if (attributes.mShadowColor != 0) {
4253             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4254                     attributes.mShadowColor);
4255         }
4256 
4257         if (attributes.mAllCaps) {
4258             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4259         }
4260 
4261         if (attributes.mHasElegant) {
4262             setElegantTextHeight(attributes.mElegant);
4263         }
4264 
4265         if (attributes.mHasFallbackLineSpacing) {
4266             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4267         }
4268 
4269         if (attributes.mHasLetterSpacing) {
4270             setLetterSpacing(attributes.mLetterSpacing);
4271         }
4272 
4273         if (attributes.mFontFeatureSettings != null) {
4274             setFontFeatureSettings(attributes.mFontFeatureSettings);
4275         }
4276 
4277         if (attributes.mFontVariationSettings != null) {
4278             setFontVariationSettings(attributes.mFontVariationSettings);
4279         }
4280 
4281         if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) {
4282             updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle,
4283                     attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle,
4284                     attributes.mLineBreakWordStyle);
4285         }
4286     }
4287 
4288     /**
4289      * Updates the LineBreakConfig from the TextAppearance.
4290      *
4291      * This method updates the given line configuration from the TextAppearance. This method will
4292      * request new layout if line break config has been changed.
4293      *
4294      * @param isLineBreakStyleSpecified true if the line break style is specified.
4295      * @param isLineBreakWordStyleSpecified true if the line break word style is specified.
4296      * @param lineBreakStyle the value of the line break style in the TextAppearance.
4297      * @param lineBreakWordStyle the value of the line break word style in the TextAppearance.
4298      */
updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, boolean isLineBreakWordStyleSpecified, @LineBreakConfig.LineBreakStyle int lineBreakStyle, @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)4299     private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified,
4300             boolean isLineBreakWordStyleSpecified,
4301             @LineBreakConfig.LineBreakStyle int lineBreakStyle,
4302             @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
4303         boolean updated = false;
4304         if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) {
4305             mLineBreakStyle = lineBreakStyle;
4306             updated = true;
4307         }
4308         if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) {
4309             mLineBreakWordStyle = lineBreakWordStyle;
4310             updated = true;
4311         }
4312         if (updated && mLayout != null) {
4313             nullLayouts();
4314             requestLayout();
4315             invalidate();
4316         }
4317     }
4318     /**
4319      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4320      * the first member of {@link #getTextLocales()}.
4321      * @return the default primary {@link Locale} of the text in this TextView.
4322      */
4323     @NonNull
getTextLocale()4324     public Locale getTextLocale() {
4325         return mTextPaint.getTextLocale();
4326     }
4327 
4328     /**
4329      * Get the default {@link LocaleList} of the text in this TextView.
4330      * @return the default {@link LocaleList} of the text in this TextView.
4331      */
4332     @NonNull @Size(min = 1)
getTextLocales()4333     public LocaleList getTextLocales() {
4334         return mTextPaint.getTextLocales();
4335     }
4336 
changeListenerLocaleTo(@ullable Locale locale)4337     private void changeListenerLocaleTo(@Nullable Locale locale) {
4338         if (mListenerChanged) {
4339             // If a listener has been explicitly set, don't change it. We may break something.
4340             return;
4341         }
4342         // The following null check is not absolutely necessary since all calling points of
4343         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4344         // here in case others would want to call this method in the future.
4345         if (mEditor != null) {
4346             KeyListener listener = mEditor.mKeyListener;
4347             if (listener instanceof DigitsKeyListener) {
4348                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4349             } else if (listener instanceof DateKeyListener) {
4350                 listener = DateKeyListener.getInstance(locale);
4351             } else if (listener instanceof TimeKeyListener) {
4352                 listener = TimeKeyListener.getInstance(locale);
4353             } else if (listener instanceof DateTimeKeyListener) {
4354                 listener = DateTimeKeyListener.getInstance(locale);
4355             } else {
4356                 return;
4357             }
4358             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4359             setKeyListenerOnly(listener);
4360             setInputTypeFromEditor();
4361             if (wasPasswordType) {
4362                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4363                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4364                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4365                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4366                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4367                 }
4368             }
4369         }
4370     }
4371 
4372     /**
4373      * Set the default {@link Locale} of the text in this TextView to a one-member
4374      * {@link LocaleList} containing just the given Locale.
4375      *
4376      * @param locale the {@link Locale} for drawing text, must not be null.
4377      *
4378      * @see #setTextLocales
4379      */
setTextLocale(@onNull Locale locale)4380     public void setTextLocale(@NonNull Locale locale) {
4381         mLocalesChanged = true;
4382         mTextPaint.setTextLocale(locale);
4383         if (mLayout != null) {
4384             nullLayouts();
4385             requestLayout();
4386             invalidate();
4387         }
4388     }
4389 
4390     /**
4391      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4392      *
4393      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4394      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4395      * other aspects of text display, including line breaking.
4396      *
4397      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4398      *
4399      * @see Paint#setTextLocales
4400      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)4401     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4402         mLocalesChanged = true;
4403         mTextPaint.setTextLocales(locales);
4404         if (mLayout != null) {
4405             nullLayouts();
4406             requestLayout();
4407             invalidate();
4408         }
4409     }
4410 
4411     @Override
onConfigurationChanged(Configuration newConfig)4412     protected void onConfigurationChanged(Configuration newConfig) {
4413         super.onConfigurationChanged(newConfig);
4414         if (!mLocalesChanged) {
4415             mTextPaint.setTextLocales(LocaleList.getDefault());
4416             if (mLayout != null) {
4417                 nullLayouts();
4418                 requestLayout();
4419                 invalidate();
4420             }
4421         }
4422         if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) {
4423             mFontWeightAdjustment = newConfig.fontWeightAdjustment;
4424             setTypeface(getTypeface());
4425         }
4426     }
4427 
4428     /**
4429      * @return the size (in pixels) of the default text size in this TextView.
4430      */
4431     @InspectableProperty
4432     @ViewDebug.ExportedProperty(category = "text")
getTextSize()4433     public float getTextSize() {
4434         return mTextPaint.getTextSize();
4435     }
4436 
4437     /**
4438      * @return the size (in scaled pixels) of the default text size in this TextView.
4439      * @hide
4440      */
4441     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()4442     public float getScaledTextSize() {
4443         return mTextPaint.getTextSize() / mTextPaint.density;
4444     }
4445 
4446     /** @hide */
4447     @ViewDebug.ExportedProperty(category = "text", mapping = {
4448             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4449             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4450             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4451             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4452     })
getTypefaceStyle()4453     public int getTypefaceStyle() {
4454         Typeface typeface = mTextPaint.getTypeface();
4455         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4456     }
4457 
4458     /**
4459      * Set the default text size to the given value, interpreted as "scaled
4460      * pixel" units.  This size is adjusted based on the current density and
4461      * user font size preference.
4462      *
4463      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4464      *
4465      * @param size The scaled pixel size.
4466      *
4467      * @attr ref android.R.styleable#TextView_textSize
4468      */
4469     @android.view.RemotableViewMethod
setTextSize(float size)4470     public void setTextSize(float size) {
4471         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4472     }
4473 
4474     /**
4475      * Set the default text size to a given unit and value. See {@link
4476      * TypedValue} for the possible dimension units.
4477      *
4478      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4479      *
4480      * @param unit The desired dimension unit.
4481      * @param size The desired size in the given units.
4482      *
4483      * @attr ref android.R.styleable#TextView_textSize
4484      */
setTextSize(int unit, float size)4485     public void setTextSize(int unit, float size) {
4486         if (!isAutoSizeEnabled()) {
4487             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4488         }
4489     }
4490 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4491     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4492         Context c = getContext();
4493         Resources r;
4494 
4495         if (c == null) {
4496             r = Resources.getSystem();
4497         } else {
4498             r = c.getResources();
4499         }
4500 
4501         mTextSizeUnit = unit;
4502         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
4503                 shouldRequestLayout);
4504     }
4505 
4506     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setRawTextSize(float size, boolean shouldRequestLayout)4507     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4508         if (size != mTextPaint.getTextSize()) {
4509             mTextPaint.setTextSize(size);
4510 
4511             if (shouldRequestLayout && mLayout != null) {
4512                 // Do not auto-size right after setting the text size.
4513                 mNeedsAutoSizeText = false;
4514                 nullLayouts();
4515                 requestLayout();
4516                 invalidate();
4517             }
4518         }
4519     }
4520 
4521     /**
4522      * Gets the text size unit defined by the developer. It may be specified in resources or be
4523      * passed as the unit argument of {@link #setTextSize(int, float)} at runtime.
4524      *
4525      * @return the dimension type of the text size unit originally defined.
4526      * @see TypedValue#TYPE_DIMENSION
4527      */
getTextSizeUnit()4528     public int getTextSizeUnit() {
4529         return mTextSizeUnit;
4530     }
4531 
4532     /**
4533      * Gets the extent by which text should be stretched horizontally.
4534      * This will usually be 1.0.
4535      * @return The horizontal scale factor.
4536      */
4537     @InspectableProperty
getTextScaleX()4538     public float getTextScaleX() {
4539         return mTextPaint.getTextScaleX();
4540     }
4541 
4542     /**
4543      * Sets the horizontal scale factor for text. The default value
4544      * is 1.0. Values greater than 1.0 stretch the text wider.
4545      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4546      * @param size The horizontal scale factor.
4547      * @attr ref android.R.styleable#TextView_textScaleX
4548      */
4549     @android.view.RemotableViewMethod
setTextScaleX(float size)4550     public void setTextScaleX(float size) {
4551         if (size != mTextPaint.getTextScaleX()) {
4552             mUserSetTextScaleX = true;
4553             mTextPaint.setTextScaleX(size);
4554 
4555             if (mLayout != null) {
4556                 nullLayouts();
4557                 requestLayout();
4558                 invalidate();
4559             }
4560         }
4561     }
4562 
4563     /**
4564      * Sets the typeface and style in which the text should be displayed.
4565      * Note that not all Typeface families actually have bold and italic
4566      * variants, so you may need to use
4567      * {@link #setTypeface(Typeface, int)} to get the appearance
4568      * that you actually want.
4569      *
4570      * @see #getTypeface()
4571      *
4572      * @attr ref android.R.styleable#TextView_fontFamily
4573      * @attr ref android.R.styleable#TextView_typeface
4574      * @attr ref android.R.styleable#TextView_textStyle
4575      */
setTypeface(@ullable Typeface tf)4576     public void setTypeface(@Nullable Typeface tf) {
4577         mOriginalTypeface = tf;
4578         if (mFontWeightAdjustment != 0
4579                 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
4580             if (tf == null) {
4581                 tf = Typeface.DEFAULT;
4582             } else {
4583                 int newWeight = Math.min(
4584                         Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
4585                         FontStyle.FONT_WEIGHT_MAX);
4586                 int typefaceStyle = tf != null ? tf.getStyle() : 0;
4587                 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0;
4588                 tf = Typeface.create(tf, newWeight, italic);
4589             }
4590         }
4591         if (mTextPaint.getTypeface() != tf) {
4592             mTextPaint.setTypeface(tf);
4593 
4594             if (mLayout != null) {
4595                 nullLayouts();
4596                 requestLayout();
4597                 invalidate();
4598             }
4599         }
4600     }
4601 
4602     /**
4603      * Gets the current {@link Typeface} that is used to style the text.
4604      * @return The current Typeface.
4605      *
4606      * @see #setTypeface(Typeface)
4607      *
4608      * @attr ref android.R.styleable#TextView_fontFamily
4609      * @attr ref android.R.styleable#TextView_typeface
4610      * @attr ref android.R.styleable#TextView_textStyle
4611      */
4612     @InspectableProperty
getTypeface()4613     public Typeface getTypeface() {
4614         return mOriginalTypeface;
4615     }
4616 
4617     /**
4618      * Set the TextView's elegant height metrics flag. This setting selects font
4619      * variants that have not been compacted to fit Latin-based vertical
4620      * metrics, and also increases top and bottom bounds to provide more space.
4621      *
4622      * @param elegant set the paint's elegant metrics flag.
4623      *
4624      * @see #isElegantTextHeight()
4625      * @see Paint#isElegantTextHeight()
4626      *
4627      * @attr ref android.R.styleable#TextView_elegantTextHeight
4628      */
setElegantTextHeight(boolean elegant)4629     public void setElegantTextHeight(boolean elegant) {
4630         if (elegant != mTextPaint.isElegantTextHeight()) {
4631             mTextPaint.setElegantTextHeight(elegant);
4632             if (mLayout != null) {
4633                 nullLayouts();
4634                 requestLayout();
4635                 invalidate();
4636             }
4637         }
4638     }
4639 
4640     /**
4641      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4642      * displaying the text (which is needed to avoid text from consecutive lines running into
4643      * each other). If set, fallback fonts that end up getting used can increase the ascent
4644      * and descent of the lines that they are used on.
4645      * <p/>
4646      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4647      * is typically much taller or deeper than Latin text.
4648      *
4649      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4650      *
4651      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4652      *
4653      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4654      */
setFallbackLineSpacing(boolean enabled)4655     public void setFallbackLineSpacing(boolean enabled) {
4656         int fallbackStrategy;
4657         if (enabled) {
4658             if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
4659                 fallbackStrategy = FALLBACK_LINE_SPACING_ALL;
4660             } else {
4661                 fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
4662             }
4663         } else {
4664             fallbackStrategy = FALLBACK_LINE_SPACING_NONE;
4665         }
4666         if (mUseFallbackLineSpacing != fallbackStrategy) {
4667             mUseFallbackLineSpacing = fallbackStrategy;
4668             if (mLayout != null) {
4669                 nullLayouts();
4670                 requestLayout();
4671                 invalidate();
4672             }
4673         }
4674     }
4675 
4676     /**
4677      * @return whether fallback line spacing is enabled, {@code true} by default
4678      *
4679      * @see #setFallbackLineSpacing(boolean)
4680      *
4681      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4682      */
4683     @InspectableProperty
isFallbackLineSpacing()4684     public boolean isFallbackLineSpacing() {
4685         return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE;
4686     }
4687 
isFallbackLineSpacingForBoringLayout()4688     private boolean isFallbackLineSpacingForBoringLayout() {
4689         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL;
4690     }
4691 
4692     // Package privte for accessing from Editor.java
isFallbackLineSpacingForStaticLayout()4693     /* package */ boolean isFallbackLineSpacingForStaticLayout() {
4694         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL
4695                 || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
4696     }
4697 
4698     /**
4699      * Get the value of the TextView's elegant height metrics flag. This setting selects font
4700      * variants that have not been compacted to fit Latin-based vertical
4701      * metrics, and also increases top and bottom bounds to provide more space.
4702      * @return {@code true} if the elegant height metrics flag is set.
4703      *
4704      * @see #setElegantTextHeight(boolean)
4705      * @see Paint#setElegantTextHeight(boolean)
4706      */
4707     @InspectableProperty
isElegantTextHeight()4708     public boolean isElegantTextHeight() {
4709         return mTextPaint.isElegantTextHeight();
4710     }
4711 
4712     /**
4713      * Gets the text letter-space value, which determines the spacing between characters.
4714      * The value returned is in ems. Normally, this value is 0.0.
4715      * @return The text letter-space value in ems.
4716      *
4717      * @see #setLetterSpacing(float)
4718      * @see Paint#setLetterSpacing
4719      */
4720     @InspectableProperty
getLetterSpacing()4721     public float getLetterSpacing() {
4722         return mTextPaint.getLetterSpacing();
4723     }
4724 
4725     /**
4726      * Sets text letter-spacing in em units.  Typical values
4727      * for slight expansion will be around 0.05.  Negative values tighten text.
4728      *
4729      * @see #getLetterSpacing()
4730      * @see Paint#getLetterSpacing
4731      *
4732      * @param letterSpacing A text letter-space value in ems.
4733      * @attr ref android.R.styleable#TextView_letterSpacing
4734      */
4735     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)4736     public void setLetterSpacing(float letterSpacing) {
4737         if (letterSpacing != mTextPaint.getLetterSpacing()) {
4738             mTextPaint.setLetterSpacing(letterSpacing);
4739 
4740             if (mLayout != null) {
4741                 nullLayouts();
4742                 requestLayout();
4743                 invalidate();
4744             }
4745         }
4746     }
4747 
4748     /**
4749      * Returns the font feature settings. The format is the same as the CSS
4750      * font-feature-settings attribute:
4751      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4752      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4753      *
4754      * @return the currently set font feature settings.  Default is null.
4755      *
4756      * @see #setFontFeatureSettings(String)
4757      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
4758      */
4759     @InspectableProperty
4760     @Nullable
getFontFeatureSettings()4761     public String getFontFeatureSettings() {
4762         return mTextPaint.getFontFeatureSettings();
4763     }
4764 
4765     /**
4766      * Returns the font variation settings.
4767      *
4768      * @return the currently set font variation settings.  Returns null if no variation is
4769      * specified.
4770      *
4771      * @see #setFontVariationSettings(String)
4772      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
4773      */
4774     @Nullable
getFontVariationSettings()4775     public String getFontVariationSettings() {
4776         return mTextPaint.getFontVariationSettings();
4777     }
4778 
4779     /**
4780      * Sets the break strategy for breaking paragraphs into lines. The default value for
4781      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
4782      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
4783      * text "dancing" when being edited.
4784      * <p>
4785      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4786      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4787      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4788      * improves the structure of text layout however has performance impact and requires more time
4789      * to do the text layout.</p>
4790      * <p>
4791      * Compared with {@link #setLineBreakStyle(int)}, line break style with different strictness is
4792      * evaluated in the ICU to identify the potential breakpoints. In
4793      * {@link #setBreakStrategy(int)}, line break strategy handles the post processing of ICU's line
4794      * break result. It aims to evaluate ICU's breakpoints and break the lines based on the
4795      * constraint.
4796      * </p>
4797      *
4798      * @attr ref android.R.styleable#TextView_breakStrategy
4799      * @see #getBreakStrategy()
4800      * @see #setHyphenationFrequency(int)
4801      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4802     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
4803         mBreakStrategy = breakStrategy;
4804         if (mLayout != null) {
4805             nullLayouts();
4806             requestLayout();
4807             invalidate();
4808         }
4809     }
4810 
4811     /**
4812      * Gets the current strategy for breaking paragraphs into lines.
4813      * @return the current strategy for breaking paragraphs into lines.
4814      *
4815      * @attr ref android.R.styleable#TextView_breakStrategy
4816      * @see #setBreakStrategy(int)
4817      */
4818     @InspectableProperty(enumMapping = {
4819             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
4820             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
4821             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
4822     })
4823     @Layout.BreakStrategy
getBreakStrategy()4824     public int getBreakStrategy() {
4825         return mBreakStrategy;
4826     }
4827 
4828     /**
4829      * Sets the frequency of automatic hyphenation to use when determining word breaks.
4830      * The default value for both TextView and {@link EditText} is
4831      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
4832      * is set from the theme.
4833      * <p/>
4834      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4835      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4836      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4837      * improves the structure of text layout however has performance impact and requires more time
4838      * to do the text layout.
4839      * <p/>
4840      * Note: Before Android Q, in the theme hyphenation frequency is set to
4841      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
4842      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
4843      *
4844      * @param hyphenationFrequency the hyphenation frequency to use, one of
4845      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
4846      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
4847      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
4848      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4849      * @see #getHyphenationFrequency()
4850      * @see #getBreakStrategy()
4851      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4852     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
4853         mHyphenationFrequency = hyphenationFrequency;
4854         if (mLayout != null) {
4855             nullLayouts();
4856             requestLayout();
4857             invalidate();
4858         }
4859     }
4860 
4861     /**
4862      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
4863      * @return the current frequency of automatic hyphenation to be used when determining word
4864      * breaks.
4865      *
4866      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4867      * @see #setHyphenationFrequency(int)
4868      */
4869     @InspectableProperty(enumMapping = {
4870             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
4871             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
4872             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
4873     })
4874     @Layout.HyphenationFrequency
getHyphenationFrequency()4875     public int getHyphenationFrequency() {
4876         return mHyphenationFrequency;
4877     }
4878 
4879     /**
4880      * Sets the line-break style for text wrapping.
4881      *
4882      * <p>Line-break style specifies the line-break strategies that can be used
4883      * for text wrapping. The line-break style affects rule-based line breaking
4884      * by specifying the strictness of line-breaking rules.
4885      *
4886      * <p>The following are types of line-break styles:
4887      * <ul>
4888      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}
4889      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}
4890      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}
4891      * </ul>
4892      *
4893      * <p>The default line-break style is
4894      * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
4895      * line-breaking rules are used.
4896      *
4897      * <p>See the
4898      * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
4899      * line-break property</a> for more information.
4900      *
4901      * @param lineBreakStyle The line-break style for the text.
4902      */
setLineBreakStyle(@ineBreakConfig.LineBreakStyle int lineBreakStyle)4903     public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
4904         if (mLineBreakStyle != lineBreakStyle) {
4905             mLineBreakStyle = lineBreakStyle;
4906             if (mLayout != null) {
4907                 nullLayouts();
4908                 requestLayout();
4909                 invalidate();
4910             }
4911         }
4912     }
4913 
4914     /**
4915      * Sets the line-break word style for text wrapping.
4916      *
4917      * <p>The line-break word style affects dictionary-based line breaking by
4918      * providing phrase-based line-breaking opportunities. Use
4919      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
4920      * phrase-based line breaking.
4921      *
4922      * <p>The default line-break word style is
4923      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
4924      * no line-breaking word style is used.
4925      *
4926      * <p>See the
4927      * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
4928      * word-break property</a> for more information.
4929      *
4930      * @param lineBreakWordStyle The line-break word style for the text.
4931      */
setLineBreakWordStyle(@ineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)4932     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
4933         if (mLineBreakWordStyle != lineBreakWordStyle) {
4934             mLineBreakWordStyle = lineBreakWordStyle;
4935             if (mLayout != null) {
4936                 nullLayouts();
4937                 requestLayout();
4938                 invalidate();
4939             }
4940         }
4941     }
4942 
4943     /**
4944      * Gets the current line-break style for text wrapping.
4945      *
4946      * @return The line-break style to be used for text wrapping.
4947      */
getLineBreakStyle()4948     public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
4949         return mLineBreakStyle;
4950     }
4951 
4952     /**
4953      * Gets the current line-break word style for text wrapping.
4954      *
4955      * @return The line-break word style to be used for text wrapping.
4956      */
getLineBreakWordStyle()4957     public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
4958         return mLineBreakWordStyle;
4959     }
4960 
4961     /**
4962      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
4963      *
4964      * @return a current {@link PrecomputedText.Params}
4965      * @see PrecomputedText
4966      */
getTextMetricsParams()4967     public @NonNull PrecomputedText.Params getTextMetricsParams() {
4968         return new PrecomputedText.Params(new TextPaint(mTextPaint),
4969                 LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
4970                 getTextDirectionHeuristic(),
4971                 mBreakStrategy, mHyphenationFrequency);
4972     }
4973 
4974     /**
4975      * Apply the text layout parameter.
4976      *
4977      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
4978      * @see PrecomputedText
4979      */
setTextMetricsParams(@onNull PrecomputedText.Params params)4980     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
4981         mTextPaint.set(params.getTextPaint());
4982         mUserSetTextScaleX = true;
4983         mTextDir = params.getTextDirection();
4984         mBreakStrategy = params.getBreakStrategy();
4985         mHyphenationFrequency = params.getHyphenationFrequency();
4986         LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
4987         mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
4988         mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
4989         if (mLayout != null) {
4990             nullLayouts();
4991             requestLayout();
4992             invalidate();
4993         }
4994     }
4995 
4996     /**
4997      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
4998      * last line is too short for justification, the last line will be displayed with the
4999      * alignment set by {@link android.view.View#setTextAlignment}.
5000      *
5001      * @see #getJustificationMode()
5002      */
5003     @Layout.JustificationMode
5004     @android.view.RemotableViewMethod
setJustificationMode(@ayout.JustificationMode int justificationMode)5005     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
5006         mJustificationMode = justificationMode;
5007         if (mLayout != null) {
5008             nullLayouts();
5009             requestLayout();
5010             invalidate();
5011         }
5012     }
5013 
5014     /**
5015      * @return true if currently paragraph justification mode.
5016      *
5017      * @see #setJustificationMode(int)
5018      */
5019     @InspectableProperty(enumMapping = {
5020             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
5021             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
5022     })
getJustificationMode()5023     public @Layout.JustificationMode int getJustificationMode() {
5024         return mJustificationMode;
5025     }
5026 
5027     /**
5028      * Sets font feature settings. The format is the same as the CSS
5029      * font-feature-settings attribute:
5030      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
5031      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
5032      *
5033      * @param fontFeatureSettings font feature settings represented as CSS compatible string
5034      *
5035      * @see #getFontFeatureSettings()
5036      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
5037      *
5038      * @attr ref android.R.styleable#TextView_fontFeatureSettings
5039      */
5040     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)5041     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
5042         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
5043             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
5044 
5045             if (mLayout != null) {
5046                 nullLayouts();
5047                 requestLayout();
5048                 invalidate();
5049             }
5050         }
5051     }
5052 
5053 
5054     /**
5055      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
5056      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
5057      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
5058      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
5059      * are invalid. If a specified axis name is not defined in the font, the settings will be
5060      * ignored.
5061      *
5062      * <p>
5063      * Examples,
5064      * <ul>
5065      * <li>Set font width to 150.
5066      * <pre>
5067      * <code>
5068      *   TextView textView = (TextView) findViewById(R.id.textView);
5069      *   textView.setFontVariationSettings("'wdth' 150");
5070      * </code>
5071      * </pre>
5072      * </li>
5073      *
5074      * <li>Set the font slant to 20 degrees and ask for italic style.
5075      * <pre>
5076      * <code>
5077      *   TextView textView = (TextView) findViewById(R.id.textView);
5078      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
5079      * </code>
5080      * </pre>
5081      * </p>
5082      * </li>
5083      * </ul>
5084      *
5085      * @param fontVariationSettings font variation settings. You can pass null or empty string as
5086      *                              no variation settings.
5087      * @return true if the given settings is effective to at least one font file underlying this
5088      *         TextView. This function also returns true for empty settings string. Otherwise
5089      *         returns false.
5090      *
5091      * @throws IllegalArgumentException If given string is not a valid font variation settings
5092      *                                  format.
5093      *
5094      * @see #getFontVariationSettings()
5095      * @see FontVariationAxis
5096      *
5097      * @attr ref android.R.styleable#TextView_fontVariationSettings
5098      */
setFontVariationSettings(@ullable String fontVariationSettings)5099     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
5100         final String existingSettings = mTextPaint.getFontVariationSettings();
5101         if (fontVariationSettings == existingSettings
5102                 || (fontVariationSettings != null
5103                         && fontVariationSettings.equals(existingSettings))) {
5104             return true;
5105         }
5106         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
5107 
5108         if (effective && mLayout != null) {
5109             nullLayouts();
5110             requestLayout();
5111             invalidate();
5112         }
5113         return effective;
5114     }
5115 
5116     /**
5117      * Sets the text color for all the states (normal, selected,
5118      * focused) to be this color.
5119      *
5120      * @param color A color value in the form 0xAARRGGBB.
5121      * Do not pass a resource ID. To get a color value from a resource ID, call
5122      * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}.
5123      *
5124      * @see #setTextColor(ColorStateList)
5125      * @see #getTextColors()
5126      *
5127      * @attr ref android.R.styleable#TextView_textColor
5128      */
5129     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)5130     public void setTextColor(@ColorInt int color) {
5131         mTextColor = ColorStateList.valueOf(color);
5132         updateTextColors();
5133     }
5134 
5135     /**
5136      * Sets the text color.
5137      *
5138      * @see #setTextColor(int)
5139      * @see #getTextColors()
5140      * @see #setHintTextColor(ColorStateList)
5141      * @see #setLinkTextColor(ColorStateList)
5142      *
5143      * @attr ref android.R.styleable#TextView_textColor
5144      */
5145     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)5146     public void setTextColor(ColorStateList colors) {
5147         if (colors == null) {
5148             throw new NullPointerException();
5149         }
5150 
5151         mTextColor = colors;
5152         updateTextColors();
5153     }
5154 
5155     /**
5156      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
5157      *
5158      * @see #setTextColor(ColorStateList)
5159      * @see #setTextColor(int)
5160      *
5161      * @attr ref android.R.styleable#TextView_textColor
5162      */
5163     @InspectableProperty(name = "textColor")
getTextColors()5164     public final ColorStateList getTextColors() {
5165         return mTextColor;
5166     }
5167 
5168     /**
5169      * Return the current color selected for normal text.
5170      *
5171      * @return Returns the current text color.
5172      */
5173     @ColorInt
getCurrentTextColor()5174     public final int getCurrentTextColor() {
5175         return mCurTextColor;
5176     }
5177 
5178     /**
5179      * Sets the color used to display the selection highlight.
5180      *
5181      * @attr ref android.R.styleable#TextView_textColorHighlight
5182      */
5183     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)5184     public void setHighlightColor(@ColorInt int color) {
5185         if (mHighlightColor != color) {
5186             mHighlightColor = color;
5187             invalidate();
5188         }
5189     }
5190 
5191     /**
5192      * @return the color used to display the selection highlight
5193      *
5194      * @see #setHighlightColor(int)
5195      *
5196      * @attr ref android.R.styleable#TextView_textColorHighlight
5197      */
5198     @InspectableProperty(name = "textColorHighlight")
5199     @ColorInt
getHighlightColor()5200     public int getHighlightColor() {
5201         return mHighlightColor;
5202     }
5203 
5204     /**
5205      * Sets whether the soft input method will be made visible when this
5206      * TextView gets focused. The default is true.
5207      */
5208     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)5209     public final void setShowSoftInputOnFocus(boolean show) {
5210         createEditorIfNeeded();
5211         mEditor.mShowSoftInputOnFocus = show;
5212     }
5213 
5214     /**
5215      * Returns whether the soft input method will be made visible when this
5216      * TextView gets focused. The default is true.
5217      */
getShowSoftInputOnFocus()5218     public final boolean getShowSoftInputOnFocus() {
5219         // When there is no Editor, return default true value
5220         return mEditor == null || mEditor.mShowSoftInputOnFocus;
5221     }
5222 
5223     /**
5224      * Gives the text a shadow of the specified blur radius and color, the specified
5225      * distance from its drawn position.
5226      * <p>
5227      * The text shadow produced does not interact with the properties on view
5228      * that are responsible for real time shadows,
5229      * {@link View#getElevation() elevation} and
5230      * {@link View#getTranslationZ() translationZ}.
5231      *
5232      * @see Paint#setShadowLayer(float, float, float, int)
5233      *
5234      * @attr ref android.R.styleable#TextView_shadowColor
5235      * @attr ref android.R.styleable#TextView_shadowDx
5236      * @attr ref android.R.styleable#TextView_shadowDy
5237      * @attr ref android.R.styleable#TextView_shadowRadius
5238      */
setShadowLayer(float radius, float dx, float dy, int color)5239     public void setShadowLayer(float radius, float dx, float dy, int color) {
5240         mTextPaint.setShadowLayer(radius, dx, dy, color);
5241 
5242         mShadowRadius = radius;
5243         mShadowDx = dx;
5244         mShadowDy = dy;
5245         mShadowColor = color;
5246 
5247         // Will change text clip region
5248         if (mEditor != null) {
5249             mEditor.invalidateTextDisplayList();
5250             mEditor.invalidateHandlesAndActionMode();
5251         }
5252         invalidate();
5253     }
5254 
5255     /**
5256      * Gets the radius of the shadow layer.
5257      *
5258      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
5259      *
5260      * @see #setShadowLayer(float, float, float, int)
5261      *
5262      * @attr ref android.R.styleable#TextView_shadowRadius
5263      */
5264     @InspectableProperty
getShadowRadius()5265     public float getShadowRadius() {
5266         return mShadowRadius;
5267     }
5268 
5269     /**
5270      * @return the horizontal offset of the shadow layer
5271      *
5272      * @see #setShadowLayer(float, float, float, int)
5273      *
5274      * @attr ref android.R.styleable#TextView_shadowDx
5275      */
5276     @InspectableProperty
getShadowDx()5277     public float getShadowDx() {
5278         return mShadowDx;
5279     }
5280 
5281     /**
5282      * Gets the vertical offset of the shadow layer.
5283      * @return The vertical offset of the shadow layer.
5284      *
5285      * @see #setShadowLayer(float, float, float, int)
5286      *
5287      * @attr ref android.R.styleable#TextView_shadowDy
5288      */
5289     @InspectableProperty
getShadowDy()5290     public float getShadowDy() {
5291         return mShadowDy;
5292     }
5293 
5294     /**
5295      * Gets the color of the shadow layer.
5296      * @return the color of the shadow layer
5297      *
5298      * @see #setShadowLayer(float, float, float, int)
5299      *
5300      * @attr ref android.R.styleable#TextView_shadowColor
5301      */
5302     @InspectableProperty
5303     @ColorInt
getShadowColor()5304     public int getShadowColor() {
5305         return mShadowColor;
5306     }
5307 
5308     /**
5309      * Gets the {@link TextPaint} used for the text.
5310      * Use this only to consult the Paint's properties and not to change them.
5311      * @return The base paint used for the text.
5312      */
getPaint()5313     public TextPaint getPaint() {
5314         return mTextPaint;
5315     }
5316 
5317     /**
5318      * Sets the autolink mask of the text.  See {@link
5319      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
5320      * possible values.
5321      *
5322      * <p class="note"><b>Note:</b>
5323      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
5324      * is deprecated and should be avoided; see its documentation.
5325      *
5326      * @attr ref android.R.styleable#TextView_autoLink
5327      */
5328     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)5329     public final void setAutoLinkMask(int mask) {
5330         mAutoLinkMask = mask;
5331     }
5332 
5333     /**
5334      * Sets whether the movement method will automatically be set to
5335      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5336      * set to nonzero and links are detected in {@link #setText}.
5337      * The default is true.
5338      *
5339      * @attr ref android.R.styleable#TextView_linksClickable
5340      */
5341     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)5342     public final void setLinksClickable(boolean whether) {
5343         mLinksClickable = whether;
5344     }
5345 
5346     /**
5347      * Returns whether the movement method will automatically be set to
5348      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5349      * set to nonzero and links are detected in {@link #setText}.
5350      * The default is true.
5351      *
5352      * @attr ref android.R.styleable#TextView_linksClickable
5353      */
5354     @InspectableProperty
getLinksClickable()5355     public final boolean getLinksClickable() {
5356         return mLinksClickable;
5357     }
5358 
5359     /**
5360      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5361      * (by {@link Linkify} or otherwise) if any.  You can call
5362      * {@link URLSpan#getURL} on them to find where they link to
5363      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5364      * to find the region of the text they are attached to.
5365      */
getUrls()5366     public URLSpan[] getUrls() {
5367         if (mText instanceof Spanned) {
5368             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5369         } else {
5370             return new URLSpan[0];
5371         }
5372     }
5373 
5374     /**
5375      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5376      * TextView.
5377      *
5378      * @see #setHintTextColor(ColorStateList)
5379      * @see #getHintTextColors()
5380      * @see #setTextColor(int)
5381      *
5382      * @attr ref android.R.styleable#TextView_textColorHint
5383      */
5384     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)5385     public final void setHintTextColor(@ColorInt int color) {
5386         mHintTextColor = ColorStateList.valueOf(color);
5387         updateTextColors();
5388     }
5389 
5390     /**
5391      * Sets the color of the hint text.
5392      *
5393      * @see #getHintTextColors()
5394      * @see #setHintTextColor(int)
5395      * @see #setTextColor(ColorStateList)
5396      * @see #setLinkTextColor(ColorStateList)
5397      *
5398      * @attr ref android.R.styleable#TextView_textColorHint
5399      */
setHintTextColor(ColorStateList colors)5400     public final void setHintTextColor(ColorStateList colors) {
5401         mHintTextColor = colors;
5402         updateTextColors();
5403     }
5404 
5405     /**
5406      * @return the color of the hint text, for the different states of this TextView.
5407      *
5408      * @see #setHintTextColor(ColorStateList)
5409      * @see #setHintTextColor(int)
5410      * @see #setTextColor(ColorStateList)
5411      * @see #setLinkTextColor(ColorStateList)
5412      *
5413      * @attr ref android.R.styleable#TextView_textColorHint
5414      */
5415     @InspectableProperty(name = "textColorHint")
getHintTextColors()5416     public final ColorStateList getHintTextColors() {
5417         return mHintTextColor;
5418     }
5419 
5420     /**
5421      * <p>Return the current color selected to paint the hint text.</p>
5422      *
5423      * @return Returns the current hint text color.
5424      */
5425     @ColorInt
getCurrentHintTextColor()5426     public final int getCurrentHintTextColor() {
5427         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5428     }
5429 
5430     /**
5431      * Sets the color of links in the text.
5432      *
5433      * @see #setLinkTextColor(ColorStateList)
5434      * @see #getLinkTextColors()
5435      *
5436      * @attr ref android.R.styleable#TextView_textColorLink
5437      */
5438     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)5439     public final void setLinkTextColor(@ColorInt int color) {
5440         mLinkTextColor = ColorStateList.valueOf(color);
5441         updateTextColors();
5442     }
5443 
5444     /**
5445      * Sets the color of links in the text.
5446      *
5447      * @see #setLinkTextColor(int)
5448      * @see #getLinkTextColors()
5449      * @see #setTextColor(ColorStateList)
5450      * @see #setHintTextColor(ColorStateList)
5451      *
5452      * @attr ref android.R.styleable#TextView_textColorLink
5453      */
setLinkTextColor(ColorStateList colors)5454     public final void setLinkTextColor(ColorStateList colors) {
5455         mLinkTextColor = colors;
5456         updateTextColors();
5457     }
5458 
5459     /**
5460      * @return the list of colors used to paint the links in the text, for the different states of
5461      * this TextView
5462      *
5463      * @see #setLinkTextColor(ColorStateList)
5464      * @see #setLinkTextColor(int)
5465      *
5466      * @attr ref android.R.styleable#TextView_textColorLink
5467      */
5468     @InspectableProperty(name = "textColorLink")
getLinkTextColors()5469     public final ColorStateList getLinkTextColors() {
5470         return mLinkTextColor;
5471     }
5472 
5473     /**
5474      * Sets the horizontal alignment of the text and the
5475      * vertical gravity that will be used when there is extra space
5476      * in the TextView beyond what is required for the text itself.
5477      *
5478      * @see android.view.Gravity
5479      * @attr ref android.R.styleable#TextView_gravity
5480      */
5481     @android.view.RemotableViewMethod
setGravity(int gravity)5482     public void setGravity(int gravity) {
5483         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5484             gravity |= Gravity.START;
5485         }
5486         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5487             gravity |= Gravity.TOP;
5488         }
5489 
5490         boolean newLayout = false;
5491 
5492         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5493                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5494             newLayout = true;
5495         }
5496 
5497         if (gravity != mGravity) {
5498             invalidate();
5499         }
5500 
5501         mGravity = gravity;
5502 
5503         if (mLayout != null && newLayout) {
5504             // XXX this is heavy-handed because no actual content changes.
5505             int want = mLayout.getWidth();
5506             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5507 
5508             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5509                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5510         }
5511     }
5512 
5513     /**
5514      * Returns the horizontal and vertical alignment of this TextView.
5515      *
5516      * @see android.view.Gravity
5517      * @attr ref android.R.styleable#TextView_gravity
5518      */
5519     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()5520     public int getGravity() {
5521         return mGravity;
5522     }
5523 
5524     /**
5525      * Gets the flags on the Paint being used to display the text.
5526      * @return The flags on the Paint being used to display the text.
5527      * @see Paint#getFlags
5528      */
getPaintFlags()5529     public int getPaintFlags() {
5530         return mTextPaint.getFlags();
5531     }
5532 
5533     /**
5534      * Sets flags on the Paint being used to display the text and
5535      * reflows the text if they are different from the old flags.
5536      * @see Paint#setFlags
5537      */
5538     @android.view.RemotableViewMethod
setPaintFlags(int flags)5539     public void setPaintFlags(int flags) {
5540         if (mTextPaint.getFlags() != flags) {
5541             mTextPaint.setFlags(flags);
5542 
5543             if (mLayout != null) {
5544                 nullLayouts();
5545                 requestLayout();
5546                 invalidate();
5547             }
5548         }
5549     }
5550 
5551     /**
5552      * Sets whether the text should be allowed to be wider than the
5553      * View is.  If false, it will be wrapped to the width of the View.
5554      *
5555      * @attr ref android.R.styleable#TextView_scrollHorizontally
5556      */
setHorizontallyScrolling(boolean whether)5557     public void setHorizontallyScrolling(boolean whether) {
5558         if (mHorizontallyScrolling != whether) {
5559             mHorizontallyScrolling = whether;
5560 
5561             if (mLayout != null) {
5562                 nullLayouts();
5563                 requestLayout();
5564                 invalidate();
5565             }
5566         }
5567     }
5568 
5569     /**
5570      * Returns whether the text is allowed to be wider than the View.
5571      * If false, the text will be wrapped to the width of the View.
5572      *
5573      * @attr ref android.R.styleable#TextView_scrollHorizontally
5574      * @see #setHorizontallyScrolling(boolean)
5575      */
5576     @InspectableProperty(name = "scrollHorizontally")
isHorizontallyScrollable()5577     public final boolean isHorizontallyScrollable() {
5578         return mHorizontallyScrolling;
5579     }
5580 
5581     /**
5582      * Returns whether the text is allowed to be wider than the View.
5583      * If false, the text will be wrapped to the width of the View.
5584      *
5585      * @attr ref android.R.styleable#TextView_scrollHorizontally
5586      * @hide
5587      */
5588     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
getHorizontallyScrolling()5589     public boolean getHorizontallyScrolling() {
5590         return mHorizontallyScrolling;
5591     }
5592 
5593     /**
5594      * Sets the height of the TextView to be at least {@code minLines} tall.
5595      * <p>
5596      * This value is used for height calculation if LayoutParams does not force TextView to have an
5597      * exact height. Setting this value overrides other previous minimum height configurations such
5598      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
5599      * this value to 1.
5600      *
5601      * @param minLines the minimum height of TextView in terms of number of lines
5602      *
5603      * @see #getMinLines()
5604      * @see #setLines(int)
5605      *
5606      * @attr ref android.R.styleable#TextView_minLines
5607      */
5608     @android.view.RemotableViewMethod
setMinLines(int minLines)5609     public void setMinLines(int minLines) {
5610         mMinimum = minLines;
5611         mMinMode = LINES;
5612 
5613         requestLayout();
5614         invalidate();
5615     }
5616 
5617     /**
5618      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
5619      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
5620      *
5621      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
5622      *         height is not defined in lines
5623      *
5624      * @see #setMinLines(int)
5625      * @see #setLines(int)
5626      *
5627      * @attr ref android.R.styleable#TextView_minLines
5628      */
5629     @InspectableProperty
getMinLines()5630     public int getMinLines() {
5631         return mMinMode == LINES ? mMinimum : -1;
5632     }
5633 
5634     /**
5635      * Sets the height of the TextView to be at least {@code minPixels} tall.
5636      * <p>
5637      * This value is used for height calculation if LayoutParams does not force TextView to have an
5638      * exact height. Setting this value overrides previous minimum height configurations such as
5639      * {@link #setMinLines(int)} or {@link #setLines(int)}.
5640      * <p>
5641      * The value given here is different than {@link #setMinimumHeight(int)}. Between
5642      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
5643      * used to decide the final height.
5644      *
5645      * @param minPixels the minimum height of TextView in terms of pixels
5646      *
5647      * @see #getMinHeight()
5648      * @see #setHeight(int)
5649      *
5650      * @attr ref android.R.styleable#TextView_minHeight
5651      */
5652     @android.view.RemotableViewMethod
setMinHeight(int minPixels)5653     public void setMinHeight(int minPixels) {
5654         mMinimum = minPixels;
5655         mMinMode = PIXELS;
5656 
5657         requestLayout();
5658         invalidate();
5659     }
5660 
5661     /**
5662      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
5663      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
5664      *
5665      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
5666      *         defined in pixels
5667      *
5668      * @see #setMinHeight(int)
5669      * @see #setHeight(int)
5670      *
5671      * @attr ref android.R.styleable#TextView_minHeight
5672      */
getMinHeight()5673     public int getMinHeight() {
5674         return mMinMode == PIXELS ? mMinimum : -1;
5675     }
5676 
5677     /**
5678      * Sets the height of the TextView to be at most {@code maxLines} tall.
5679      * <p>
5680      * This value is used for height calculation if LayoutParams does not force TextView to have an
5681      * exact height. Setting this value overrides previous maximum height configurations such as
5682      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
5683      *
5684      * @param maxLines the maximum height of TextView in terms of number of lines
5685      *
5686      * @see #getMaxLines()
5687      * @see #setLines(int)
5688      *
5689      * @attr ref android.R.styleable#TextView_maxLines
5690      */
5691     @android.view.RemotableViewMethod
setMaxLines(int maxLines)5692     public void setMaxLines(int maxLines) {
5693         mMaximum = maxLines;
5694         mMaxMode = LINES;
5695 
5696         requestLayout();
5697         invalidate();
5698     }
5699 
5700     /**
5701      * Returns the maximum height of TextView in terms of number of lines or -1 if the
5702      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
5703      *
5704      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
5705      *         is not defined in lines.
5706      *
5707      * @see #setMaxLines(int)
5708      * @see #setLines(int)
5709      *
5710      * @attr ref android.R.styleable#TextView_maxLines
5711      */
5712     @InspectableProperty
getMaxLines()5713     public int getMaxLines() {
5714         return mMaxMode == LINES ? mMaximum : -1;
5715     }
5716 
5717     /**
5718      * Sets the height of the TextView to be at most {@code maxPixels} tall.
5719      * <p>
5720      * This value is used for height calculation if LayoutParams does not force TextView to have an
5721      * exact height. Setting this value overrides previous maximum height configurations such as
5722      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
5723      *
5724      * @param maxPixels the maximum height of TextView in terms of pixels
5725      *
5726      * @see #getMaxHeight()
5727      * @see #setHeight(int)
5728      *
5729      * @attr ref android.R.styleable#TextView_maxHeight
5730      */
5731     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)5732     public void setMaxHeight(int maxPixels) {
5733         mMaximum = maxPixels;
5734         mMaxMode = PIXELS;
5735 
5736         requestLayout();
5737         invalidate();
5738     }
5739 
5740     /**
5741      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
5742      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
5743      *
5744      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
5745      *         is not defined in pixels
5746      *
5747      * @see #setMaxHeight(int)
5748      * @see #setHeight(int)
5749      *
5750      * @attr ref android.R.styleable#TextView_maxHeight
5751      */
5752     @InspectableProperty
getMaxHeight()5753     public int getMaxHeight() {
5754         return mMaxMode == PIXELS ? mMaximum : -1;
5755     }
5756 
5757     /**
5758      * Sets the height of the TextView to be exactly {@code lines} tall.
5759      * <p>
5760      * This value is used for height calculation if LayoutParams does not force TextView to have an
5761      * exact height. Setting this value overrides previous minimum/maximum height configurations
5762      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
5763      * set this value to 1.
5764      *
5765      * @param lines the exact height of the TextView in terms of lines
5766      *
5767      * @see #setHeight(int)
5768      *
5769      * @attr ref android.R.styleable#TextView_lines
5770      */
5771     @android.view.RemotableViewMethod
setLines(int lines)5772     public void setLines(int lines) {
5773         mMaximum = mMinimum = lines;
5774         mMaxMode = mMinMode = LINES;
5775 
5776         requestLayout();
5777         invalidate();
5778     }
5779 
5780     /**
5781      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
5782      * <p>
5783      * This value is used for height calculation if LayoutParams does not force TextView to have an
5784      * exact height. Setting this value overrides previous minimum/maximum height configurations
5785      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
5786      *
5787      * @param pixels the exact height of the TextView in terms of pixels
5788      *
5789      * @see #setLines(int)
5790      *
5791      * @attr ref android.R.styleable#TextView_height
5792      */
5793     @android.view.RemotableViewMethod
setHeight(int pixels)5794     public void setHeight(int pixels) {
5795         mMaximum = mMinimum = pixels;
5796         mMaxMode = mMinMode = PIXELS;
5797 
5798         requestLayout();
5799         invalidate();
5800     }
5801 
5802     /**
5803      * Sets the width of the TextView to be at least {@code minEms} wide.
5804      * <p>
5805      * This value is used for width calculation if LayoutParams does not force TextView to have an
5806      * exact width. Setting this value overrides previous minimum width configurations such as
5807      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5808      *
5809      * @param minEms the minimum width of TextView in terms of ems
5810      *
5811      * @see #getMinEms()
5812      * @see #setEms(int)
5813      *
5814      * @attr ref android.R.styleable#TextView_minEms
5815      */
5816     @android.view.RemotableViewMethod
setMinEms(int minEms)5817     public void setMinEms(int minEms) {
5818         mMinWidth = minEms;
5819         mMinWidthMode = EMS;
5820 
5821         requestLayout();
5822         invalidate();
5823     }
5824 
5825     /**
5826      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
5827      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5828      *
5829      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
5830      *         defined in ems
5831      *
5832      * @see #setMinEms(int)
5833      * @see #setEms(int)
5834      *
5835      * @attr ref android.R.styleable#TextView_minEms
5836      */
5837     @InspectableProperty
getMinEms()5838     public int getMinEms() {
5839         return mMinWidthMode == EMS ? mMinWidth : -1;
5840     }
5841 
5842     /**
5843      * Sets the width of the TextView to be at least {@code minPixels} wide.
5844      * <p>
5845      * This value is used for width calculation if LayoutParams does not force TextView to have an
5846      * exact width. Setting this value overrides previous minimum width configurations such as
5847      * {@link #setMinEms(int)} or {@link #setEms(int)}.
5848      * <p>
5849      * The value given here is different than {@link #setMinimumWidth(int)}. Between
5850      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
5851      * to decide the final width.
5852      *
5853      * @param minPixels the minimum width of TextView in terms of pixels
5854      *
5855      * @see #getMinWidth()
5856      * @see #setWidth(int)
5857      *
5858      * @attr ref android.R.styleable#TextView_minWidth
5859      */
5860     @android.view.RemotableViewMethod
setMinWidth(int minPixels)5861     public void setMinWidth(int minPixels) {
5862         mMinWidth = minPixels;
5863         mMinWidthMode = PIXELS;
5864 
5865         requestLayout();
5866         invalidate();
5867     }
5868 
5869     /**
5870      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
5871      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
5872      *
5873      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
5874      *         defined in pixels
5875      *
5876      * @see #setMinWidth(int)
5877      * @see #setWidth(int)
5878      *
5879      * @attr ref android.R.styleable#TextView_minWidth
5880      */
5881     @InspectableProperty
getMinWidth()5882     public int getMinWidth() {
5883         return mMinWidthMode == PIXELS ? mMinWidth : -1;
5884     }
5885 
5886     /**
5887      * Sets the width of the TextView to be at most {@code maxEms} wide.
5888      * <p>
5889      * This value is used for width calculation if LayoutParams does not force TextView to have an
5890      * exact width. Setting this value overrides previous maximum width configurations such as
5891      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5892      *
5893      * @param maxEms the maximum width of TextView in terms of ems
5894      *
5895      * @see #getMaxEms()
5896      * @see #setEms(int)
5897      *
5898      * @attr ref android.R.styleable#TextView_maxEms
5899      */
5900     @android.view.RemotableViewMethod
setMaxEms(int maxEms)5901     public void setMaxEms(int maxEms) {
5902         mMaxWidth = maxEms;
5903         mMaxWidthMode = EMS;
5904 
5905         requestLayout();
5906         invalidate();
5907     }
5908 
5909     /**
5910      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
5911      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5912      *
5913      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
5914      *         defined in ems
5915      *
5916      * @see #setMaxEms(int)
5917      * @see #setEms(int)
5918      *
5919      * @attr ref android.R.styleable#TextView_maxEms
5920      */
5921     @InspectableProperty
getMaxEms()5922     public int getMaxEms() {
5923         return mMaxWidthMode == EMS ? mMaxWidth : -1;
5924     }
5925 
5926     /**
5927      * Sets the width of the TextView to be at most {@code maxPixels} wide.
5928      * <p>
5929      * This value is used for width calculation if LayoutParams does not force TextView to have an
5930      * exact width. Setting this value overrides previous maximum width configurations such as
5931      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
5932      *
5933      * @param maxPixels the maximum width of TextView in terms of pixels
5934      *
5935      * @see #getMaxWidth()
5936      * @see #setWidth(int)
5937      *
5938      * @attr ref android.R.styleable#TextView_maxWidth
5939      */
5940     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)5941     public void setMaxWidth(int maxPixels) {
5942         mMaxWidth = maxPixels;
5943         mMaxWidthMode = PIXELS;
5944 
5945         requestLayout();
5946         invalidate();
5947     }
5948 
5949     /**
5950      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
5951      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
5952      *
5953      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
5954      *         defined in pixels
5955      *
5956      * @see #setMaxWidth(int)
5957      * @see #setWidth(int)
5958      *
5959      * @attr ref android.R.styleable#TextView_maxWidth
5960      */
5961     @InspectableProperty
getMaxWidth()5962     public int getMaxWidth() {
5963         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
5964     }
5965 
5966     /**
5967      * Sets the width of the TextView to be exactly {@code ems} wide.
5968      *
5969      * This value is used for width calculation if LayoutParams does not force TextView to have an
5970      * exact width. Setting this value overrides previous minimum/maximum configurations such as
5971      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
5972      *
5973      * @param ems the exact width of the TextView in terms of ems
5974      *
5975      * @see #setWidth(int)
5976      *
5977      * @attr ref android.R.styleable#TextView_ems
5978      */
5979     @android.view.RemotableViewMethod
setEms(int ems)5980     public void setEms(int ems) {
5981         mMaxWidth = mMinWidth = ems;
5982         mMaxWidthMode = mMinWidthMode = EMS;
5983 
5984         requestLayout();
5985         invalidate();
5986     }
5987 
5988     /**
5989      * Sets the width of the TextView to be exactly {@code pixels} wide.
5990      * <p>
5991      * This value is used for width calculation if LayoutParams does not force TextView to have an
5992      * exact width. Setting this value overrides previous minimum/maximum width configurations
5993      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
5994      *
5995      * @param pixels the exact width of the TextView in terms of pixels
5996      *
5997      * @see #setEms(int)
5998      *
5999      * @attr ref android.R.styleable#TextView_width
6000      */
6001     @android.view.RemotableViewMethod
setWidth(int pixels)6002     public void setWidth(int pixels) {
6003         mMaxWidth = mMinWidth = pixels;
6004         mMaxWidthMode = mMinWidthMode = PIXELS;
6005 
6006         requestLayout();
6007         invalidate();
6008     }
6009 
6010     /**
6011      * Sets line spacing for this TextView.  Each line other than the last line will have its height
6012      * multiplied by {@code mult} and have {@code add} added to it.
6013      *
6014      * @param add The value in pixels that should be added to each line other than the last line.
6015      *            This will be applied after the multiplier
6016      * @param mult The value by which each line height other than the last line will be multiplied
6017      *             by
6018      *
6019      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6020      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6021      */
setLineSpacing(float add, float mult)6022     public void setLineSpacing(float add, float mult) {
6023         if (mSpacingAdd != add || mSpacingMult != mult) {
6024             mSpacingAdd = add;
6025             mSpacingMult = mult;
6026 
6027             if (mLayout != null) {
6028                 nullLayouts();
6029                 requestLayout();
6030                 invalidate();
6031             }
6032         }
6033     }
6034 
6035     /**
6036      * Gets the line spacing multiplier
6037      *
6038      * @return the value by which each line's height is multiplied to get its actual height.
6039      *
6040      * @see #setLineSpacing(float, float)
6041      * @see #getLineSpacingExtra()
6042      *
6043      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6044      */
6045     @InspectableProperty
getLineSpacingMultiplier()6046     public float getLineSpacingMultiplier() {
6047         return mSpacingMult;
6048     }
6049 
6050     /**
6051      * Gets the line spacing extra space
6052      *
6053      * @return the extra space that is added to the height of each lines of this TextView.
6054      *
6055      * @see #setLineSpacing(float, float)
6056      * @see #getLineSpacingMultiplier()
6057      *
6058      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6059      */
6060     @InspectableProperty
getLineSpacingExtra()6061     public float getLineSpacingExtra() {
6062         return mSpacingAdd;
6063     }
6064 
6065     /**
6066      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
6067      * between subsequent baselines in the TextView.
6068      *
6069      * @param lineHeight the line height in pixels
6070      *
6071      * @see #setLineSpacing(float, float)
6072      * @see #getLineSpacingExtra()
6073      *
6074      * @attr ref android.R.styleable#TextView_lineHeight
6075      */
6076     @android.view.RemotableViewMethod
setLineHeight(@x @ntRangefrom = 0) int lineHeight)6077     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
6078         Preconditions.checkArgumentNonnegative(lineHeight);
6079 
6080         final int fontHeight = getPaint().getFontMetricsInt(null);
6081         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
6082         if (lineHeight != fontHeight) {
6083             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
6084             setLineSpacing(lineHeight - fontHeight, 1f);
6085         }
6086     }
6087 
6088     /**
6089      * Convenience method to append the specified text to the TextView's
6090      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6091      * if it was not already editable.
6092      *
6093      * @param text text to be appended to the already displayed text
6094      */
append(CharSequence text)6095     public final void append(CharSequence text) {
6096         append(text, 0, text.length());
6097     }
6098 
6099     /**
6100      * Convenience method to append the specified text slice to the TextView's
6101      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6102      * if it was not already editable.
6103      *
6104      * @param text text to be appended to the already displayed text
6105      * @param start the index of the first character in the {@code text}
6106      * @param end the index of the character following the last character in the {@code text}
6107      *
6108      * @see Appendable#append(CharSequence, int, int)
6109      */
append(CharSequence text, int start, int end)6110     public void append(CharSequence text, int start, int end) {
6111         if (!(mText instanceof Editable)) {
6112             setText(mText, BufferType.EDITABLE);
6113         }
6114 
6115         ((Editable) mText).append(text, start, end);
6116 
6117         if (mAutoLinkMask != 0) {
6118             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
6119             // Do not change the movement method for text that support text selection as it
6120             // would prevent an arbitrary cursor displacement.
6121             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
6122                 setMovementMethod(LinkMovementMethod.getInstance());
6123             }
6124         }
6125     }
6126 
updateTextColors()6127     private void updateTextColors() {
6128         boolean inval = false;
6129         final int[] drawableState = getDrawableState();
6130         int color = mTextColor.getColorForState(drawableState, 0);
6131         if (color != mCurTextColor) {
6132             mCurTextColor = color;
6133             inval = true;
6134         }
6135         if (mLinkTextColor != null) {
6136             color = mLinkTextColor.getColorForState(drawableState, 0);
6137             if (color != mTextPaint.linkColor) {
6138                 mTextPaint.linkColor = color;
6139                 inval = true;
6140             }
6141         }
6142         if (mHintTextColor != null) {
6143             color = mHintTextColor.getColorForState(drawableState, 0);
6144             if (color != mCurHintTextColor) {
6145                 mCurHintTextColor = color;
6146                 if (mText.length() == 0) {
6147                     inval = true;
6148                 }
6149             }
6150         }
6151         if (inval) {
6152             // Text needs to be redrawn with the new color
6153             if (mEditor != null) mEditor.invalidateTextDisplayList();
6154             invalidate();
6155         }
6156     }
6157 
6158     @Override
drawableStateChanged()6159     protected void drawableStateChanged() {
6160         super.drawableStateChanged();
6161 
6162         if (mTextColor != null && mTextColor.isStateful()
6163                 || (mHintTextColor != null && mHintTextColor.isStateful())
6164                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
6165             updateTextColors();
6166         }
6167 
6168         if (mDrawables != null) {
6169             final int[] state = getDrawableState();
6170             for (Drawable dr : mDrawables.mShowing) {
6171                 if (dr != null && dr.isStateful() && dr.setState(state)) {
6172                     invalidateDrawable(dr);
6173                 }
6174             }
6175         }
6176     }
6177 
6178     @Override
drawableHotspotChanged(float x, float y)6179     public void drawableHotspotChanged(float x, float y) {
6180         super.drawableHotspotChanged(x, y);
6181 
6182         if (mDrawables != null) {
6183             for (Drawable dr : mDrawables.mShowing) {
6184                 if (dr != null) {
6185                     dr.setHotspot(x, y);
6186                 }
6187             }
6188         }
6189     }
6190 
6191     @Override
onSaveInstanceState()6192     public Parcelable onSaveInstanceState() {
6193         Parcelable superState = super.onSaveInstanceState();
6194 
6195         // Save state if we are forced to
6196         final boolean freezesText = getFreezesText();
6197         boolean hasSelection = false;
6198         int start = -1;
6199         int end = -1;
6200 
6201         if (mText != null) {
6202             start = getSelectionStart();
6203             end = getSelectionEnd();
6204             if (start >= 0 || end >= 0) {
6205                 // Or save state if there is a selection
6206                 hasSelection = true;
6207             }
6208         }
6209 
6210         if (freezesText || hasSelection) {
6211             SavedState ss = new SavedState(superState);
6212 
6213             if (freezesText) {
6214                 if (mText instanceof Spanned) {
6215                     final Spannable sp = new SpannableStringBuilder(mText);
6216 
6217                     if (mEditor != null) {
6218                         removeMisspelledSpans(sp);
6219                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
6220                     }
6221 
6222                     ss.text = sp;
6223                 } else {
6224                     ss.text = mText.toString();
6225                 }
6226             }
6227 
6228             if (hasSelection) {
6229                 // XXX Should also save the current scroll position!
6230                 ss.selStart = start;
6231                 ss.selEnd = end;
6232             }
6233 
6234             if (isFocused() && start >= 0 && end >= 0) {
6235                 ss.frozenWithFocus = true;
6236             }
6237 
6238             ss.error = getError();
6239 
6240             if (mEditor != null) {
6241                 ss.editorState = mEditor.saveInstanceState();
6242             }
6243             return ss;
6244         }
6245 
6246         return superState;
6247     }
6248 
removeMisspelledSpans(Spannable spannable)6249     void removeMisspelledSpans(Spannable spannable) {
6250         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
6251                 SuggestionSpan.class);
6252         for (int i = 0; i < suggestionSpans.length; i++) {
6253             int flags = suggestionSpans[i].getFlags();
6254             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
6255                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
6256                 spannable.removeSpan(suggestionSpans[i]);
6257             }
6258         }
6259     }
6260 
6261     @Override
onRestoreInstanceState(Parcelable state)6262     public void onRestoreInstanceState(Parcelable state) {
6263         if (!(state instanceof SavedState)) {
6264             super.onRestoreInstanceState(state);
6265             return;
6266         }
6267 
6268         SavedState ss = (SavedState) state;
6269         super.onRestoreInstanceState(ss.getSuperState());
6270 
6271         // XXX restore buffer type too, as well as lots of other stuff
6272         if (ss.text != null) {
6273             setText(ss.text);
6274         }
6275 
6276         if (ss.selStart >= 0 && ss.selEnd >= 0) {
6277             if (mSpannable != null) {
6278                 int len = mText.length();
6279 
6280                 if (ss.selStart > len || ss.selEnd > len) {
6281                     String restored = "";
6282 
6283                     if (ss.text != null) {
6284                         restored = "(restored) ";
6285                     }
6286 
6287                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
6288                             + " out of range for " + restored + "text " + mText);
6289                 } else {
6290                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
6291 
6292                     if (ss.frozenWithFocus) {
6293                         createEditorIfNeeded();
6294                         mEditor.mFrozenWithFocus = true;
6295                     }
6296                 }
6297             }
6298         }
6299 
6300         if (ss.error != null) {
6301             final CharSequence error = ss.error;
6302             // Display the error later, after the first layout pass
6303             post(new Runnable() {
6304                 public void run() {
6305                     if (mEditor == null || !mEditor.mErrorWasChanged) {
6306                         setError(error);
6307                     }
6308                 }
6309             });
6310         }
6311 
6312         if (ss.editorState != null) {
6313             createEditorIfNeeded();
6314             mEditor.restoreInstanceState(ss.editorState);
6315         }
6316     }
6317 
6318     /**
6319      * Control whether this text view saves its entire text contents when
6320      * freezing to an icicle, in addition to dynamic state such as cursor
6321      * position.  By default this is false, not saving the text.  Set to true
6322      * if the text in the text view is not being saved somewhere else in
6323      * persistent storage (such as in a content provider) so that if the
6324      * view is later thawed the user will not lose their data. For
6325      * {@link android.widget.EditText} it is always enabled, regardless of
6326      * the value of the attribute.
6327      *
6328      * @param freezesText Controls whether a frozen icicle should include the
6329      * entire text data: true to include it, false to not.
6330      *
6331      * @attr ref android.R.styleable#TextView_freezesText
6332      */
6333     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)6334     public void setFreezesText(boolean freezesText) {
6335         mFreezesText = freezesText;
6336     }
6337 
6338     /**
6339      * Return whether this text view is including its entire text contents
6340      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
6341      *
6342      * @return Returns true if text is included, false if it isn't.
6343      *
6344      * @see #setFreezesText
6345      */
6346     @InspectableProperty
getFreezesText()6347     public boolean getFreezesText() {
6348         return mFreezesText;
6349     }
6350 
6351     ///////////////////////////////////////////////////////////////////////////
6352 
6353     /**
6354      * Sets the Factory used to create new {@link Editable Editables}.
6355      *
6356      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
6357      *
6358      * @see android.text.Editable.Factory
6359      * @see android.widget.TextView.BufferType#EDITABLE
6360      */
setEditableFactory(Editable.Factory factory)6361     public final void setEditableFactory(Editable.Factory factory) {
6362         mEditableFactory = factory;
6363         setText(mText);
6364     }
6365 
6366     /**
6367      * Sets the Factory used to create new {@link Spannable Spannables}.
6368      *
6369      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
6370      *
6371      * @see android.text.Spannable.Factory
6372      * @see android.widget.TextView.BufferType#SPANNABLE
6373      */
setSpannableFactory(Spannable.Factory factory)6374     public final void setSpannableFactory(Spannable.Factory factory) {
6375         mSpannableFactory = factory;
6376         setText(mText);
6377     }
6378 
6379     /**
6380      * Sets the text to be displayed. TextView <em>does not</em> accept
6381      * HTML-like formatting, which you can do with text strings in XML resource files.
6382      * To style your strings, attach android.text.style.* objects to a
6383      * {@link android.text.SpannableString}, or see the
6384      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
6385      * Available Resource Types</a> documentation for an example of setting
6386      * formatted text in the XML resource file.
6387      * <p/>
6388      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6389      * intermediate {@link Spannable Spannables}. Likewise it will use
6390      * {@link android.text.Editable.Factory} to create final or intermediate
6391      * {@link Editable Editables}.
6392      *
6393      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
6394      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
6395      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
6396      *
6397      * @param text text to be displayed
6398      *
6399      * @attr ref android.R.styleable#TextView_text
6400      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
6401      *                                  parameters used to create the PrecomputedText mismatches
6402      *                                  with this TextView.
6403      */
6404     @android.view.RemotableViewMethod
setText(CharSequence text)6405     public final void setText(CharSequence text) {
6406         setText(text, mBufferType);
6407     }
6408 
6409     /**
6410      * Sets the text to be displayed but retains the cursor position. Same as
6411      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
6412      * new text.
6413      * <p/>
6414      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6415      * intermediate {@link Spannable Spannables}. Likewise it will use
6416      * {@link android.text.Editable.Factory} to create final or intermediate
6417      * {@link Editable Editables}.
6418      *
6419      * @param text text to be displayed
6420      *
6421      * @see #setText(CharSequence)
6422      */
6423     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)6424     public final void setTextKeepState(CharSequence text) {
6425         setTextKeepState(text, mBufferType);
6426     }
6427 
6428     /**
6429      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
6430      * <p/>
6431      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6432      * intermediate {@link Spannable Spannables}. Likewise it will use
6433      * {@link android.text.Editable.Factory} to create final or intermediate
6434      * {@link Editable Editables}.
6435      *
6436      * Subclasses overriding this method should ensure that the following post condition holds,
6437      * in order to guarantee the safety of the view's measurement and layout operations:
6438      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
6439      * will be different from {@code null}.
6440      *
6441      * @param text text to be displayed
6442      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6443      *              stored as a static text, styleable/spannable text, or editable text
6444      *
6445      * @see #setText(CharSequence)
6446      * @see android.widget.TextView.BufferType
6447      * @see #setSpannableFactory(Spannable.Factory)
6448      * @see #setEditableFactory(Editable.Factory)
6449      *
6450      * @attr ref android.R.styleable#TextView_text
6451      * @attr ref android.R.styleable#TextView_bufferType
6452      */
setText(CharSequence text, BufferType type)6453     public void setText(CharSequence text, BufferType type) {
6454         setText(text, type, true, 0);
6455 
6456         // drop any potential mCharWrappper leaks
6457         mCharWrapper = null;
6458     }
6459 
6460     @UnsupportedAppUsage
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6461     private void setText(CharSequence text, BufferType type,
6462                          boolean notifyBefore, int oldlen) {
6463         mTextSetFromXmlOrResourceId = false;
6464         if (text == null) {
6465             text = "";
6466         }
6467 
6468         // If suggestions are not enabled, remove the suggestion spans from the text
6469         if (!isSuggestionsEnabled()) {
6470             text = removeSuggestionSpans(text);
6471         }
6472 
6473         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
6474 
6475         if (text instanceof Spanned
6476                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
6477             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
6478                 setHorizontalFadingEdgeEnabled(true);
6479                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
6480             } else {
6481                 setHorizontalFadingEdgeEnabled(false);
6482                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
6483             }
6484             setEllipsize(TextUtils.TruncateAt.MARQUEE);
6485         }
6486 
6487         int n = mFilters.length;
6488         for (int i = 0; i < n; i++) {
6489             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
6490             if (out != null) {
6491                 text = out;
6492             }
6493         }
6494 
6495         if (notifyBefore) {
6496             if (mText != null) {
6497                 oldlen = mText.length();
6498                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
6499             } else {
6500                 sendBeforeTextChanged("", 0, 0, text.length());
6501             }
6502         }
6503 
6504         boolean needEditableForNotification = false;
6505 
6506         if (mListeners != null && mListeners.size() != 0) {
6507             needEditableForNotification = true;
6508         }
6509 
6510         PrecomputedText precomputed =
6511                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
6512         if (type == BufferType.EDITABLE || getKeyListener() != null
6513                 || needEditableForNotification) {
6514             createEditorIfNeeded();
6515             mEditor.forgetUndoRedo();
6516             mEditor.scheduleRestartInputForSetText();
6517             Editable t = mEditableFactory.newEditable(text);
6518             text = t;
6519             setFilters(t, mFilters);
6520         } else if (precomputed != null) {
6521             if (mTextDir == null) {
6522                 mTextDir = getTextDirectionHeuristic();
6523             }
6524             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
6525                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
6526                             mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
6527                                     mLineBreakStyle, mLineBreakWordStyle));
6528             switch (checkResult) {
6529                 case PrecomputedText.Params.UNUSABLE:
6530                     throw new IllegalArgumentException(
6531                         "PrecomputedText's Parameters don't match the parameters of this TextView."
6532                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
6533                         + "to override the settings of this TextView: "
6534                         + "PrecomputedText: " + precomputed.getParams()
6535                         + "TextView: " + getTextMetricsParams());
6536                 case PrecomputedText.Params.NEED_RECOMPUTE:
6537                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
6538                     break;
6539                 case PrecomputedText.Params.USABLE:
6540                     // pass through
6541             }
6542         } else if (type == BufferType.SPANNABLE || mMovement != null) {
6543             text = mSpannableFactory.newSpannable(text);
6544         } else if (!(text instanceof CharWrapper)) {
6545             text = TextUtils.stringOrSpannedString(text);
6546         }
6547 
6548         @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE;
6549         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
6550             a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText);
6551         }
6552 
6553         if (mAutoLinkMask != 0) {
6554             Spannable s2;
6555 
6556             if (type == BufferType.EDITABLE || text instanceof Spannable) {
6557                 s2 = (Spannable) text;
6558             } else {
6559                 s2 = mSpannableFactory.newSpannable(text);
6560             }
6561 
6562             if (Linkify.addLinks(s2, mAutoLinkMask)) {
6563                 text = s2;
6564                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
6565 
6566                 /*
6567                  * We must go ahead and set the text before changing the
6568                  * movement method, because setMovementMethod() may call
6569                  * setText() again to try to upgrade the buffer type.
6570                  */
6571                 setTextInternal(text);
6572                 if (a11yTextChangeType == AccessibilityUtils.NONE) {
6573                     a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN;
6574                 }
6575 
6576                 // Do not change the movement method for text that support text selection as it
6577                 // would prevent an arbitrary cursor displacement.
6578                 if (mLinksClickable && !textCanBeSelected()) {
6579                     setMovementMethod(LinkMovementMethod.getInstance());
6580                 }
6581             }
6582         }
6583 
6584         mBufferType = type;
6585         setTextInternal(text);
6586 
6587         if (mTransformation == null) {
6588             mTransformed = text;
6589         } else {
6590             mTransformed = mTransformation.getTransformation(text, this);
6591         }
6592         if (mTransformed == null) {
6593             // Should not happen if the transformation method follows the non-null postcondition.
6594             mTransformed = "";
6595         }
6596 
6597         final int textLength = text.length();
6598 
6599         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
6600             Spannable sp = (Spannable) text;
6601 
6602             // Remove any ChangeWatchers that might have come from other TextViews.
6603             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
6604             final int count = watchers.length;
6605             for (int i = 0; i < count; i++) {
6606                 sp.removeSpan(watchers[i]);
6607             }
6608 
6609             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
6610 
6611             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
6612                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
6613 
6614             if (mEditor != null) mEditor.addSpanWatchers(sp);
6615 
6616             if (mTransformation != null) {
6617                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
6618             }
6619 
6620             if (mMovement != null) {
6621                 mMovement.initialize(this, (Spannable) text);
6622 
6623                 /*
6624                  * Initializing the movement method will have set the
6625                  * selection, so reset mSelectionMoved to keep that from
6626                  * interfering with the normal on-focus selection-setting.
6627                  */
6628                 if (mEditor != null) mEditor.mSelectionMoved = false;
6629             }
6630         }
6631 
6632         if (mLayout != null) {
6633             checkForRelayout();
6634         }
6635 
6636         sendOnTextChanged(text, 0, oldlen, textLength);
6637         onTextChanged(text, 0, oldlen, textLength);
6638 
6639         if (a11yTextChangeType == AccessibilityUtils.TEXT) {
6640             notifyViewAccessibilityStateChangedIfNeeded(
6641                     AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
6642         } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) {
6643             notifyViewAccessibilityStateChangedIfNeeded(
6644                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
6645         }
6646 
6647         if (needEditableForNotification) {
6648             sendAfterTextChanged((Editable) text);
6649         } else {
6650             notifyListeningManagersAfterTextChanged();
6651         }
6652 
6653         if (mEditor != null) {
6654             // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
6655             mEditor.prepareCursorControllers();
6656 
6657             mEditor.maybeFireScheduledRestartInputForSetText();
6658         }
6659     }
6660 
6661     /**
6662      * Sets the TextView to display the specified slice of the specified
6663      * char array. You must promise that you will not change the contents
6664      * of the array except for right before another call to setText(),
6665      * since the TextView has no way to know that the text
6666      * has changed and that it needs to invalidate and re-layout.
6667      *
6668      * @throws NullPointerException if text is null
6669      * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length
6670      *
6671      * @param text char array to be displayed
6672      * @param start start index in the char array
6673      * @param len length of char count after {@code start}
6674      */
setText( char[] text, int start, int len)6675     public final void setText(/* @NonNull */ char[] text, int start, int len) {
6676         int oldlen = 0;
6677 
6678         if (start < 0 || len < 0 || start + len > text.length) {
6679             throw new IndexOutOfBoundsException(start + ", " + len);
6680         }
6681 
6682         /*
6683          * We must do the before-notification here ourselves because if
6684          * the old text is a CharWrapper we destroy it before calling
6685          * into the normal path.
6686          */
6687         if (mText != null) {
6688             oldlen = mText.length();
6689             sendBeforeTextChanged(mText, 0, oldlen, len);
6690         } else {
6691             sendBeforeTextChanged("", 0, 0, len);
6692         }
6693 
6694         if (mCharWrapper == null) {
6695             mCharWrapper = new CharWrapper(text, start, len);
6696         } else {
6697             mCharWrapper.set(text, start, len);
6698         }
6699 
6700         setText(mCharWrapper, mBufferType, false, oldlen);
6701     }
6702 
6703     /**
6704      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
6705      * the cursor position. Same as
6706      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
6707      * position (if any) is retained in the new text.
6708      * <p/>
6709      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6710      * intermediate {@link Spannable Spannables}. Likewise it will use
6711      * {@link android.text.Editable.Factory} to create final or intermediate
6712      * {@link Editable Editables}.
6713      *
6714      * @param text text to be displayed
6715      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6716      *              stored as a static text, styleable/spannable text, or editable text
6717      *
6718      * @see #setText(CharSequence, android.widget.TextView.BufferType)
6719      */
setTextKeepState(CharSequence text, BufferType type)6720     public final void setTextKeepState(CharSequence text, BufferType type) {
6721         int start = getSelectionStart();
6722         int end = getSelectionEnd();
6723         int len = text.length();
6724 
6725         setText(text, type);
6726 
6727         if (start >= 0 || end >= 0) {
6728             if (mSpannable != null) {
6729                 Selection.setSelection(mSpannable,
6730                                        Math.max(0, Math.min(start, len)),
6731                                        Math.max(0, Math.min(end, len)));
6732             }
6733         }
6734     }
6735 
6736     /**
6737      * Sets the text to be displayed using a string resource identifier.
6738      *
6739      * @param resid the resource identifier of the string resource to be displayed
6740      *
6741      * @see #setText(CharSequence)
6742      *
6743      * @attr ref android.R.styleable#TextView_text
6744      */
6745     @android.view.RemotableViewMethod
setText(@tringRes int resid)6746     public final void setText(@StringRes int resid) {
6747         setText(getContext().getResources().getText(resid));
6748         mTextSetFromXmlOrResourceId = true;
6749         mTextId = resid;
6750     }
6751 
6752     /**
6753      * Sets the text to be displayed using a string resource identifier and the
6754      * {@link android.widget.TextView.BufferType}.
6755      * <p/>
6756      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6757      * intermediate {@link Spannable Spannables}. Likewise it will use
6758      * {@link android.text.Editable.Factory} to create final or intermediate
6759      * {@link Editable Editables}.
6760      *
6761      * @param resid the resource identifier of the string resource to be displayed
6762      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6763      *              stored as a static text, styleable/spannable text, or editable text
6764      *
6765      * @see #setText(int)
6766      * @see #setText(CharSequence)
6767      * @see android.widget.TextView.BufferType
6768      * @see #setSpannableFactory(Spannable.Factory)
6769      * @see #setEditableFactory(Editable.Factory)
6770      *
6771      * @attr ref android.R.styleable#TextView_text
6772      * @attr ref android.R.styleable#TextView_bufferType
6773      */
setText(@tringRes int resid, BufferType type)6774     public final void setText(@StringRes int resid, BufferType type) {
6775         setText(getContext().getResources().getText(resid), type);
6776         mTextSetFromXmlOrResourceId = true;
6777         mTextId = resid;
6778     }
6779 
6780     /**
6781      * Sets the text to be displayed when the text of the TextView is empty.
6782      * Null means to use the normal empty text. The hint does not currently
6783      * participate in determining the size of the view.
6784      *
6785      * @attr ref android.R.styleable#TextView_hint
6786      */
6787     @android.view.RemotableViewMethod
setHint(CharSequence hint)6788     public final void setHint(CharSequence hint) {
6789         setHintInternal(hint);
6790 
6791         if (mEditor != null && isInputMethodTarget()) {
6792             mEditor.reportExtractedText();
6793         }
6794     }
6795 
setHintInternal(CharSequence hint)6796     private void setHintInternal(CharSequence hint) {
6797         mHint = TextUtils.stringOrSpannedString(hint);
6798 
6799         if (mLayout != null) {
6800             checkForRelayout();
6801         }
6802 
6803         if (mText.length() == 0) {
6804             invalidate();
6805         }
6806 
6807         // Invalidate display list if hint is currently used
6808         if (mEditor != null && mText.length() == 0 && mHint != null) {
6809             mEditor.invalidateTextDisplayList();
6810         }
6811     }
6812 
6813     /**
6814      * Sets the text to be displayed when the text of the TextView is empty,
6815      * from a resource.
6816      *
6817      * @attr ref android.R.styleable#TextView_hint
6818      */
6819     @android.view.RemotableViewMethod
setHint(@tringRes int resid)6820     public final void setHint(@StringRes int resid) {
6821         mHintId = resid;
6822         setHint(getContext().getResources().getText(resid));
6823     }
6824 
6825     /**
6826      * Returns the hint that is displayed when the text of the TextView
6827      * is empty.
6828      *
6829      * @attr ref android.R.styleable#TextView_hint
6830      */
6831     @InspectableProperty
6832     @ViewDebug.CapturedViewProperty
getHint()6833     public CharSequence getHint() {
6834         return mHint;
6835     }
6836 
6837     /**
6838      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
6839      * line characters instead of letting it wrap onto multiple lines.
6840      *
6841      * @attr ref android.R.styleable#TextView_singleLine
6842      */
6843     @InspectableProperty
isSingleLine()6844     public boolean isSingleLine() {
6845         return mSingleLine;
6846     }
6847 
isMultilineInputType(int type)6848     private static boolean isMultilineInputType(int type) {
6849         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
6850                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
6851     }
6852 
6853     /**
6854      * Removes the suggestion spans.
6855      */
removeSuggestionSpans(CharSequence text)6856     CharSequence removeSuggestionSpans(CharSequence text) {
6857         if (text instanceof Spanned) {
6858             Spannable spannable;
6859             if (text instanceof Spannable) {
6860                 spannable = (Spannable) text;
6861             } else {
6862                 spannable = mSpannableFactory.newSpannable(text);
6863             }
6864 
6865             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
6866             if (spans.length == 0) {
6867                 return text;
6868             } else {
6869                 text = spannable;
6870             }
6871 
6872             for (int i = 0; i < spans.length; i++) {
6873                 spannable.removeSpan(spans[i]);
6874             }
6875         }
6876         return text;
6877     }
6878 
6879     /**
6880      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
6881      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
6882      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
6883      * then a soft keyboard will not be displayed for this text view.
6884      *
6885      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
6886      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
6887      * type.
6888      *
6889      * @see #getInputType()
6890      * @see #setRawInputType(int)
6891      * @see android.text.InputType
6892      * @attr ref android.R.styleable#TextView_inputType
6893      */
setInputType(int type)6894     public void setInputType(int type) {
6895         final boolean wasPassword = isPasswordInputType(getInputType());
6896         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
6897         setInputType(type, false);
6898         final boolean isPassword = isPasswordInputType(type);
6899         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
6900         boolean forceUpdate = false;
6901         if (isPassword) {
6902             setTransformationMethod(PasswordTransformationMethod.getInstance());
6903             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6904                     Typeface.NORMAL, -1 /* weight, not specifeid */);
6905         } else if (isVisiblePassword) {
6906             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6907                 forceUpdate = true;
6908             }
6909             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6910                     Typeface.NORMAL, -1 /* weight, not specified */);
6911         } else if (wasPassword || wasVisiblePassword) {
6912             // not in password mode, clean up typeface and transformation
6913             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
6914                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
6915                     -1 /* weight, not specified */);
6916             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6917                 forceUpdate = true;
6918             }
6919         }
6920 
6921         boolean singleLine = !isMultilineInputType(type);
6922 
6923         // We need to update the single line mode if it has changed or we
6924         // were previously in password mode.
6925         if (mSingleLine != singleLine || forceUpdate) {
6926             // Change single line mode, but only change the transformation if
6927             // we are not in password mode.
6928             applySingleLine(singleLine, !isPassword, true, true);
6929         }
6930 
6931         if (!isSuggestionsEnabled()) {
6932             setTextInternal(removeSuggestionSpans(mText));
6933         }
6934 
6935         InputMethodManager imm = getInputMethodManager();
6936         if (imm != null) imm.restartInput(this);
6937     }
6938 
6939     /**
6940      * It would be better to rely on the input type for everything. A password inputType should have
6941      * a password transformation. We should hence use isPasswordInputType instead of this method.
6942      *
6943      * We should:
6944      * - Call setInputType in setKeyListener instead of changing the input type directly (which
6945      * would install the correct transformation).
6946      * - Refuse the installation of a non-password transformation in setTransformation if the input
6947      * type is password.
6948      *
6949      * However, this is like this for legacy reasons and we cannot break existing apps. This method
6950      * is useful since it matches what the user can see (obfuscated text or not).
6951      *
6952      * @return true if the current transformation method is of the password type.
6953      */
hasPasswordTransformationMethod()6954     boolean hasPasswordTransformationMethod() {
6955         return mTransformation instanceof PasswordTransformationMethod;
6956     }
6957 
6958     /**
6959      * Returns true if the current inputType is any type of password.
6960      *
6961      * @hide
6962      */
isAnyPasswordInputType()6963     public boolean isAnyPasswordInputType() {
6964         final int inputType = getInputType();
6965         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
6966     }
6967 
isPasswordInputType(int inputType)6968     static boolean isPasswordInputType(int inputType) {
6969         final int variation =
6970                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6971         return variation
6972                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
6973                 || variation
6974                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
6975                 || variation
6976                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
6977     }
6978 
isVisiblePasswordInputType(int inputType)6979     private static boolean isVisiblePasswordInputType(int inputType) {
6980         final int variation =
6981                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6982         return variation
6983                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
6984     }
6985 
6986     /**
6987      * Directly change the content type integer of the text view, without
6988      * modifying any other state.
6989      * @see #setInputType(int)
6990      * @see android.text.InputType
6991      * @attr ref android.R.styleable#TextView_inputType
6992      */
setRawInputType(int type)6993     public void setRawInputType(int type) {
6994         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
6995         createEditorIfNeeded();
6996         mEditor.mInputType = type;
6997     }
6998 
6999     @Override
getAutofillHints()7000     public String[] getAutofillHints() {
7001         String[] hints = super.getAutofillHints();
7002         if (isAnyPasswordInputType()) {
7003             if (!ArrayUtils.contains(hints, AUTOFILL_HINT_PASSWORD_AUTO)) {
7004                 hints = ArrayUtils.appendElement(String.class, hints,
7005                         AUTOFILL_HINT_PASSWORD_AUTO);
7006             }
7007         }
7008         return hints;
7009     }
7010 
7011     /**
7012      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
7013      *         a {@code Locale} object that can be used to customize key various listeners.
7014      * @see DateKeyListener#getInstance(Locale)
7015      * @see DateTimeKeyListener#getInstance(Locale)
7016      * @see DigitsKeyListener#getInstance(Locale)
7017      * @see TimeKeyListener#getInstance(Locale)
7018      */
7019     @Nullable
getCustomLocaleForKeyListenerOrNull()7020     private Locale getCustomLocaleForKeyListenerOrNull() {
7021         if (!mUseInternationalizedInput) {
7022             // If the application does not target O, stick to the previous behavior.
7023             return null;
7024         }
7025         final LocaleList locales = getImeHintLocales();
7026         if (locales == null) {
7027             // If the application does not explicitly specify IME hint locale, also stick to the
7028             // previous behavior.
7029             return null;
7030         }
7031         return locales.get(0);
7032     }
7033 
7034     @UnsupportedAppUsage
setInputType(int type, boolean direct)7035     private void setInputType(int type, boolean direct) {
7036         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
7037         KeyListener input;
7038         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
7039             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
7040             TextKeyListener.Capitalize cap;
7041             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
7042                 cap = TextKeyListener.Capitalize.CHARACTERS;
7043             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
7044                 cap = TextKeyListener.Capitalize.WORDS;
7045             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
7046                 cap = TextKeyListener.Capitalize.SENTENCES;
7047             } else {
7048                 cap = TextKeyListener.Capitalize.NONE;
7049             }
7050             input = TextKeyListener.getInstance(autotext, cap);
7051         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
7052             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7053             input = DigitsKeyListener.getInstance(
7054                     locale,
7055                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
7056                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
7057             if (locale != null) {
7058                 // Override type, if necessary for i18n.
7059                 int newType = input.getInputType();
7060                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
7061                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
7062                     // The class is different from the original class. So we need to override
7063                     // 'type'. But we want to keep the password flag if it's there.
7064                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
7065                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
7066                     }
7067                     type = newType;
7068                 }
7069             }
7070         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
7071             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7072             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
7073                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
7074                     input = DateKeyListener.getInstance(locale);
7075                     break;
7076                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
7077                     input = TimeKeyListener.getInstance(locale);
7078                     break;
7079                 default:
7080                     input = DateTimeKeyListener.getInstance(locale);
7081                     break;
7082             }
7083             if (mUseInternationalizedInput) {
7084                 type = input.getInputType(); // Override type, if necessary for i18n.
7085             }
7086         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
7087             input = DialerKeyListener.getInstance();
7088         } else {
7089             input = TextKeyListener.getInstance();
7090         }
7091         setRawInputType(type);
7092         mListenerChanged = false;
7093         if (direct) {
7094             createEditorIfNeeded();
7095             mEditor.mKeyListener = input;
7096         } else {
7097             setKeyListenerOnly(input);
7098         }
7099     }
7100 
7101     /**
7102      * Get the type of the editable content.
7103      *
7104      * @see #setInputType(int)
7105      * @see android.text.InputType
7106      */
7107     @InspectableProperty(flagMapping = {
7108             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
7109             @FlagEntry(
7110                     name = "text",
7111                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7112                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
7113             @FlagEntry(
7114                     name = "textUri",
7115                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7116                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
7117             @FlagEntry(
7118                     name = "textEmailAddress",
7119                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7120                     target = InputType.TYPE_CLASS_TEXT
7121                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
7122             @FlagEntry(
7123                     name = "textEmailSubject",
7124                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7125                     target = InputType.TYPE_CLASS_TEXT
7126                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
7127             @FlagEntry(
7128                     name = "textShortMessage",
7129                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7130                     target = InputType.TYPE_CLASS_TEXT
7131                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
7132             @FlagEntry(
7133                     name = "textLongMessage",
7134                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7135                     target = InputType.TYPE_CLASS_TEXT
7136                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
7137             @FlagEntry(
7138                     name = "textPersonName",
7139                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7140                     target = InputType.TYPE_CLASS_TEXT
7141                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
7142             @FlagEntry(
7143                     name = "textPostalAddress",
7144                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7145                     target = InputType.TYPE_CLASS_TEXT
7146                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
7147             @FlagEntry(
7148                     name = "textPassword",
7149                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7150                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
7151             @FlagEntry(
7152                     name = "textVisiblePassword",
7153                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7154                     target = InputType.TYPE_CLASS_TEXT
7155                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
7156             @FlagEntry(
7157                     name = "textWebEditText",
7158                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7159                     target = InputType.TYPE_CLASS_TEXT
7160                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
7161             @FlagEntry(
7162                     name = "textFilter",
7163                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7164                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
7165             @FlagEntry(
7166                     name = "textPhonetic",
7167                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7168                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
7169             @FlagEntry(
7170                     name = "textWebEmailAddress",
7171                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7172                     target = InputType.TYPE_CLASS_TEXT
7173                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
7174             @FlagEntry(
7175                     name = "textWebPassword",
7176                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7177                     target = InputType.TYPE_CLASS_TEXT
7178                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
7179             @FlagEntry(
7180                     name = "number",
7181                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7182                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
7183             @FlagEntry(
7184                     name = "numberPassword",
7185                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7186                     target = InputType.TYPE_CLASS_NUMBER
7187                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
7188             @FlagEntry(
7189                     name = "phone",
7190                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7191                     target = InputType.TYPE_CLASS_PHONE),
7192             @FlagEntry(
7193                     name = "datetime",
7194                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7195                     target = InputType.TYPE_CLASS_DATETIME
7196                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
7197             @FlagEntry(
7198                     name = "date",
7199                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7200                     target = InputType.TYPE_CLASS_DATETIME
7201                             | InputType.TYPE_DATETIME_VARIATION_DATE),
7202             @FlagEntry(
7203                     name = "time",
7204                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7205                     target = InputType.TYPE_CLASS_DATETIME
7206                             | InputType.TYPE_DATETIME_VARIATION_TIME),
7207             @FlagEntry(
7208                     name = "textCapCharacters",
7209                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7210                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
7211             @FlagEntry(
7212                     name = "textCapWords",
7213                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7214                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
7215             @FlagEntry(
7216                     name = "textCapSentences",
7217                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7218                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
7219             @FlagEntry(
7220                     name = "textAutoCorrect",
7221                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7222                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
7223             @FlagEntry(
7224                     name = "textAutoComplete",
7225                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7226                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
7227             @FlagEntry(
7228                     name = "textMultiLine",
7229                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7230                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
7231             @FlagEntry(
7232                     name = "textImeMultiLine",
7233                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7234                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
7235             @FlagEntry(
7236                     name = "textNoSuggestions",
7237                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7238                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
7239             @FlagEntry(
7240                     name = "numberSigned",
7241                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7242                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
7243             @FlagEntry(
7244                     name = "numberDecimal",
7245                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7246                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
7247     })
getInputType()7248     public int getInputType() {
7249         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
7250     }
7251 
7252     /**
7253      * Change the editor type integer associated with the text view, which
7254      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
7255      * when it has focus.
7256      * @see #getImeOptions
7257      * @see android.view.inputmethod.EditorInfo
7258      * @attr ref android.R.styleable#TextView_imeOptions
7259      */
setImeOptions(int imeOptions)7260     public void setImeOptions(int imeOptions) {
7261         createEditorIfNeeded();
7262         mEditor.createInputContentTypeIfNeeded();
7263         mEditor.mInputContentType.imeOptions = imeOptions;
7264     }
7265 
7266     /**
7267      * Get the type of the Input Method Editor (IME).
7268      * @return the type of the IME
7269      * @see #setImeOptions(int)
7270      * @see EditorInfo
7271      */
7272     @InspectableProperty(flagMapping = {
7273             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
7274             @FlagEntry(
7275                     name = "actionUnspecified",
7276                     mask = EditorInfo.IME_MASK_ACTION,
7277                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
7278             @FlagEntry(
7279                     name = "actionNone",
7280                     mask = EditorInfo.IME_MASK_ACTION,
7281                     target = EditorInfo.IME_ACTION_NONE),
7282             @FlagEntry(
7283                     name = "actionGo",
7284                     mask = EditorInfo.IME_MASK_ACTION,
7285                     target = EditorInfo.IME_ACTION_GO),
7286             @FlagEntry(
7287                     name = "actionSearch",
7288                     mask = EditorInfo.IME_MASK_ACTION,
7289                     target = EditorInfo.IME_ACTION_SEARCH),
7290             @FlagEntry(
7291                     name = "actionSend",
7292                     mask = EditorInfo.IME_MASK_ACTION,
7293                     target = EditorInfo.IME_ACTION_SEND),
7294             @FlagEntry(
7295                     name = "actionNext",
7296                     mask = EditorInfo.IME_MASK_ACTION,
7297                     target = EditorInfo.IME_ACTION_NEXT),
7298             @FlagEntry(
7299                     name = "actionDone",
7300                     mask = EditorInfo.IME_MASK_ACTION,
7301                     target = EditorInfo.IME_ACTION_DONE),
7302             @FlagEntry(
7303                     name = "actionPrevious",
7304                     mask = EditorInfo.IME_MASK_ACTION,
7305                     target = EditorInfo.IME_ACTION_PREVIOUS),
7306             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
7307             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
7308             @FlagEntry(
7309                     name = "flagNavigatePrevious",
7310                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
7311             @FlagEntry(
7312                     name = "flagNoAccessoryAction",
7313                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
7314             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
7315             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
7316             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
7317             @FlagEntry(
7318                     name = "flagNoPersonalizedLearning",
7319                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
7320     })
getImeOptions()7321     public int getImeOptions() {
7322         return mEditor != null && mEditor.mInputContentType != null
7323                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
7324     }
7325 
7326     /**
7327      * Change the custom IME action associated with the text view, which
7328      * will be reported to an IME with {@link EditorInfo#actionLabel}
7329      * and {@link EditorInfo#actionId} when it has focus.
7330      * @see #getImeActionLabel
7331      * @see #getImeActionId
7332      * @see android.view.inputmethod.EditorInfo
7333      * @attr ref android.R.styleable#TextView_imeActionLabel
7334      * @attr ref android.R.styleable#TextView_imeActionId
7335      */
setImeActionLabel(CharSequence label, int actionId)7336     public void setImeActionLabel(CharSequence label, int actionId) {
7337         createEditorIfNeeded();
7338         mEditor.createInputContentTypeIfNeeded();
7339         mEditor.mInputContentType.imeActionLabel = label;
7340         mEditor.mInputContentType.imeActionId = actionId;
7341     }
7342 
7343     /**
7344      * Get the IME action label previous set with {@link #setImeActionLabel}.
7345      *
7346      * @see #setImeActionLabel
7347      * @see android.view.inputmethod.EditorInfo
7348      */
7349     @InspectableProperty
getImeActionLabel()7350     public CharSequence getImeActionLabel() {
7351         return mEditor != null && mEditor.mInputContentType != null
7352                 ? mEditor.mInputContentType.imeActionLabel : null;
7353     }
7354 
7355     /**
7356      * Get the IME action ID previous set with {@link #setImeActionLabel}.
7357      *
7358      * @see #setImeActionLabel
7359      * @see android.view.inputmethod.EditorInfo
7360      */
7361     @InspectableProperty
getImeActionId()7362     public int getImeActionId() {
7363         return mEditor != null && mEditor.mInputContentType != null
7364                 ? mEditor.mInputContentType.imeActionId : 0;
7365     }
7366 
7367     /**
7368      * Set a special listener to be called when an action is performed
7369      * on the text view.  This will be called when the enter key is pressed,
7370      * or when an action supplied to the IME is selected by the user.  Setting
7371      * this means that the normal hard key event will not insert a newline
7372      * into the text view, even if it is multi-line; holding down the ALT
7373      * modifier will, however, allow the user to insert a newline character.
7374      */
setOnEditorActionListener(OnEditorActionListener l)7375     public void setOnEditorActionListener(OnEditorActionListener l) {
7376         createEditorIfNeeded();
7377         mEditor.createInputContentTypeIfNeeded();
7378         mEditor.mInputContentType.onEditorActionListener = l;
7379     }
7380 
7381     /**
7382      * Called when an attached input method calls
7383      * {@link InputConnection#performEditorAction(int)
7384      * InputConnection.performEditorAction()}
7385      * for this text view.  The default implementation will call your action
7386      * listener supplied to {@link #setOnEditorActionListener}, or perform
7387      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
7388      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
7389      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
7390      * EditorInfo.IME_ACTION_DONE}.
7391      *
7392      * <p>For backwards compatibility, if no IME options have been set and the
7393      * text view would not normally advance focus on enter, then
7394      * the NEXT and DONE actions received here will be turned into an enter
7395      * key down/up pair to go through the normal key handling.
7396      *
7397      * @param actionCode The code of the action being performed.
7398      *
7399      * @see #setOnEditorActionListener
7400      */
onEditorAction(int actionCode)7401     public void onEditorAction(int actionCode) {
7402         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
7403         if (ict != null) {
7404             if (ict.onEditorActionListener != null) {
7405                 if (ict.onEditorActionListener.onEditorAction(this,
7406                         actionCode, null)) {
7407                     return;
7408                 }
7409             }
7410 
7411             // This is the handling for some default action.
7412             // Note that for backwards compatibility we don't do this
7413             // default handling if explicit ime options have not been given,
7414             // instead turning this into the normal enter key codes that an
7415             // app may be expecting.
7416             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
7417                 View v = focusSearch(FOCUS_FORWARD);
7418                 if (v != null) {
7419                     if (!v.requestFocus(FOCUS_FORWARD)) {
7420                         throw new IllegalStateException("focus search returned a view "
7421                                 + "that wasn't able to take focus!");
7422                     }
7423                 }
7424                 return;
7425 
7426             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
7427                 View v = focusSearch(FOCUS_BACKWARD);
7428                 if (v != null) {
7429                     if (!v.requestFocus(FOCUS_BACKWARD)) {
7430                         throw new IllegalStateException("focus search returned a view "
7431                                 + "that wasn't able to take focus!");
7432                     }
7433                 }
7434                 return;
7435 
7436             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
7437                 InputMethodManager imm = getInputMethodManager();
7438                 if (imm != null && imm.isActive(this)) {
7439                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7440                 }
7441                 return;
7442             }
7443         }
7444 
7445         ViewRootImpl viewRootImpl = getViewRootImpl();
7446         if (viewRootImpl != null) {
7447             long eventTime = SystemClock.uptimeMillis();
7448             viewRootImpl.dispatchKeyFromIme(
7449                     new KeyEvent(eventTime, eventTime,
7450                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
7451                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7452                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7453                     | KeyEvent.FLAG_EDITOR_ACTION));
7454             viewRootImpl.dispatchKeyFromIme(
7455                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
7456                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
7457                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7458                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7459                     | KeyEvent.FLAG_EDITOR_ACTION));
7460         }
7461     }
7462 
7463     /**
7464      * Set the private content type of the text, which is the
7465      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
7466      * field that will be filled in when creating an input connection.
7467      *
7468      * @see #getPrivateImeOptions()
7469      * @see EditorInfo#privateImeOptions
7470      * @attr ref android.R.styleable#TextView_privateImeOptions
7471      */
setPrivateImeOptions(String type)7472     public void setPrivateImeOptions(String type) {
7473         createEditorIfNeeded();
7474         mEditor.createInputContentTypeIfNeeded();
7475         mEditor.mInputContentType.privateImeOptions = type;
7476     }
7477 
7478     /**
7479      * Get the private type of the content.
7480      *
7481      * @see #setPrivateImeOptions(String)
7482      * @see EditorInfo#privateImeOptions
7483      */
7484     @InspectableProperty
getPrivateImeOptions()7485     public String getPrivateImeOptions() {
7486         return mEditor != null && mEditor.mInputContentType != null
7487                 ? mEditor.mInputContentType.privateImeOptions : null;
7488     }
7489 
7490     /**
7491      * Set the extra input data of the text, which is the
7492      * {@link EditorInfo#extras TextBoxAttribute.extras}
7493      * Bundle that will be filled in when creating an input connection.  The
7494      * given integer is the resource identifier of an XML resource holding an
7495      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
7496      *
7497      * @see #getInputExtras(boolean)
7498      * @see EditorInfo#extras
7499      * @attr ref android.R.styleable#TextView_editorExtras
7500      */
setInputExtras(@mlRes int xmlResId)7501     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
7502         createEditorIfNeeded();
7503         XmlResourceParser parser = getResources().getXml(xmlResId);
7504         mEditor.createInputContentTypeIfNeeded();
7505         mEditor.mInputContentType.extras = new Bundle();
7506         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
7507     }
7508 
7509     /**
7510      * Retrieve the input extras currently associated with the text view, which
7511      * can be viewed as well as modified.
7512      *
7513      * @param create If true, the extras will be created if they don't already
7514      * exist.  Otherwise, null will be returned if none have been created.
7515      * @see #setInputExtras(int)
7516      * @see EditorInfo#extras
7517      * @attr ref android.R.styleable#TextView_editorExtras
7518      */
getInputExtras(boolean create)7519     public Bundle getInputExtras(boolean create) {
7520         if (mEditor == null && !create) return null;
7521         createEditorIfNeeded();
7522         if (mEditor.mInputContentType == null) {
7523             if (!create) return null;
7524             mEditor.createInputContentTypeIfNeeded();
7525         }
7526         if (mEditor.mInputContentType.extras == null) {
7527             if (!create) return null;
7528             mEditor.mInputContentType.extras = new Bundle();
7529         }
7530         return mEditor.mInputContentType.extras;
7531     }
7532 
7533     /**
7534      * Change "hint" locales associated with the text view, which will be reported to an IME with
7535      * {@link EditorInfo#hintLocales} when it has focus.
7536      *
7537      * Starting with Android O, this also causes internationalized listeners to be created (or
7538      * change locale) based on the first locale in the input locale list.
7539      *
7540      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
7541      * call {@link InputMethodManager#restartInput(View)}.</p>
7542      * @param hintLocales List of the languages that the user is supposed to switch to no matter
7543      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
7544      * @see #getImeHintLocales()
7545      * @see android.view.inputmethod.EditorInfo#hintLocales
7546      */
setImeHintLocales(@ullable LocaleList hintLocales)7547     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
7548         createEditorIfNeeded();
7549         mEditor.createInputContentTypeIfNeeded();
7550         mEditor.mInputContentType.imeHintLocales = hintLocales;
7551         if (mUseInternationalizedInput) {
7552             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
7553         }
7554     }
7555 
7556     /**
7557      * @return The current languages list "hint". {@code null} when no "hint" is available.
7558      * @see #setImeHintLocales(LocaleList)
7559      * @see android.view.inputmethod.EditorInfo#hintLocales
7560      */
7561     @Nullable
getImeHintLocales()7562     public LocaleList getImeHintLocales() {
7563         if (mEditor == null) {
7564             return null;
7565         }
7566         if (mEditor.mInputContentType == null) {
7567             return null;
7568         }
7569         return mEditor.mInputContentType.imeHintLocales;
7570     }
7571 
7572     /**
7573      * Returns the error message that was set to be displayed with
7574      * {@link #setError}, or <code>null</code> if no error was set
7575      * or if it the error was cleared by the widget after user input.
7576      */
getError()7577     public CharSequence getError() {
7578         return mEditor == null ? null : mEditor.mError;
7579     }
7580 
7581     /**
7582      * Sets the right-hand compound drawable of the TextView to the "error"
7583      * icon and sets an error message that will be displayed in a popup when
7584      * the TextView has focus.  The icon and error message will be reset to
7585      * null when any key events cause changes to the TextView's text.  If the
7586      * <code>error</code> is <code>null</code>, the error message and icon
7587      * will be cleared.
7588      */
7589     @android.view.RemotableViewMethod
setError(CharSequence error)7590     public void setError(CharSequence error) {
7591         if (error == null) {
7592             setError(null, null);
7593         } else {
7594             Drawable dr = getContext().getDrawable(
7595                     com.android.internal.R.drawable.indicator_input_error);
7596 
7597             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
7598             setError(error, dr);
7599         }
7600     }
7601 
7602     /**
7603      * Sets the right-hand compound drawable of the TextView to the specified
7604      * icon and sets an error message that will be displayed in a popup when
7605      * the TextView has focus.  The icon and error message will be reset to
7606      * null when any key events cause changes to the TextView's text.  The
7607      * drawable must already have had {@link Drawable#setBounds} set on it.
7608      * If the <code>error</code> is <code>null</code>, the error message will
7609      * be cleared (and you should provide a <code>null</code> icon as well).
7610      */
setError(CharSequence error, Drawable icon)7611     public void setError(CharSequence error, Drawable icon) {
7612         createEditorIfNeeded();
7613         mEditor.setError(error, icon);
7614         notifyViewAccessibilityStateChangedIfNeeded(
7615                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7616     }
7617 
7618     @Override
setFrame(int l, int t, int r, int b)7619     protected boolean setFrame(int l, int t, int r, int b) {
7620         boolean result = super.setFrame(l, t, r, b);
7621 
7622         if (mEditor != null) mEditor.setFrame();
7623 
7624         restartMarqueeIfNeeded();
7625 
7626         return result;
7627     }
7628 
restartMarqueeIfNeeded()7629     private void restartMarqueeIfNeeded() {
7630         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7631             mRestartMarquee = false;
7632             startMarquee();
7633         }
7634     }
7635 
7636     /**
7637      * Sets the list of input filters that will be used if the buffer is
7638      * Editable. Has no effect otherwise.
7639      *
7640      * @attr ref android.R.styleable#TextView_maxLength
7641      */
setFilters(InputFilter[] filters)7642     public void setFilters(InputFilter[] filters) {
7643         if (filters == null) {
7644             throw new IllegalArgumentException();
7645         }
7646 
7647         mFilters = filters;
7648 
7649         if (mText instanceof Editable) {
7650             setFilters((Editable) mText, filters);
7651         }
7652     }
7653 
7654     /**
7655      * Sets the list of input filters on the specified Editable,
7656      * and includes mInput in the list if it is an InputFilter.
7657      */
setFilters(Editable e, InputFilter[] filters)7658     private void setFilters(Editable e, InputFilter[] filters) {
7659         if (mEditor != null) {
7660             final boolean undoFilter = mEditor.mUndoInputFilter != null;
7661             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
7662             int num = 0;
7663             if (undoFilter) num++;
7664             if (keyFilter) num++;
7665             if (num > 0) {
7666                 InputFilter[] nf = new InputFilter[filters.length + num];
7667 
7668                 System.arraycopy(filters, 0, nf, 0, filters.length);
7669                 num = 0;
7670                 if (undoFilter) {
7671                     nf[filters.length] = mEditor.mUndoInputFilter;
7672                     num++;
7673                 }
7674                 if (keyFilter) {
7675                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
7676                 }
7677 
7678                 e.setFilters(nf);
7679                 return;
7680             }
7681         }
7682         e.setFilters(filters);
7683     }
7684 
7685     /**
7686      * Returns the current list of input filters.
7687      *
7688      * @attr ref android.R.styleable#TextView_maxLength
7689      */
getFilters()7690     public InputFilter[] getFilters() {
7691         return mFilters;
7692     }
7693 
7694     /////////////////////////////////////////////////////////////////////////
7695 
getBoxHeight(Layout l)7696     private int getBoxHeight(Layout l) {
7697         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
7698         int padding = (l == mHintLayout)
7699                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
7700                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
7701         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
7702     }
7703 
7704     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)7705     int getVerticalOffset(boolean forceNormal) {
7706         int voffset = 0;
7707         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7708 
7709         Layout l = mLayout;
7710         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7711             l = mHintLayout;
7712         }
7713 
7714         if (gravity != Gravity.TOP) {
7715             int boxht = getBoxHeight(l);
7716             int textht = l.getHeight();
7717 
7718             if (textht < boxht) {
7719                 if (gravity == Gravity.BOTTOM) {
7720                     voffset = boxht - textht;
7721                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7722                     voffset = (boxht - textht) >> 1;
7723                 }
7724             }
7725         }
7726         return voffset;
7727     }
7728 
getBottomVerticalOffset(boolean forceNormal)7729     private int getBottomVerticalOffset(boolean forceNormal) {
7730         int voffset = 0;
7731         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7732 
7733         Layout l = mLayout;
7734         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7735             l = mHintLayout;
7736         }
7737 
7738         if (gravity != Gravity.BOTTOM) {
7739             int boxht = getBoxHeight(l);
7740             int textht = l.getHeight();
7741 
7742             if (textht < boxht) {
7743                 if (gravity == Gravity.TOP) {
7744                     voffset = boxht - textht;
7745                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7746                     voffset = (boxht - textht) >> 1;
7747                 }
7748             }
7749         }
7750         return voffset;
7751     }
7752 
invalidateCursorPath()7753     void invalidateCursorPath() {
7754         if (mHighlightPathBogus) {
7755             invalidateCursor();
7756         } else {
7757             final int horizontalPadding = getCompoundPaddingLeft();
7758             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7759 
7760             if (mEditor.mDrawableForCursor == null) {
7761                 synchronized (TEMP_RECTF) {
7762                     /*
7763                      * The reason for this concern about the thickness of the
7764                      * cursor and doing the floor/ceil on the coordinates is that
7765                      * some EditTexts (notably textfields in the Browser) have
7766                      * anti-aliased text where not all the characters are
7767                      * necessarily at integer-multiple locations.  This should
7768                      * make sure the entire cursor gets invalidated instead of
7769                      * sometimes missing half a pixel.
7770                      */
7771                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
7772                     if (thick < 1.0f) {
7773                         thick = 1.0f;
7774                     }
7775 
7776                     thick /= 2.0f;
7777 
7778                     // mHighlightPath is guaranteed to be non null at that point.
7779                     mHighlightPath.computeBounds(TEMP_RECTF, false);
7780 
7781                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
7782                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
7783                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
7784                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
7785                 }
7786             } else {
7787                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7788                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
7789                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
7790             }
7791         }
7792     }
7793 
invalidateCursor()7794     void invalidateCursor() {
7795         int where = getSelectionEnd();
7796 
7797         invalidateCursor(where, where, where);
7798     }
7799 
invalidateCursor(int a, int b, int c)7800     private void invalidateCursor(int a, int b, int c) {
7801         if (a >= 0 || b >= 0 || c >= 0) {
7802             int start = Math.min(Math.min(a, b), c);
7803             int end = Math.max(Math.max(a, b), c);
7804             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
7805         }
7806     }
7807 
7808     /**
7809      * Invalidates the region of text enclosed between the start and end text offsets.
7810      */
invalidateRegion(int start, int end, boolean invalidateCursor)7811     void invalidateRegion(int start, int end, boolean invalidateCursor) {
7812         if (mLayout == null) {
7813             invalidate();
7814         } else {
7815             int lineStart = mLayout.getLineForOffset(start);
7816             int top = mLayout.getLineTop(lineStart);
7817 
7818             // This is ridiculous, but the descent from the line above
7819             // can hang down into the line we really want to redraw,
7820             // so we have to invalidate part of the line above to make
7821             // sure everything that needs to be redrawn really is.
7822             // (But not the whole line above, because that would cause
7823             // the same problem with the descenders on the line above it!)
7824             if (lineStart > 0) {
7825                 top -= mLayout.getLineDescent(lineStart - 1);
7826             }
7827 
7828             int lineEnd;
7829 
7830             if (start == end) {
7831                 lineEnd = lineStart;
7832             } else {
7833                 lineEnd = mLayout.getLineForOffset(end);
7834             }
7835 
7836             int bottom = mLayout.getLineBottom(lineEnd);
7837 
7838             // mEditor can be null in case selection is set programmatically.
7839             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
7840                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7841                 top = Math.min(top, bounds.top);
7842                 bottom = Math.max(bottom, bounds.bottom);
7843             }
7844 
7845             final int compoundPaddingLeft = getCompoundPaddingLeft();
7846             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7847 
7848             int left, right;
7849             if (lineStart == lineEnd && !invalidateCursor) {
7850                 left = (int) mLayout.getPrimaryHorizontal(start);
7851                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
7852                 left += compoundPaddingLeft;
7853                 right += compoundPaddingLeft;
7854             } else {
7855                 // Rectangle bounding box when the region spans several lines
7856                 left = compoundPaddingLeft;
7857                 right = getWidth() - getCompoundPaddingRight();
7858             }
7859 
7860             invalidate(mScrollX + left, verticalPadding + top,
7861                     mScrollX + right, verticalPadding + bottom);
7862         }
7863     }
7864 
registerForPreDraw()7865     private void registerForPreDraw() {
7866         if (!mPreDrawRegistered) {
7867             getViewTreeObserver().addOnPreDrawListener(this);
7868             mPreDrawRegistered = true;
7869         }
7870     }
7871 
unregisterForPreDraw()7872     private void unregisterForPreDraw() {
7873         getViewTreeObserver().removeOnPreDrawListener(this);
7874         mPreDrawRegistered = false;
7875         mPreDrawListenerDetached = false;
7876     }
7877 
7878     /**
7879      * {@inheritDoc}
7880      */
7881     @Override
onPreDraw()7882     public boolean onPreDraw() {
7883         if (mLayout == null) {
7884             assumeLayout();
7885         }
7886 
7887         if (mMovement != null) {
7888             /* This code also provides auto-scrolling when a cursor is moved using a
7889              * CursorController (insertion point or selection limits).
7890              * For selection, ensure start or end is visible depending on controller's state.
7891              */
7892             int curs = getSelectionEnd();
7893             // Do not create the controller if it is not already created.
7894             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
7895                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
7896                 curs = getSelectionStart();
7897             }
7898 
7899             /*
7900              * TODO: This should really only keep the end in view if
7901              * it already was before the text changed.  I'm not sure
7902              * of a good way to tell from here if it was.
7903              */
7904             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7905                 curs = mText.length();
7906             }
7907 
7908             if (curs >= 0) {
7909                 bringPointIntoView(curs);
7910             }
7911         } else {
7912             bringTextIntoView();
7913         }
7914 
7915         // This has to be checked here since:
7916         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
7917         //   a screen rotation) since layout is not yet initialized at that point.
7918         if (mEditor != null && mEditor.mCreatedWithASelection) {
7919             mEditor.refreshTextActionMode();
7920             mEditor.mCreatedWithASelection = false;
7921         }
7922 
7923         unregisterForPreDraw();
7924 
7925         return true;
7926     }
7927 
7928     @Override
onAttachedToWindow()7929     protected void onAttachedToWindow() {
7930         super.onAttachedToWindow();
7931 
7932         if (mEditor != null) mEditor.onAttachedToWindow();
7933 
7934         if (mPreDrawListenerDetached) {
7935             getViewTreeObserver().addOnPreDrawListener(this);
7936             mPreDrawListenerDetached = false;
7937         }
7938     }
7939 
7940     /** @hide */
7941     @Override
onDetachedFromWindowInternal()7942     protected void onDetachedFromWindowInternal() {
7943         if (mPreDrawRegistered) {
7944             getViewTreeObserver().removeOnPreDrawListener(this);
7945             mPreDrawListenerDetached = true;
7946         }
7947 
7948         resetResolvedDrawables();
7949 
7950         if (mEditor != null) mEditor.onDetachedFromWindow();
7951 
7952         super.onDetachedFromWindowInternal();
7953     }
7954 
7955     @Override
onScreenStateChanged(int screenState)7956     public void onScreenStateChanged(int screenState) {
7957         super.onScreenStateChanged(screenState);
7958         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
7959     }
7960 
7961     @Override
isPaddingOffsetRequired()7962     protected boolean isPaddingOffsetRequired() {
7963         return mShadowRadius != 0 || mDrawables != null;
7964     }
7965 
7966     @Override
getLeftPaddingOffset()7967     protected int getLeftPaddingOffset() {
7968         return getCompoundPaddingLeft() - mPaddingLeft
7969                 + (int) Math.min(0, mShadowDx - mShadowRadius);
7970     }
7971 
7972     @Override
getTopPaddingOffset()7973     protected int getTopPaddingOffset() {
7974         return (int) Math.min(0, mShadowDy - mShadowRadius);
7975     }
7976 
7977     @Override
getBottomPaddingOffset()7978     protected int getBottomPaddingOffset() {
7979         return (int) Math.max(0, mShadowDy + mShadowRadius);
7980     }
7981 
7982     @Override
getRightPaddingOffset()7983     protected int getRightPaddingOffset() {
7984         return -(getCompoundPaddingRight() - mPaddingRight)
7985                 + (int) Math.max(0, mShadowDx + mShadowRadius);
7986     }
7987 
7988     @Override
verifyDrawable(@onNull Drawable who)7989     protected boolean verifyDrawable(@NonNull Drawable who) {
7990         final boolean verified = super.verifyDrawable(who);
7991         if (!verified && mDrawables != null) {
7992             for (Drawable dr : mDrawables.mShowing) {
7993                 if (who == dr) {
7994                     return true;
7995                 }
7996             }
7997         }
7998         return verified;
7999     }
8000 
8001     @Override
jumpDrawablesToCurrentState()8002     public void jumpDrawablesToCurrentState() {
8003         super.jumpDrawablesToCurrentState();
8004         if (mDrawables != null) {
8005             for (Drawable dr : mDrawables.mShowing) {
8006                 if (dr != null) {
8007                     dr.jumpToCurrentState();
8008                 }
8009             }
8010         }
8011     }
8012 
8013     @Override
invalidateDrawable(@onNull Drawable drawable)8014     public void invalidateDrawable(@NonNull Drawable drawable) {
8015         boolean handled = false;
8016 
8017         if (verifyDrawable(drawable)) {
8018             final Rect dirty = drawable.getBounds();
8019             int scrollX = mScrollX;
8020             int scrollY = mScrollY;
8021 
8022             // IMPORTANT: The coordinates below are based on the coordinates computed
8023             // for each compound drawable in onDraw(). Make sure to update each section
8024             // accordingly.
8025             final TextView.Drawables drawables = mDrawables;
8026             if (drawables != null) {
8027                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
8028                     final int compoundPaddingTop = getCompoundPaddingTop();
8029                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8030                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8031 
8032                     scrollX += mPaddingLeft;
8033                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
8034                     handled = true;
8035                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
8036                     final int compoundPaddingTop = getCompoundPaddingTop();
8037                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8038                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8039 
8040                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
8041                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
8042                     handled = true;
8043                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
8044                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8045                     final int compoundPaddingRight = getCompoundPaddingRight();
8046                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8047 
8048                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
8049                     scrollY += mPaddingTop;
8050                     handled = true;
8051                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
8052                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8053                     final int compoundPaddingRight = getCompoundPaddingRight();
8054                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8055 
8056                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
8057                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
8058                     handled = true;
8059                 }
8060             }
8061 
8062             if (handled) {
8063                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
8064                         dirty.right + scrollX, dirty.bottom + scrollY);
8065             }
8066         }
8067 
8068         if (!handled) {
8069             super.invalidateDrawable(drawable);
8070         }
8071     }
8072 
8073     @Override
hasOverlappingRendering()8074     public boolean hasOverlappingRendering() {
8075         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
8076         return ((getBackground() != null && getBackground().getCurrent() != null)
8077                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
8078                 || mShadowColor != 0);
8079     }
8080 
8081     /**
8082      *
8083      * Returns the state of the {@code textIsSelectable} flag (See
8084      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
8085      * to allow users to select and copy text in a non-editable TextView, the content of an
8086      * {@link EditText} can always be selected, independently of the value of this flag.
8087      * <p>
8088      *
8089      * @return True if the text displayed in this TextView can be selected by the user.
8090      *
8091      * @attr ref android.R.styleable#TextView_textIsSelectable
8092      */
8093     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()8094     public boolean isTextSelectable() {
8095         return mEditor == null ? false : mEditor.mTextIsSelectable;
8096     }
8097 
8098     /**
8099      * Sets whether the content of this view is selectable by the user. The default is
8100      * {@code false}, meaning that the content is not selectable.
8101      * <p>
8102      * When you use a TextView to display a useful piece of information to the user (such as a
8103      * contact's address), make it selectable, so that the user can select and copy its
8104      * content. You can also use set the XML attribute
8105      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
8106      * <p>
8107      * When you call this method to set the value of {@code textIsSelectable}, it sets
8108      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
8109      * and {@code longClickable} to the same value. These flags correspond to the attributes
8110      * {@link android.R.styleable#View_focusable android:focusable},
8111      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
8112      * {@link android.R.styleable#View_clickable android:clickable}, and
8113      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
8114      * flags to a state you had set previously, call one or more of the following methods:
8115      * {@link #setFocusable(boolean) setFocusable()},
8116      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
8117      * {@link #setClickable(boolean) setClickable()} or
8118      * {@link #setLongClickable(boolean) setLongClickable()}.
8119      *
8120      * @param selectable Whether the content of this TextView should be selectable.
8121      */
setTextIsSelectable(boolean selectable)8122     public void setTextIsSelectable(boolean selectable) {
8123         if (!selectable && mEditor == null) return; // false is default value with no edit data
8124 
8125         createEditorIfNeeded();
8126         if (mEditor.mTextIsSelectable == selectable) return;
8127 
8128         mEditor.mTextIsSelectable = selectable;
8129         setFocusableInTouchMode(selectable);
8130         setFocusable(FOCUSABLE_AUTO);
8131         setClickable(selectable);
8132         setLongClickable(selectable);
8133 
8134         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
8135 
8136         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
8137         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
8138 
8139         // Called by setText above, but safer in case of future code changes
8140         mEditor.prepareCursorControllers();
8141     }
8142 
8143     @Override
onCreateDrawableState(int extraSpace)8144     protected int[] onCreateDrawableState(int extraSpace) {
8145         final int[] drawableState;
8146 
8147         if (mSingleLine) {
8148             drawableState = super.onCreateDrawableState(extraSpace);
8149         } else {
8150             drawableState = super.onCreateDrawableState(extraSpace + 1);
8151             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
8152         }
8153 
8154         if (isTextSelectable()) {
8155             // Disable pressed state, which was introduced when TextView was made clickable.
8156             // Prevents text color change.
8157             // setClickable(false) would have a similar effect, but it also disables focus changes
8158             // and long press actions, which are both needed by text selection.
8159             final int length = drawableState.length;
8160             for (int i = 0; i < length; i++) {
8161                 if (drawableState[i] == R.attr.state_pressed) {
8162                     final int[] nonPressedState = new int[length - 1];
8163                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
8164                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
8165                     return nonPressedState;
8166                 }
8167             }
8168         }
8169 
8170         return drawableState;
8171     }
8172 
8173     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getUpdatedHighlightPath()8174     private Path getUpdatedHighlightPath() {
8175         Path highlight = null;
8176         Paint highlightPaint = mHighlightPaint;
8177 
8178         final int selStart = getSelectionStart();
8179         final int selEnd = getSelectionEnd();
8180         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
8181             if (selStart == selEnd) {
8182                 if (mEditor != null && mEditor.shouldRenderCursor()) {
8183                     if (mHighlightPathBogus) {
8184                         if (mHighlightPath == null) mHighlightPath = new Path();
8185                         mHighlightPath.reset();
8186                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
8187                         mEditor.updateCursorPosition();
8188                         mHighlightPathBogus = false;
8189                     }
8190 
8191                     // XXX should pass to skin instead of drawing directly
8192                     highlightPaint.setColor(mCurTextColor);
8193                     highlightPaint.setStyle(Paint.Style.STROKE);
8194                     highlight = mHighlightPath;
8195                 }
8196             } else {
8197                 if (mHighlightPathBogus) {
8198                     if (mHighlightPath == null) mHighlightPath = new Path();
8199                     mHighlightPath.reset();
8200                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8201                     mHighlightPathBogus = false;
8202                 }
8203 
8204                 // XXX should pass to skin instead of drawing directly
8205                 highlightPaint.setColor(mHighlightColor);
8206                 highlightPaint.setStyle(Paint.Style.FILL);
8207 
8208                 highlight = mHighlightPath;
8209             }
8210         }
8211         return highlight;
8212     }
8213 
8214     /**
8215      * @hide
8216      */
getHorizontalOffsetForDrawables()8217     public int getHorizontalOffsetForDrawables() {
8218         return 0;
8219     }
8220 
8221     @Override
onDraw(Canvas canvas)8222     protected void onDraw(Canvas canvas) {
8223         restartMarqueeIfNeeded();
8224 
8225         // Draw the background for this view
8226         super.onDraw(canvas);
8227 
8228         final int compoundPaddingLeft = getCompoundPaddingLeft();
8229         final int compoundPaddingTop = getCompoundPaddingTop();
8230         final int compoundPaddingRight = getCompoundPaddingRight();
8231         final int compoundPaddingBottom = getCompoundPaddingBottom();
8232         final int scrollX = mScrollX;
8233         final int scrollY = mScrollY;
8234         final int right = mRight;
8235         final int left = mLeft;
8236         final int bottom = mBottom;
8237         final int top = mTop;
8238         final boolean isLayoutRtl = isLayoutRtl();
8239         final int offset = getHorizontalOffsetForDrawables();
8240         final int leftOffset = isLayoutRtl ? 0 : offset;
8241         final int rightOffset = isLayoutRtl ? offset : 0;
8242 
8243         final Drawables dr = mDrawables;
8244         if (dr != null) {
8245             /*
8246              * Compound, not extended, because the icon is not clipped
8247              * if the text height is smaller.
8248              */
8249 
8250             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
8251             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
8252 
8253             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8254             // Make sure to update invalidateDrawable() when changing this code.
8255             if (dr.mShowing[Drawables.LEFT] != null) {
8256                 canvas.save();
8257                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
8258                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
8259                 dr.mShowing[Drawables.LEFT].draw(canvas);
8260                 canvas.restore();
8261             }
8262 
8263             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8264             // Make sure to update invalidateDrawable() when changing this code.
8265             if (dr.mShowing[Drawables.RIGHT] != null) {
8266                 canvas.save();
8267                 canvas.translate(scrollX + right - left - mPaddingRight
8268                         - dr.mDrawableSizeRight - rightOffset,
8269                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
8270                 dr.mShowing[Drawables.RIGHT].draw(canvas);
8271                 canvas.restore();
8272             }
8273 
8274             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8275             // Make sure to update invalidateDrawable() when changing this code.
8276             if (dr.mShowing[Drawables.TOP] != null) {
8277                 canvas.save();
8278                 canvas.translate(scrollX + compoundPaddingLeft
8279                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
8280                 dr.mShowing[Drawables.TOP].draw(canvas);
8281                 canvas.restore();
8282             }
8283 
8284             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8285             // Make sure to update invalidateDrawable() when changing this code.
8286             if (dr.mShowing[Drawables.BOTTOM] != null) {
8287                 canvas.save();
8288                 canvas.translate(scrollX + compoundPaddingLeft
8289                         + (hspace - dr.mDrawableWidthBottom) / 2,
8290                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
8291                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
8292                 canvas.restore();
8293             }
8294         }
8295 
8296         int color = mCurTextColor;
8297 
8298         if (mLayout == null) {
8299             assumeLayout();
8300         }
8301 
8302         Layout layout = mLayout;
8303 
8304         if (mHint != null && mText.length() == 0) {
8305             if (mHintTextColor != null) {
8306                 color = mCurHintTextColor;
8307             }
8308 
8309             layout = mHintLayout;
8310         }
8311 
8312         mTextPaint.setColor(color);
8313         mTextPaint.drawableState = getDrawableState();
8314 
8315         canvas.save();
8316         /*  Would be faster if we didn't have to do this. Can we chop the
8317             (displayable) text so that we don't need to do this ever?
8318         */
8319 
8320         int extendedPaddingTop = getExtendedPaddingTop();
8321         int extendedPaddingBottom = getExtendedPaddingBottom();
8322 
8323         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8324         final int maxScrollY = mLayout.getHeight() - vspace;
8325 
8326         float clipLeft = compoundPaddingLeft + scrollX;
8327         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
8328         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
8329         float clipBottom = bottom - top + scrollY
8330                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
8331 
8332         if (mShadowRadius != 0) {
8333             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
8334             clipRight += Math.max(0, mShadowDx + mShadowRadius);
8335 
8336             clipTop += Math.min(0, mShadowDy - mShadowRadius);
8337             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
8338         }
8339 
8340         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
8341 
8342         int voffsetText = 0;
8343         int voffsetCursor = 0;
8344 
8345         // translate in by our padding
8346         /* shortcircuit calling getVerticaOffset() */
8347         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8348             voffsetText = getVerticalOffset(false);
8349             voffsetCursor = getVerticalOffset(true);
8350         }
8351         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
8352 
8353         final int layoutDirection = getLayoutDirection();
8354         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8355         if (isMarqueeFadeEnabled()) {
8356             if (!mSingleLine && getLineCount() == 1 && canMarquee()
8357                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
8358                 final int width = mRight - mLeft;
8359                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
8360                 final float dx = mLayout.getLineRight(0) - (width - padding);
8361                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8362             }
8363 
8364             if (mMarquee != null && mMarquee.isRunning()) {
8365                 final float dx = -mMarquee.getScroll();
8366                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8367             }
8368         }
8369 
8370         final int cursorOffsetVertical = voffsetCursor - voffsetText;
8371 
8372         Path highlight = getUpdatedHighlightPath();
8373         if (mEditor != null) {
8374             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
8375         } else {
8376             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
8377         }
8378 
8379         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
8380             final float dx = mMarquee.getGhostOffset();
8381             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
8382             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
8383         }
8384 
8385         canvas.restore();
8386     }
8387 
8388     @Override
getFocusedRect(Rect r)8389     public void getFocusedRect(Rect r) {
8390         if (mLayout == null) {
8391             super.getFocusedRect(r);
8392             return;
8393         }
8394 
8395         int selEnd = getSelectionEnd();
8396         if (selEnd < 0) {
8397             super.getFocusedRect(r);
8398             return;
8399         }
8400 
8401         int selStart = getSelectionStart();
8402         if (selStart < 0 || selStart >= selEnd) {
8403             int line = mLayout.getLineForOffset(selEnd);
8404             r.top = mLayout.getLineTop(line);
8405             r.bottom = mLayout.getLineBottom(line);
8406             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
8407             r.right = r.left + 4;
8408         } else {
8409             int lineStart = mLayout.getLineForOffset(selStart);
8410             int lineEnd = mLayout.getLineForOffset(selEnd);
8411             r.top = mLayout.getLineTop(lineStart);
8412             r.bottom = mLayout.getLineBottom(lineEnd);
8413             if (lineStart == lineEnd) {
8414                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
8415                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
8416             } else {
8417                 // Selection extends across multiple lines -- make the focused
8418                 // rect cover the entire width.
8419                 if (mHighlightPathBogus) {
8420                     if (mHighlightPath == null) mHighlightPath = new Path();
8421                     mHighlightPath.reset();
8422                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8423                     mHighlightPathBogus = false;
8424                 }
8425                 synchronized (TEMP_RECTF) {
8426                     mHighlightPath.computeBounds(TEMP_RECTF, true);
8427                     r.left = (int) TEMP_RECTF.left - 1;
8428                     r.right = (int) TEMP_RECTF.right + 1;
8429                 }
8430             }
8431         }
8432 
8433         // Adjust for padding and gravity.
8434         int paddingLeft = getCompoundPaddingLeft();
8435         int paddingTop = getExtendedPaddingTop();
8436         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8437             paddingTop += getVerticalOffset(false);
8438         }
8439         r.offset(paddingLeft, paddingTop);
8440         int paddingBottom = getExtendedPaddingBottom();
8441         r.bottom += paddingBottom;
8442     }
8443 
8444     /**
8445      * Return the number of lines of text, or 0 if the internal Layout has not
8446      * been built.
8447      */
getLineCount()8448     public int getLineCount() {
8449         return mLayout != null ? mLayout.getLineCount() : 0;
8450     }
8451 
8452     /**
8453      * Return the baseline for the specified line (0...getLineCount() - 1)
8454      * If bounds is not null, return the top, left, right, bottom extents
8455      * of the specified line in it. If the internal Layout has not been built,
8456      * return 0 and set bounds to (0, 0, 0, 0)
8457      * @param line which line to examine (0..getLineCount() - 1)
8458      * @param bounds Optional. If not null, it returns the extent of the line
8459      * @return the Y-coordinate of the baseline
8460      */
getLineBounds(int line, Rect bounds)8461     public int getLineBounds(int line, Rect bounds) {
8462         if (mLayout == null) {
8463             if (bounds != null) {
8464                 bounds.set(0, 0, 0, 0);
8465             }
8466             return 0;
8467         } else {
8468             int baseline = mLayout.getLineBounds(line, bounds);
8469 
8470             int voffset = getExtendedPaddingTop();
8471             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8472                 voffset += getVerticalOffset(true);
8473             }
8474             if (bounds != null) {
8475                 bounds.offset(getCompoundPaddingLeft(), voffset);
8476             }
8477             return baseline + voffset;
8478         }
8479     }
8480 
8481     @Override
getBaseline()8482     public int getBaseline() {
8483         if (mLayout == null) {
8484             return super.getBaseline();
8485         }
8486 
8487         return getBaselineOffset() + mLayout.getLineBaseline(0);
8488     }
8489 
getBaselineOffset()8490     int getBaselineOffset() {
8491         int voffset = 0;
8492         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8493             voffset = getVerticalOffset(true);
8494         }
8495 
8496         if (isLayoutModeOptical(mParent)) {
8497             voffset -= getOpticalInsets().top;
8498         }
8499 
8500         return getExtendedPaddingTop() + voffset;
8501     }
8502 
8503     /**
8504      * @hide
8505      */
8506     @Override
getFadeTop(boolean offsetRequired)8507     protected int getFadeTop(boolean offsetRequired) {
8508         if (mLayout == null) return 0;
8509 
8510         int voffset = 0;
8511         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8512             voffset = getVerticalOffset(true);
8513         }
8514 
8515         if (offsetRequired) voffset += getTopPaddingOffset();
8516 
8517         return getExtendedPaddingTop() + voffset;
8518     }
8519 
8520     /**
8521      * @hide
8522      */
8523     @Override
getFadeHeight(boolean offsetRequired)8524     protected int getFadeHeight(boolean offsetRequired) {
8525         return mLayout != null ? mLayout.getHeight() : 0;
8526     }
8527 
8528     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)8529     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
8530         if (mSpannable != null && mLinksClickable) {
8531             final float x = event.getX(pointerIndex);
8532             final float y = event.getY(pointerIndex);
8533             final int offset = getOffsetForPosition(x, y);
8534             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
8535                     ClickableSpan.class);
8536             if (clickables.length > 0) {
8537                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
8538             }
8539         }
8540         if (isTextSelectable() || isTextEditable()) {
8541             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
8542         }
8543         return super.onResolvePointerIcon(event, pointerIndex);
8544     }
8545 
8546     @Override
onKeyPreIme(int keyCode, KeyEvent event)8547     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
8548         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
8549         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
8550         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
8551         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
8552             return true;
8553         }
8554         return super.onKeyPreIme(keyCode, event);
8555     }
8556 
8557     /**
8558      * @hide
8559      */
handleBackInTextActionModeIfNeeded(KeyEvent event)8560     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
8561         // Do nothing unless mEditor is in text action mode.
8562         if (mEditor == null || mEditor.getTextActionMode() == null) {
8563             return false;
8564         }
8565 
8566         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
8567             KeyEvent.DispatcherState state = getKeyDispatcherState();
8568             if (state != null) {
8569                 state.startTracking(event, this);
8570             }
8571             return true;
8572         } else if (event.getAction() == KeyEvent.ACTION_UP) {
8573             KeyEvent.DispatcherState state = getKeyDispatcherState();
8574             if (state != null) {
8575                 state.handleUpEvent(event);
8576             }
8577             if (event.isTracking() && !event.isCanceled()) {
8578                 stopTextActionMode();
8579                 return true;
8580             }
8581         }
8582         return false;
8583     }
8584 
8585     @Override
onKeyDown(int keyCode, KeyEvent event)8586     public boolean onKeyDown(int keyCode, KeyEvent event) {
8587         final int which = doKeyDown(keyCode, event, null);
8588         if (which == KEY_EVENT_NOT_HANDLED) {
8589             return super.onKeyDown(keyCode, event);
8590         }
8591 
8592         return true;
8593     }
8594 
8595     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8596     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
8597         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
8598         final int which = doKeyDown(keyCode, down, event);
8599         if (which == KEY_EVENT_NOT_HANDLED) {
8600             // Go through default dispatching.
8601             return super.onKeyMultiple(keyCode, repeatCount, event);
8602         }
8603         if (which == KEY_EVENT_HANDLED) {
8604             // Consumed the whole thing.
8605             return true;
8606         }
8607 
8608         repeatCount--;
8609 
8610         // We are going to dispatch the remaining events to either the input
8611         // or movement method.  To do this, we will just send a repeated stream
8612         // of down and up events until we have done the complete repeatCount.
8613         // It would be nice if those interfaces had an onKeyMultiple() method,
8614         // but adding that is a more complicated change.
8615         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
8616         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
8617             // mEditor and mEditor.mInput are not null from doKeyDown
8618             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8619             while (--repeatCount > 0) {
8620                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
8621                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8622             }
8623             hideErrorIfUnchanged();
8624 
8625         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
8626             // mMovement is not null from doKeyDown
8627             mMovement.onKeyUp(this, mSpannable, keyCode, up);
8628             while (--repeatCount > 0) {
8629                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
8630                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
8631             }
8632         }
8633 
8634         return true;
8635     }
8636 
8637     /**
8638      * Returns true if pressing ENTER in this field advances focus instead
8639      * of inserting the character.  This is true mostly in single-line fields,
8640      * but also in mail addresses and subjects which will display on multiple
8641      * lines but where it doesn't make sense to insert newlines.
8642      */
shouldAdvanceFocusOnEnter()8643     private boolean shouldAdvanceFocusOnEnter() {
8644         if (getKeyListener() == null) {
8645             return false;
8646         }
8647 
8648         if (mSingleLine) {
8649             return true;
8650         }
8651 
8652         if (mEditor != null
8653                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8654                         == EditorInfo.TYPE_CLASS_TEXT) {
8655             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8656             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
8657                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
8658                 return true;
8659             }
8660         }
8661 
8662         return false;
8663     }
8664 
isDirectionalNavigationKey(int keyCode)8665     private boolean isDirectionalNavigationKey(int keyCode) {
8666         switch(keyCode) {
8667             case KeyEvent.KEYCODE_DPAD_UP:
8668             case KeyEvent.KEYCODE_DPAD_DOWN:
8669             case KeyEvent.KEYCODE_DPAD_LEFT:
8670             case KeyEvent.KEYCODE_DPAD_RIGHT:
8671                 return true;
8672         }
8673         return false;
8674     }
8675 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8676     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
8677         if (!isEnabled()) {
8678             return KEY_EVENT_NOT_HANDLED;
8679         }
8680 
8681         // If this is the initial keydown, we don't want to prevent a movement away from this view.
8682         // While this shouldn't be necessary because any time we're preventing default movement we
8683         // should be restricting the focus to remain within this view, thus we'll also receive
8684         // the key up event, occasionally key up events will get dropped and we don't want to
8685         // prevent the user from traversing out of this on the next key down.
8686         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8687             mPreventDefaultMovement = false;
8688         }
8689 
8690         switch (keyCode) {
8691             case KeyEvent.KEYCODE_ENTER:
8692             case KeyEvent.KEYCODE_NUMPAD_ENTER:
8693                 if (event.hasNoModifiers()) {
8694                     // When mInputContentType is set, we know that we are
8695                     // running in a "modern" cupcake environment, so don't need
8696                     // to worry about the application trying to capture
8697                     // enter key events.
8698                     if (mEditor != null && mEditor.mInputContentType != null) {
8699                         // If there is an action listener, given them a
8700                         // chance to consume the event.
8701                         if (mEditor.mInputContentType.onEditorActionListener != null
8702                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8703                                         this, EditorInfo.IME_NULL, event)) {
8704                             mEditor.mInputContentType.enterDown = true;
8705                             // We are consuming the enter key for them.
8706                             return KEY_EVENT_HANDLED;
8707                         }
8708                     }
8709 
8710                     // If our editor should move focus when enter is pressed, or
8711                     // this is a generated event from an IME action button, then
8712                     // don't let it be inserted into the text.
8713                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8714                             || shouldAdvanceFocusOnEnter()) {
8715                         if (hasOnClickListeners()) {
8716                             return KEY_EVENT_NOT_HANDLED;
8717                         }
8718                         return KEY_EVENT_HANDLED;
8719                     }
8720                 }
8721                 break;
8722 
8723             case KeyEvent.KEYCODE_DPAD_CENTER:
8724                 if (event.hasNoModifiers()) {
8725                     if (shouldAdvanceFocusOnEnter()) {
8726                         return KEY_EVENT_NOT_HANDLED;
8727                     }
8728                 }
8729                 break;
8730 
8731             case KeyEvent.KEYCODE_TAB:
8732                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
8733                     // Tab is used to move focus.
8734                     return KEY_EVENT_NOT_HANDLED;
8735                 }
8736                 break;
8737 
8738                 // Has to be done on key down (and not on key up) to correctly be intercepted.
8739             case KeyEvent.KEYCODE_BACK:
8740                 if (mEditor != null && mEditor.getTextActionMode() != null) {
8741                     stopTextActionMode();
8742                     return KEY_EVENT_HANDLED;
8743                 }
8744                 break;
8745 
8746             case KeyEvent.KEYCODE_CUT:
8747                 if (event.hasNoModifiers() && canCut()) {
8748                     if (onTextContextMenuItem(ID_CUT)) {
8749                         return KEY_EVENT_HANDLED;
8750                     }
8751                 }
8752                 break;
8753 
8754             case KeyEvent.KEYCODE_COPY:
8755                 if (event.hasNoModifiers() && canCopy()) {
8756                     if (onTextContextMenuItem(ID_COPY)) {
8757                         return KEY_EVENT_HANDLED;
8758                     }
8759                 }
8760                 break;
8761 
8762             case KeyEvent.KEYCODE_PASTE:
8763                 if (event.hasNoModifiers() && canPaste()) {
8764                     if (onTextContextMenuItem(ID_PASTE)) {
8765                         return KEY_EVENT_HANDLED;
8766                     }
8767                 }
8768                 break;
8769 
8770             case KeyEvent.KEYCODE_FORWARD_DEL:
8771                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
8772                     if (onTextContextMenuItem(ID_CUT)) {
8773                         return KEY_EVENT_HANDLED;
8774                     }
8775                 }
8776                 break;
8777 
8778             case KeyEvent.KEYCODE_INSERT:
8779                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
8780                     if (onTextContextMenuItem(ID_COPY)) {
8781                         return KEY_EVENT_HANDLED;
8782                     }
8783                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
8784                     if (onTextContextMenuItem(ID_PASTE)) {
8785                         return KEY_EVENT_HANDLED;
8786                     }
8787                 }
8788                 break;
8789         }
8790 
8791         if (mEditor != null && mEditor.mKeyListener != null) {
8792             boolean doDown = true;
8793             if (otherEvent != null) {
8794                 try {
8795                     beginBatchEdit();
8796                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
8797                             otherEvent);
8798                     hideErrorIfUnchanged();
8799                     doDown = false;
8800                     if (handled) {
8801                         return KEY_EVENT_HANDLED;
8802                     }
8803                 } catch (AbstractMethodError e) {
8804                     // onKeyOther was added after 1.0, so if it isn't
8805                     // implemented we need to try to dispatch as a regular down.
8806                 } finally {
8807                     endBatchEdit();
8808                 }
8809             }
8810 
8811             if (doDown) {
8812                 beginBatchEdit();
8813                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
8814                         keyCode, event);
8815                 endBatchEdit();
8816                 hideErrorIfUnchanged();
8817                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
8818             }
8819         }
8820 
8821         // bug 650865: sometimes we get a key event before a layout.
8822         // don't try to move around if we don't know the layout.
8823 
8824         if (mMovement != null && mLayout != null) {
8825             boolean doDown = true;
8826             if (otherEvent != null) {
8827                 try {
8828                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
8829                     doDown = false;
8830                     if (handled) {
8831                         return KEY_EVENT_HANDLED;
8832                     }
8833                 } catch (AbstractMethodError e) {
8834                     // onKeyOther was added after 1.0, so if it isn't
8835                     // implemented we need to try to dispatch as a regular down.
8836                 }
8837             }
8838             if (doDown) {
8839                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
8840                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8841                         mPreventDefaultMovement = true;
8842                     }
8843                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
8844                 }
8845             }
8846             // Consume arrows from keyboard devices to prevent focus leaving the editor.
8847             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
8848             // to move focus with arrows.
8849             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
8850                     && isDirectionalNavigationKey(keyCode)) {
8851                 return KEY_EVENT_HANDLED;
8852             }
8853         }
8854 
8855         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
8856                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
8857     }
8858 
8859     /**
8860      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
8861      * can be recorded.
8862      * @hide
8863      */
resetErrorChangedFlag()8864     public void resetErrorChangedFlag() {
8865         /*
8866          * Keep track of what the error was before doing the input
8867          * so that if an input filter changed the error, we leave
8868          * that error showing.  Otherwise, we take down whatever
8869          * error was showing when the user types something.
8870          */
8871         if (mEditor != null) mEditor.mErrorWasChanged = false;
8872     }
8873 
8874     /**
8875      * @hide
8876      */
hideErrorIfUnchanged()8877     public void hideErrorIfUnchanged() {
8878         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
8879             setError(null, null);
8880         }
8881     }
8882 
8883     @Override
onKeyUp(int keyCode, KeyEvent event)8884     public boolean onKeyUp(int keyCode, KeyEvent event) {
8885         if (!isEnabled()) {
8886             return super.onKeyUp(keyCode, event);
8887         }
8888 
8889         if (!KeyEvent.isModifierKey(keyCode)) {
8890             mPreventDefaultMovement = false;
8891         }
8892 
8893         switch (keyCode) {
8894             case KeyEvent.KEYCODE_DPAD_CENTER:
8895                 if (event.hasNoModifiers()) {
8896                     /*
8897                      * If there is a click listener, just call through to
8898                      * super, which will invoke it.
8899                      *
8900                      * If there isn't a click listener, try to show the soft
8901                      * input method.  (It will also
8902                      * call performClick(), but that won't do anything in
8903                      * this case.)
8904                      */
8905                     if (!hasOnClickListeners()) {
8906                         if (mMovement != null && mText instanceof Editable
8907                                 && mLayout != null && onCheckIsTextEditor()) {
8908                             InputMethodManager imm = getInputMethodManager();
8909                             viewClicked(imm);
8910                             if (imm != null && getShowSoftInputOnFocus()) {
8911                                 imm.showSoftInput(this, 0);
8912                             }
8913                         }
8914                     }
8915                 }
8916                 return super.onKeyUp(keyCode, event);
8917 
8918             case KeyEvent.KEYCODE_ENTER:
8919             case KeyEvent.KEYCODE_NUMPAD_ENTER:
8920                 if (event.hasNoModifiers()) {
8921                     if (mEditor != null && mEditor.mInputContentType != null
8922                             && mEditor.mInputContentType.onEditorActionListener != null
8923                             && mEditor.mInputContentType.enterDown) {
8924                         mEditor.mInputContentType.enterDown = false;
8925                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8926                                 this, EditorInfo.IME_NULL, event)) {
8927                             return true;
8928                         }
8929                     }
8930 
8931                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8932                             || shouldAdvanceFocusOnEnter()) {
8933                         /*
8934                          * If there is a click listener, just call through to
8935                          * super, which will invoke it.
8936                          *
8937                          * If there isn't a click listener, try to advance focus,
8938                          * but still call through to super, which will reset the
8939                          * pressed state and longpress state.  (It will also
8940                          * call performClick(), but that won't do anything in
8941                          * this case.)
8942                          */
8943                         if (!hasOnClickListeners()) {
8944                             View v = focusSearch(FOCUS_DOWN);
8945 
8946                             if (v != null) {
8947                                 if (!v.requestFocus(FOCUS_DOWN)) {
8948                                     throw new IllegalStateException("focus search returned a view "
8949                                             + "that wasn't able to take focus!");
8950                                 }
8951 
8952                                 /*
8953                                  * Return true because we handled the key; super
8954                                  * will return false because there was no click
8955                                  * listener.
8956                                  */
8957                                 super.onKeyUp(keyCode, event);
8958                                 return true;
8959                             } else if ((event.getFlags()
8960                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
8961                                 // No target for next focus, but make sure the IME
8962                                 // if this came from it.
8963                                 InputMethodManager imm = getInputMethodManager();
8964                                 if (imm != null && imm.isActive(this)) {
8965                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
8966                                 }
8967                             }
8968                         }
8969                     }
8970                     return super.onKeyUp(keyCode, event);
8971                 }
8972                 break;
8973         }
8974 
8975         if (mEditor != null && mEditor.mKeyListener != null) {
8976             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
8977                 return true;
8978             }
8979         }
8980 
8981         if (mMovement != null && mLayout != null) {
8982             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
8983                 return true;
8984             }
8985         }
8986 
8987         return super.onKeyUp(keyCode, event);
8988     }
8989 
8990     @Override
onCheckIsTextEditor()8991     public boolean onCheckIsTextEditor() {
8992         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
8993     }
8994 
hasEditorInFocusSearchDirection(@ocusRealDirection int direction)8995     private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) {
8996         final View nextView = focusSearch(direction);
8997         return nextView != null && nextView.onCheckIsTextEditor();
8998     }
8999 
9000     @Override
onCreateInputConnection(EditorInfo outAttrs)9001     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
9002         if (onCheckIsTextEditor() && isEnabled()) {
9003             mEditor.createInputMethodStateIfNeeded();
9004             outAttrs.inputType = getInputType();
9005             if (mEditor.mInputContentType != null) {
9006                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
9007                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
9008                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
9009                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
9010                 outAttrs.extras = mEditor.mInputContentType.extras;
9011                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
9012             } else {
9013                 outAttrs.imeOptions = EditorInfo.IME_NULL;
9014                 outAttrs.hintLocales = null;
9015             }
9016             if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) {
9017                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
9018             }
9019             if (hasEditorInFocusSearchDirection(FOCUS_UP)) {
9020                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
9021             }
9022             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
9023                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
9024                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
9025                     // An action has not been set, but the enter key will move to
9026                     // the next focus, so set the action to that.
9027                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
9028                 } else {
9029                     // An action has not been set, and there is no focus to move
9030                     // to, so let's just supply a "done" action.
9031                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
9032                 }
9033                 if (!shouldAdvanceFocusOnEnter()) {
9034                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
9035                 }
9036             }
9037             if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) {
9038                 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT;
9039             }
9040             if (isMultilineInputType(outAttrs.inputType)) {
9041                 // Multi-line text editors should always show an enter key.
9042                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
9043             }
9044             outAttrs.hintText = mHint;
9045             outAttrs.targetInputMethodUser = mTextOperationUser;
9046             if (mText instanceof Editable) {
9047                 InputConnection ic = new EditableInputConnection(this);
9048                 outAttrs.initialSelStart = getSelectionStart();
9049                 outAttrs.initialSelEnd = getSelectionEnd();
9050                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
9051                 outAttrs.setInitialSurroundingText(mText);
9052                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
9053                 return ic;
9054             }
9055         }
9056         return null;
9057     }
9058 
9059     /**
9060      * If this TextView contains editable content, extract a portion of it
9061      * based on the information in <var>request</var> in to <var>outText</var>.
9062      * @return Returns true if the text was successfully extracted, else false.
9063      */
extractText(ExtractedTextRequest request, ExtractedText outText)9064     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
9065         createEditorIfNeeded();
9066         return mEditor.extractText(request, outText);
9067     }
9068 
9069     /**
9070      * This is used to remove all style-impacting spans from text before new
9071      * extracted text is being replaced into it, so that we don't have any
9072      * lingering spans applied during the replace.
9073      */
removeParcelableSpans(Spannable spannable, int start, int end)9074     static void removeParcelableSpans(Spannable spannable, int start, int end) {
9075         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
9076         int i = spans.length;
9077         while (i > 0) {
9078             i--;
9079             spannable.removeSpan(spans[i]);
9080         }
9081     }
9082 
9083     /**
9084      * Apply to this text view the given extracted text, as previously
9085      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
9086      */
setExtractedText(ExtractedText text)9087     public void setExtractedText(ExtractedText text) {
9088         Editable content = getEditableText();
9089         if (text.text != null) {
9090             if (content == null) {
9091                 setText(text.text, TextView.BufferType.EDITABLE);
9092             } else {
9093                 int start = 0;
9094                 int end = content.length();
9095 
9096                 if (text.partialStartOffset >= 0) {
9097                     final int N = content.length();
9098                     start = text.partialStartOffset;
9099                     if (start > N) start = N;
9100                     end = text.partialEndOffset;
9101                     if (end > N) end = N;
9102                 }
9103 
9104                 removeParcelableSpans(content, start, end);
9105                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
9106                     if (text.text instanceof Spanned) {
9107                         // OK to copy spans only.
9108                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
9109                                 Object.class, content, start);
9110                     }
9111                 } else {
9112                     content.replace(start, end, text.text);
9113                 }
9114             }
9115         }
9116 
9117         // Now set the selection position...  make sure it is in range, to
9118         // avoid crashes.  If this is a partial update, it is possible that
9119         // the underlying text may have changed, causing us problems here.
9120         // Also we just don't want to trust clients to do the right thing.
9121         Spannable sp = (Spannable) getText();
9122         final int N = sp.length();
9123         int start = text.selectionStart;
9124         if (start < 0) {
9125             start = 0;
9126         } else if (start > N) {
9127             start = N;
9128         }
9129         int end = text.selectionEnd;
9130         if (end < 0) {
9131             end = 0;
9132         } else if (end > N) {
9133             end = N;
9134         }
9135         Selection.setSelection(sp, start, end);
9136 
9137         // Finally, update the selection mode.
9138         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
9139             MetaKeyKeyListener.startSelecting(this, sp);
9140         } else {
9141             MetaKeyKeyListener.stopSelecting(this, sp);
9142         }
9143 
9144         setHintInternal(text.hint);
9145     }
9146 
9147     /**
9148      * @hide
9149      */
setExtracting(ExtractedTextRequest req)9150     public void setExtracting(ExtractedTextRequest req) {
9151         if (mEditor.mInputMethodState != null) {
9152             mEditor.mInputMethodState.mExtractedTextRequest = req;
9153         }
9154         // This would stop a possible selection mode, but no such mode is started in case
9155         // extracted mode will start. Some text is selected though, and will trigger an action mode
9156         // in the extracted view.
9157         mEditor.hideCursorAndSpanControllers();
9158         stopTextActionMode();
9159         if (mEditor.mSelectionModifierCursorController != null) {
9160             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
9161         }
9162     }
9163 
9164     /**
9165      * Called by the framework in response to a text completion from
9166      * the current input method, provided by it calling
9167      * {@link InputConnection#commitCompletion
9168      * InputConnection.commitCompletion()}.  The default implementation does
9169      * nothing; text views that are supporting auto-completion should override
9170      * this to do their desired behavior.
9171      *
9172      * @param text The auto complete text the user has selected.
9173      */
onCommitCompletion(CompletionInfo text)9174     public void onCommitCompletion(CompletionInfo text) {
9175         // intentionally empty
9176     }
9177 
9178     /**
9179      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
9180      * dictionary) from the current input method, provided by it calling
9181      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
9182      * The default implementation flashes the background of the corrected word to provide
9183      * feedback to the user.
9184      *
9185      * @param info The auto correct info about the text that was corrected.
9186      */
onCommitCorrection(CorrectionInfo info)9187     public void onCommitCorrection(CorrectionInfo info) {
9188         if (mEditor != null) mEditor.onCommitCorrection(info);
9189     }
9190 
beginBatchEdit()9191     public void beginBatchEdit() {
9192         if (mEditor != null) mEditor.beginBatchEdit();
9193     }
9194 
endBatchEdit()9195     public void endBatchEdit() {
9196         if (mEditor != null) mEditor.endBatchEdit();
9197     }
9198 
9199     /**
9200      * Called by the framework in response to a request to begin a batch
9201      * of edit operations through a call to link {@link #beginBatchEdit()}.
9202      */
onBeginBatchEdit()9203     public void onBeginBatchEdit() {
9204         // intentionally empty
9205     }
9206 
9207     /**
9208      * Called by the framework in response to a request to end a batch
9209      * of edit operations through a call to link {@link #endBatchEdit}.
9210      */
onEndBatchEdit()9211     public void onEndBatchEdit() {
9212         // intentionally empty
9213     }
9214 
9215     /** @hide */
onPerformSpellCheck()9216     public void onPerformSpellCheck() {
9217         if (mEditor != null && mEditor.mSpellChecker != null) {
9218             mEditor.mSpellChecker.onPerformSpellCheck();
9219         }
9220     }
9221 
9222     /**
9223      * Called by the framework in response to a private command from the
9224      * current method, provided by it calling
9225      * {@link InputConnection#performPrivateCommand
9226      * InputConnection.performPrivateCommand()}.
9227      *
9228      * @param action The action name of the command.
9229      * @param data Any additional data for the command.  This may be null.
9230      * @return Return true if you handled the command, else false.
9231      */
onPrivateIMECommand(String action, Bundle data)9232     public boolean onPrivateIMECommand(String action, Bundle data) {
9233         return false;
9234     }
9235 
9236     /** @hide */
9237     @VisibleForTesting
9238     @UnsupportedAppUsage
nullLayouts()9239     public void nullLayouts() {
9240         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
9241             mSavedLayout = (BoringLayout) mLayout;
9242         }
9243         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
9244             mSavedHintLayout = (BoringLayout) mHintLayout;
9245         }
9246 
9247         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
9248 
9249         mBoring = mHintBoring = null;
9250 
9251         // Since it depends on the value of mLayout
9252         if (mEditor != null) mEditor.prepareCursorControllers();
9253     }
9254 
9255     /**
9256      * Make a new Layout based on the already-measured size of the view,
9257      * on the assumption that it was measured correctly at some point.
9258      */
9259     @UnsupportedAppUsage
assumeLayout()9260     private void assumeLayout() {
9261         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9262 
9263         if (width < 1) {
9264             width = 0;
9265         }
9266 
9267         int physicalWidth = width;
9268 
9269         if (mHorizontallyScrolling) {
9270             width = VERY_WIDE;
9271         }
9272 
9273         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
9274                       physicalWidth, false);
9275     }
9276 
9277     @UnsupportedAppUsage
getLayoutAlignment()9278     private Layout.Alignment getLayoutAlignment() {
9279         Layout.Alignment alignment;
9280         switch (getTextAlignment()) {
9281             case TEXT_ALIGNMENT_GRAVITY:
9282                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
9283                     case Gravity.START:
9284                         alignment = Layout.Alignment.ALIGN_NORMAL;
9285                         break;
9286                     case Gravity.END:
9287                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
9288                         break;
9289                     case Gravity.LEFT:
9290                         alignment = Layout.Alignment.ALIGN_LEFT;
9291                         break;
9292                     case Gravity.RIGHT:
9293                         alignment = Layout.Alignment.ALIGN_RIGHT;
9294                         break;
9295                     case Gravity.CENTER_HORIZONTAL:
9296                         alignment = Layout.Alignment.ALIGN_CENTER;
9297                         break;
9298                     default:
9299                         alignment = Layout.Alignment.ALIGN_NORMAL;
9300                         break;
9301                 }
9302                 break;
9303             case TEXT_ALIGNMENT_TEXT_START:
9304                 alignment = Layout.Alignment.ALIGN_NORMAL;
9305                 break;
9306             case TEXT_ALIGNMENT_TEXT_END:
9307                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
9308                 break;
9309             case TEXT_ALIGNMENT_CENTER:
9310                 alignment = Layout.Alignment.ALIGN_CENTER;
9311                 break;
9312             case TEXT_ALIGNMENT_VIEW_START:
9313                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
9314                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
9315                 break;
9316             case TEXT_ALIGNMENT_VIEW_END:
9317                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
9318                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
9319                 break;
9320             case TEXT_ALIGNMENT_INHERIT:
9321                 // This should never happen as we have already resolved the text alignment
9322                 // but better safe than sorry so we just fall through
9323             default:
9324                 alignment = Layout.Alignment.ALIGN_NORMAL;
9325                 break;
9326         }
9327         return alignment;
9328     }
9329 
9330     /**
9331      * The width passed in is now the desired layout width,
9332      * not the full view width with padding.
9333      * {@hide}
9334      */
9335     @VisibleForTesting
9336     @UnsupportedAppUsage
makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)9337     public void makeNewLayout(int wantWidth, int hintWidth,
9338                                  BoringLayout.Metrics boring,
9339                                  BoringLayout.Metrics hintBoring,
9340                                  int ellipsisWidth, boolean bringIntoView) {
9341         stopMarquee();
9342 
9343         // Update "old" cached values
9344         mOldMaximum = mMaximum;
9345         mOldMaxMode = mMaxMode;
9346 
9347         mHighlightPathBogus = true;
9348 
9349         if (wantWidth < 0) {
9350             wantWidth = 0;
9351         }
9352         if (hintWidth < 0) {
9353             hintWidth = 0;
9354         }
9355 
9356         Layout.Alignment alignment = getLayoutAlignment();
9357         final boolean testDirChange = mSingleLine && mLayout != null
9358                 && (alignment == Layout.Alignment.ALIGN_NORMAL
9359                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
9360         int oldDir = 0;
9361         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
9362         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
9363         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
9364                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
9365         TruncateAt effectiveEllipsize = mEllipsize;
9366         if (mEllipsize == TruncateAt.MARQUEE
9367                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
9368             effectiveEllipsize = TruncateAt.END_SMALL;
9369         }
9370 
9371         if (mTextDir == null) {
9372             mTextDir = getTextDirectionHeuristic();
9373         }
9374 
9375         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
9376                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
9377         if (switchEllipsize) {
9378             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
9379                     ? TruncateAt.END : TruncateAt.MARQUEE;
9380             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
9381                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
9382         }
9383 
9384         shouldEllipsize = mEllipsize != null;
9385         mHintLayout = null;
9386 
9387         if (mHint != null) {
9388             if (shouldEllipsize) hintWidth = wantWidth;
9389 
9390             if (hintBoring == UNKNOWN_BORING) {
9391                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
9392                         isFallbackLineSpacingForBoringLayout(), mHintBoring);
9393                 if (hintBoring != null) {
9394                     mHintBoring = hintBoring;
9395                 }
9396             }
9397 
9398             if (hintBoring != null) {
9399                 if (hintBoring.width <= hintWidth
9400                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
9401                     if (mSavedHintLayout != null) {
9402                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9403                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9404                                 hintBoring, mIncludePad);
9405                     } else {
9406                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9407                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9408                                 hintBoring, mIncludePad);
9409                     }
9410 
9411                     mSavedHintLayout = (BoringLayout) mHintLayout;
9412                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
9413                     if (mSavedHintLayout != null) {
9414                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9415                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9416                                 hintBoring, mIncludePad, mEllipsize,
9417                                 ellipsisWidth);
9418                     } else {
9419                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9420                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9421                                 hintBoring, mIncludePad, mEllipsize,
9422                                 ellipsisWidth);
9423                     }
9424                 }
9425             }
9426             // TODO: code duplication with makeSingleLayout()
9427             if (mHintLayout == null) {
9428                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
9429                         mHint.length(), mTextPaint, hintWidth)
9430                         .setAlignment(alignment)
9431                         .setTextDirection(mTextDir)
9432                         .setLineSpacing(mSpacingAdd, mSpacingMult)
9433                         .setIncludePad(mIncludePad)
9434                         .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
9435                         .setBreakStrategy(mBreakStrategy)
9436                         .setHyphenationFrequency(mHyphenationFrequency)
9437                         .setJustificationMode(mJustificationMode)
9438                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9439                         .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
9440                                 mLineBreakStyle, mLineBreakWordStyle));
9441                 if (shouldEllipsize) {
9442                     builder.setEllipsize(mEllipsize)
9443                             .setEllipsizedWidth(ellipsisWidth);
9444                 }
9445                 mHintLayout = builder.build();
9446             }
9447         }
9448 
9449         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
9450             registerForPreDraw();
9451         }
9452 
9453         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9454             if (!compressText(ellipsisWidth)) {
9455                 final int height = mLayoutParams.height;
9456                 // If the size of the view does not depend on the size of the text, try to
9457                 // start the marquee immediately
9458                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
9459                     startMarquee();
9460                 } else {
9461                     // Defer the start of the marquee until we know our width (see setFrame())
9462                     mRestartMarquee = true;
9463                 }
9464             }
9465         }
9466 
9467         // CursorControllers need a non-null mLayout
9468         if (mEditor != null) mEditor.prepareCursorControllers();
9469     }
9470 
9471     /**
9472      * Returns true if DynamicLayout is required
9473      *
9474      * @hide
9475      */
9476     @VisibleForTesting
useDynamicLayout()9477     public boolean useDynamicLayout() {
9478         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
9479     }
9480 
9481     /**
9482      * @hide
9483      */
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9484     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
9485             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
9486             boolean useSaved) {
9487         Layout result = null;
9488         if (useDynamicLayout()) {
9489             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
9490                     wantWidth)
9491                     .setDisplayText(mTransformed)
9492                     .setAlignment(alignment)
9493                     .setTextDirection(mTextDir)
9494                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9495                     .setIncludePad(mIncludePad)
9496                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
9497                     .setBreakStrategy(mBreakStrategy)
9498                     .setHyphenationFrequency(mHyphenationFrequency)
9499                     .setJustificationMode(mJustificationMode)
9500                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
9501                     .setEllipsizedWidth(ellipsisWidth);
9502             result = builder.build();
9503         } else {
9504             if (boring == UNKNOWN_BORING) {
9505                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
9506                         isFallbackLineSpacingForBoringLayout(), mBoring);
9507                 if (boring != null) {
9508                     mBoring = boring;
9509                 }
9510             }
9511 
9512             if (boring != null) {
9513                 if (boring.width <= wantWidth
9514                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
9515                     if (useSaved && mSavedLayout != null) {
9516                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9517                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9518                                 boring, mIncludePad);
9519                     } else {
9520                         result = BoringLayout.make(mTransformed, mTextPaint,
9521                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9522                                 boring, mIncludePad);
9523                     }
9524 
9525                     if (useSaved) {
9526                         mSavedLayout = (BoringLayout) result;
9527                     }
9528                 } else if (shouldEllipsize && boring.width <= wantWidth) {
9529                     if (useSaved && mSavedLayout != null) {
9530                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9531                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9532                                 boring, mIncludePad, effectiveEllipsize,
9533                                 ellipsisWidth);
9534                     } else {
9535                         result = BoringLayout.make(mTransformed, mTextPaint,
9536                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9537                                 boring, mIncludePad, effectiveEllipsize,
9538                                 ellipsisWidth);
9539                     }
9540                 }
9541             }
9542         }
9543         if (result == null) {
9544             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
9545                     0, mTransformed.length(), mTextPaint, wantWidth)
9546                     .setAlignment(alignment)
9547                     .setTextDirection(mTextDir)
9548                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9549                     .setIncludePad(mIncludePad)
9550                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
9551                     .setBreakStrategy(mBreakStrategy)
9552                     .setHyphenationFrequency(mHyphenationFrequency)
9553                     .setJustificationMode(mJustificationMode)
9554                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9555                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
9556                             mLineBreakStyle, mLineBreakWordStyle));
9557             if (shouldEllipsize) {
9558                 builder.setEllipsize(effectiveEllipsize)
9559                         .setEllipsizedWidth(ellipsisWidth);
9560             }
9561             result = builder.build();
9562         }
9563         return result;
9564     }
9565 
9566     @UnsupportedAppUsage
compressText(float width)9567     private boolean compressText(float width) {
9568         if (isHardwareAccelerated()) return false;
9569 
9570         // Only compress the text if it hasn't been compressed by the previous pass
9571         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
9572                 && mTextPaint.getTextScaleX() == 1.0f) {
9573             final float textWidth = mLayout.getLineWidth(0);
9574             final float overflow = (textWidth + 1.0f - width) / width;
9575             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
9576                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
9577                 post(new Runnable() {
9578                     public void run() {
9579                         requestLayout();
9580                     }
9581                 });
9582                 return true;
9583             }
9584         }
9585 
9586         return false;
9587     }
9588 
desired(Layout layout)9589     private static int desired(Layout layout) {
9590         int n = layout.getLineCount();
9591         CharSequence text = layout.getText();
9592         float max = 0;
9593 
9594         // if any line was wrapped, we can't use it.
9595         // but it's ok for the last line not to have a newline
9596 
9597         for (int i = 0; i < n - 1; i++) {
9598             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
9599                 return -1;
9600             }
9601         }
9602 
9603         for (int i = 0; i < n; i++) {
9604             max = Math.max(max, layout.getLineMax(i));
9605         }
9606 
9607         return (int) Math.ceil(max);
9608     }
9609 
9610     /**
9611      * Set whether the TextView includes extra top and bottom padding to make
9612      * room for accents that go above the normal ascent and descent.
9613      * The default is true.
9614      *
9615      * @see #getIncludeFontPadding()
9616      *
9617      * @attr ref android.R.styleable#TextView_includeFontPadding
9618      */
setIncludeFontPadding(boolean includepad)9619     public void setIncludeFontPadding(boolean includepad) {
9620         if (mIncludePad != includepad) {
9621             mIncludePad = includepad;
9622 
9623             if (mLayout != null) {
9624                 nullLayouts();
9625                 requestLayout();
9626                 invalidate();
9627             }
9628         }
9629     }
9630 
9631     /**
9632      * Gets whether the TextView includes extra top and bottom padding to make
9633      * room for accents that go above the normal ascent and descent.
9634      *
9635      * @see #setIncludeFontPadding(boolean)
9636      *
9637      * @attr ref android.R.styleable#TextView_includeFontPadding
9638      */
9639     @InspectableProperty
getIncludeFontPadding()9640     public boolean getIncludeFontPadding() {
9641         return mIncludePad;
9642     }
9643 
9644     /** @hide */
9645     @VisibleForTesting
9646     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
9647 
9648     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)9649     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
9650         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
9651         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
9652         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
9653         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
9654 
9655         int width;
9656         int height;
9657 
9658         BoringLayout.Metrics boring = UNKNOWN_BORING;
9659         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
9660 
9661         if (mTextDir == null) {
9662             mTextDir = getTextDirectionHeuristic();
9663         }
9664 
9665         int des = -1;
9666         boolean fromexisting = false;
9667         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
9668                 ?  (float) widthSize : Float.MAX_VALUE;
9669 
9670         if (widthMode == MeasureSpec.EXACTLY) {
9671             // Parent has told us how big to be. So be it.
9672             width = widthSize;
9673         } else {
9674             if (mLayout != null && mEllipsize == null) {
9675                 des = desired(mLayout);
9676             }
9677 
9678             if (des < 0) {
9679                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
9680                         isFallbackLineSpacingForBoringLayout(), mBoring);
9681                 if (boring != null) {
9682                     mBoring = boring;
9683                 }
9684             } else {
9685                 fromexisting = true;
9686             }
9687 
9688             if (boring == null || boring == UNKNOWN_BORING) {
9689                 if (des < 0) {
9690                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
9691                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
9692                 }
9693                 width = des;
9694             } else {
9695                 width = boring.width;
9696             }
9697 
9698             final Drawables dr = mDrawables;
9699             if (dr != null) {
9700                 width = Math.max(width, dr.mDrawableWidthTop);
9701                 width = Math.max(width, dr.mDrawableWidthBottom);
9702             }
9703 
9704             if (mHint != null) {
9705                 int hintDes = -1;
9706                 int hintWidth;
9707 
9708                 if (mHintLayout != null && mEllipsize == null) {
9709                     hintDes = desired(mHintLayout);
9710                 }
9711 
9712                 if (hintDes < 0) {
9713                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
9714                             isFallbackLineSpacingForBoringLayout(), mHintBoring);
9715                     if (hintBoring != null) {
9716                         mHintBoring = hintBoring;
9717                     }
9718                 }
9719 
9720                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
9721                     if (hintDes < 0) {
9722                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
9723                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
9724                     }
9725                     hintWidth = hintDes;
9726                 } else {
9727                     hintWidth = hintBoring.width;
9728                 }
9729 
9730                 if (hintWidth > width) {
9731                     width = hintWidth;
9732                 }
9733             }
9734 
9735             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
9736 
9737             if (mMaxWidthMode == EMS) {
9738                 width = Math.min(width, mMaxWidth * getLineHeight());
9739             } else {
9740                 width = Math.min(width, mMaxWidth);
9741             }
9742 
9743             if (mMinWidthMode == EMS) {
9744                 width = Math.max(width, mMinWidth * getLineHeight());
9745             } else {
9746                 width = Math.max(width, mMinWidth);
9747             }
9748 
9749             // Check against our minimum width
9750             width = Math.max(width, getSuggestedMinimumWidth());
9751 
9752             if (widthMode == MeasureSpec.AT_MOST) {
9753                 width = Math.min(widthSize, width);
9754             }
9755         }
9756 
9757         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
9758         int unpaddedWidth = want;
9759 
9760         if (mHorizontallyScrolling) want = VERY_WIDE;
9761 
9762         int hintWant = want;
9763         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
9764 
9765         if (mLayout == null) {
9766             makeNewLayout(want, hintWant, boring, hintBoring,
9767                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9768         } else {
9769             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
9770                     || (mLayout.getEllipsizedWidth()
9771                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
9772 
9773             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
9774                     && (want > mLayout.getWidth())
9775                     && (mLayout instanceof BoringLayout
9776                             || (fromexisting && des >= 0 && des <= want));
9777 
9778             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
9779 
9780             if (layoutChanged || maximumChanged) {
9781                 if (!maximumChanged && widthChanged) {
9782                     mLayout.increaseWidthTo(want);
9783                 } else {
9784                     makeNewLayout(want, hintWant, boring, hintBoring,
9785                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9786                 }
9787             } else {
9788                 // Nothing has changed
9789             }
9790         }
9791 
9792         if (heightMode == MeasureSpec.EXACTLY) {
9793             // Parent has told us how big to be. So be it.
9794             height = heightSize;
9795             mDesiredHeightAtMeasure = -1;
9796         } else {
9797             int desired = getDesiredHeight();
9798 
9799             height = desired;
9800             mDesiredHeightAtMeasure = desired;
9801 
9802             if (heightMode == MeasureSpec.AT_MOST) {
9803                 height = Math.min(desired, heightSize);
9804             }
9805         }
9806 
9807         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
9808         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
9809             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
9810         }
9811 
9812         /*
9813          * We didn't let makeNewLayout() register to bring the cursor into view,
9814          * so do it here if there is any possibility that it is needed.
9815          */
9816         if (mMovement != null
9817                 || mLayout.getWidth() > unpaddedWidth
9818                 || mLayout.getHeight() > unpaddedHeight) {
9819             registerForPreDraw();
9820         } else {
9821             scrollTo(0, 0);
9822         }
9823 
9824         setMeasuredDimension(width, height);
9825     }
9826 
9827     /**
9828      * Automatically computes and sets the text size.
9829      */
autoSizeText()9830     private void autoSizeText() {
9831         if (!isAutoSizeEnabled()) {
9832             return;
9833         }
9834 
9835         if (mNeedsAutoSizeText) {
9836             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
9837                 return;
9838             }
9839 
9840             final int availableWidth = mHorizontallyScrolling
9841                     ? VERY_WIDE
9842                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
9843             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
9844                     - getExtendedPaddingTop();
9845 
9846             if (availableWidth <= 0 || availableHeight <= 0) {
9847                 return;
9848             }
9849 
9850             synchronized (TEMP_RECTF) {
9851                 TEMP_RECTF.setEmpty();
9852                 TEMP_RECTF.right = availableWidth;
9853                 TEMP_RECTF.bottom = availableHeight;
9854                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
9855 
9856                 if (optimalTextSize != getTextSize()) {
9857                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
9858                             false /* shouldRequestLayout */);
9859 
9860                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
9861                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9862                             false /* bringIntoView */);
9863                 }
9864             }
9865         }
9866         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
9867         // after the next layout pass should set this to false.
9868         mNeedsAutoSizeText = true;
9869     }
9870 
9871     /**
9872      * Performs a binary search to find the largest text size that will still fit within the size
9873      * available to this view.
9874      */
findLargestTextSizeWhichFits(RectF availableSpace)9875     private int findLargestTextSizeWhichFits(RectF availableSpace) {
9876         final int sizesCount = mAutoSizeTextSizesInPx.length;
9877         if (sizesCount == 0) {
9878             throw new IllegalStateException("No available text sizes to choose from.");
9879         }
9880 
9881         int bestSizeIndex = 0;
9882         int lowIndex = bestSizeIndex + 1;
9883         int highIndex = sizesCount - 1;
9884         int sizeToTryIndex;
9885         while (lowIndex <= highIndex) {
9886             sizeToTryIndex = (lowIndex + highIndex) / 2;
9887             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
9888                 bestSizeIndex = lowIndex;
9889                 lowIndex = sizeToTryIndex + 1;
9890             } else {
9891                 highIndex = sizeToTryIndex - 1;
9892                 bestSizeIndex = highIndex;
9893             }
9894         }
9895 
9896         return mAutoSizeTextSizesInPx[bestSizeIndex];
9897     }
9898 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9899     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
9900         final CharSequence text = mTransformed != null
9901                 ? mTransformed
9902                 : getText();
9903         final int maxLines = getMaxLines();
9904         if (mTempTextPaint == null) {
9905             mTempTextPaint = new TextPaint();
9906         } else {
9907             mTempTextPaint.reset();
9908         }
9909         mTempTextPaint.set(getPaint());
9910         mTempTextPaint.setTextSize(suggestedSizeInPx);
9911 
9912         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
9913                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
9914 
9915         layoutBuilder.setAlignment(getLayoutAlignment())
9916                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
9917                 .setIncludePad(getIncludeFontPadding())
9918                 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
9919                 .setBreakStrategy(getBreakStrategy())
9920                 .setHyphenationFrequency(getHyphenationFrequency())
9921                 .setJustificationMode(getJustificationMode())
9922                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9923                 .setTextDirection(getTextDirectionHeuristic())
9924                 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
9925                         mLineBreakStyle, mLineBreakWordStyle));
9926 
9927         final StaticLayout layout = layoutBuilder.build();
9928 
9929         // Lines overflow.
9930         if (maxLines != -1 && layout.getLineCount() > maxLines) {
9931             return false;
9932         }
9933 
9934         // Height overflow.
9935         if (layout.getHeight() > availableSpace.bottom) {
9936             return false;
9937         }
9938 
9939         return true;
9940     }
9941 
getDesiredHeight()9942     private int getDesiredHeight() {
9943         return Math.max(
9944                 getDesiredHeight(mLayout, true),
9945                 getDesiredHeight(mHintLayout, mEllipsize != null));
9946     }
9947 
getDesiredHeight(Layout layout, boolean cap)9948     private int getDesiredHeight(Layout layout, boolean cap) {
9949         if (layout == null) {
9950             return 0;
9951         }
9952 
9953         /*
9954         * Don't cap the hint to a certain number of lines.
9955         * (Do cap it, though, if we have a maximum pixel height.)
9956         */
9957         int desired = layout.getHeight(cap);
9958 
9959         final Drawables dr = mDrawables;
9960         if (dr != null) {
9961             desired = Math.max(desired, dr.mDrawableHeightLeft);
9962             desired = Math.max(desired, dr.mDrawableHeightRight);
9963         }
9964 
9965         int linecount = layout.getLineCount();
9966         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
9967         desired += padding;
9968 
9969         if (mMaxMode != LINES) {
9970             desired = Math.min(desired, mMaximum);
9971         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
9972                 || layout instanceof BoringLayout)) {
9973             desired = layout.getLineTop(mMaximum);
9974 
9975             if (dr != null) {
9976                 desired = Math.max(desired, dr.mDrawableHeightLeft);
9977                 desired = Math.max(desired, dr.mDrawableHeightRight);
9978             }
9979 
9980             desired += padding;
9981             linecount = mMaximum;
9982         }
9983 
9984         if (mMinMode == LINES) {
9985             if (linecount < mMinimum) {
9986                 desired += getLineHeight() * (mMinimum - linecount);
9987             }
9988         } else {
9989             desired = Math.max(desired, mMinimum);
9990         }
9991 
9992         // Check against our minimum height
9993         desired = Math.max(desired, getSuggestedMinimumHeight());
9994 
9995         return desired;
9996     }
9997 
9998     /**
9999      * Check whether a change to the existing text layout requires a
10000      * new view layout.
10001      */
checkForResize()10002     private void checkForResize() {
10003         boolean sizeChanged = false;
10004 
10005         if (mLayout != null) {
10006             // Check if our width changed
10007             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
10008                 sizeChanged = true;
10009                 invalidate();
10010             }
10011 
10012             // Check if our height changed
10013             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
10014                 int desiredHeight = getDesiredHeight();
10015 
10016                 if (desiredHeight != this.getHeight()) {
10017                     sizeChanged = true;
10018                 }
10019             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
10020                 if (mDesiredHeightAtMeasure >= 0) {
10021                     int desiredHeight = getDesiredHeight();
10022 
10023                     if (desiredHeight != mDesiredHeightAtMeasure) {
10024                         sizeChanged = true;
10025                     }
10026                 }
10027             }
10028         }
10029 
10030         if (sizeChanged) {
10031             requestLayout();
10032             // caller will have already invalidated
10033         }
10034     }
10035 
10036     /**
10037      * Check whether entirely new text requires a new view layout
10038      * or merely a new text layout.
10039      */
10040     @UnsupportedAppUsage
checkForRelayout()10041     private void checkForRelayout() {
10042         // If we have a fixed width, we can just swap in a new text layout
10043         // if the text height stays the same or if the view height is fixed.
10044 
10045         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
10046                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
10047                 && (mHint == null || mHintLayout != null)
10048                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
10049             // Static width, so try making a new text layout.
10050 
10051             int oldht = mLayout.getHeight();
10052             int want = mLayout.getWidth();
10053             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
10054 
10055             /*
10056              * No need to bring the text into view, since the size is not
10057              * changing (unless we do the requestLayout(), in which case it
10058              * will happen at measure).
10059              */
10060             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
10061                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
10062                           false);
10063 
10064             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
10065                 // In a fixed-height view, so use our new text layout.
10066                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
10067                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
10068                     autoSizeText();
10069                     invalidate();
10070                     return;
10071                 }
10072 
10073                 // Dynamic height, but height has stayed the same,
10074                 // so use our new text layout.
10075                 if (mLayout.getHeight() == oldht
10076                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
10077                     autoSizeText();
10078                     invalidate();
10079                     return;
10080                 }
10081             }
10082 
10083             // We lose: the height has changed and we have a dynamic height.
10084             // Request a new view layout using our new text layout.
10085             requestLayout();
10086             invalidate();
10087         } else {
10088             // Dynamic width, so we have no choice but to request a new
10089             // view layout with a new text layout.
10090             nullLayouts();
10091             requestLayout();
10092             invalidate();
10093         }
10094     }
10095 
10096     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)10097     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
10098         super.onLayout(changed, left, top, right, bottom);
10099         if (mDeferScroll >= 0) {
10100             int curs = mDeferScroll;
10101             mDeferScroll = -1;
10102             bringPointIntoView(Math.min(curs, mText.length()));
10103         }
10104         // Call auto-size after the width and height have been calculated.
10105         autoSizeText();
10106     }
10107 
isShowingHint()10108     private boolean isShowingHint() {
10109         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
10110     }
10111 
10112     /**
10113      * Returns true if anything changed.
10114      */
10115     @UnsupportedAppUsage
bringTextIntoView()10116     private boolean bringTextIntoView() {
10117         Layout layout = isShowingHint() ? mHintLayout : mLayout;
10118         int line = 0;
10119         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
10120             line = layout.getLineCount() - 1;
10121         }
10122 
10123         Layout.Alignment a = layout.getParagraphAlignment(line);
10124         int dir = layout.getParagraphDirection(line);
10125         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10126         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10127         int ht = layout.getHeight();
10128 
10129         int scrollx, scrolly;
10130 
10131         // Convert to left, center, or right alignment.
10132         if (a == Layout.Alignment.ALIGN_NORMAL) {
10133             a = dir == Layout.DIR_LEFT_TO_RIGHT
10134                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
10135         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
10136             a = dir == Layout.DIR_LEFT_TO_RIGHT
10137                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
10138         }
10139 
10140         if (a == Layout.Alignment.ALIGN_CENTER) {
10141             /*
10142              * Keep centered if possible, or, if it is too wide to fit,
10143              * keep leading edge in view.
10144              */
10145 
10146             int left = (int) Math.floor(layout.getLineLeft(line));
10147             int right = (int) Math.ceil(layout.getLineRight(line));
10148 
10149             if (right - left < hspace) {
10150                 scrollx = (right + left) / 2 - hspace / 2;
10151             } else {
10152                 if (dir < 0) {
10153                     scrollx = right - hspace;
10154                 } else {
10155                     scrollx = left;
10156                 }
10157             }
10158         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
10159             int right = (int) Math.ceil(layout.getLineRight(line));
10160             scrollx = right - hspace;
10161         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
10162             scrollx = (int) Math.floor(layout.getLineLeft(line));
10163         }
10164 
10165         if (ht < vspace) {
10166             scrolly = 0;
10167         } else {
10168             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
10169                 scrolly = ht - vspace;
10170             } else {
10171                 scrolly = 0;
10172             }
10173         }
10174 
10175         if (scrollx != mScrollX || scrolly != mScrollY) {
10176             scrollTo(scrollx, scrolly);
10177             return true;
10178         } else {
10179             return false;
10180         }
10181     }
10182 
10183     /**
10184      * Move the point, specified by the offset, into the view if it is needed.
10185      * This has to be called after layout. Returns true if anything changed.
10186      */
bringPointIntoView(int offset)10187     public boolean bringPointIntoView(int offset) {
10188         if (isLayoutRequested()) {
10189             mDeferScroll = offset;
10190             return false;
10191         }
10192         boolean changed = false;
10193 
10194         Layout layout = isShowingHint() ? mHintLayout : mLayout;
10195 
10196         if (layout == null) return changed;
10197 
10198         int line = layout.getLineForOffset(offset);
10199 
10200         int grav;
10201 
10202         switch (layout.getParagraphAlignment(line)) {
10203             case ALIGN_LEFT:
10204                 grav = 1;
10205                 break;
10206             case ALIGN_RIGHT:
10207                 grav = -1;
10208                 break;
10209             case ALIGN_NORMAL:
10210                 grav = layout.getParagraphDirection(line);
10211                 break;
10212             case ALIGN_OPPOSITE:
10213                 grav = -layout.getParagraphDirection(line);
10214                 break;
10215             case ALIGN_CENTER:
10216             default:
10217                 grav = 0;
10218                 break;
10219         }
10220 
10221         // We only want to clamp the cursor to fit within the layout width
10222         // in left-to-right modes, because in a right to left alignment,
10223         // we want to scroll to keep the line-right on the screen, as other
10224         // lines are likely to have text flush with the right margin, which
10225         // we want to keep visible.
10226         // A better long-term solution would probably be to measure both
10227         // the full line and a blank-trimmed version, and, for example, use
10228         // the latter measurement for centering and right alignment, but for
10229         // the time being we only implement the cursor clamping in left to
10230         // right where it is most likely to be annoying.
10231         final boolean clamped = grav > 0;
10232         // FIXME: Is it okay to truncate this, or should we round?
10233         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
10234         final int top = layout.getLineTop(line);
10235         final int bottom = layout.getLineTop(line + 1);
10236 
10237         int left = (int) Math.floor(layout.getLineLeft(line));
10238         int right = (int) Math.ceil(layout.getLineRight(line));
10239         int ht = layout.getHeight();
10240 
10241         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10242         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10243         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
10244             // If cursor has been clamped, make sure we don't scroll.
10245             right = Math.max(x, left + hspace);
10246         }
10247 
10248         int hslack = (bottom - top) / 2;
10249         int vslack = hslack;
10250 
10251         if (vslack > vspace / 4) {
10252             vslack = vspace / 4;
10253         }
10254         if (hslack > hspace / 4) {
10255             hslack = hspace / 4;
10256         }
10257 
10258         int hs = mScrollX;
10259         int vs = mScrollY;
10260 
10261         if (top - vs < vslack) {
10262             vs = top - vslack;
10263         }
10264         if (bottom - vs > vspace - vslack) {
10265             vs = bottom - (vspace - vslack);
10266         }
10267         if (ht - vs < vspace) {
10268             vs = ht - vspace;
10269         }
10270         if (0 - vs > 0) {
10271             vs = 0;
10272         }
10273 
10274         if (grav != 0) {
10275             if (x - hs < hslack) {
10276                 hs = x - hslack;
10277             }
10278             if (x - hs > hspace - hslack) {
10279                 hs = x - (hspace - hslack);
10280             }
10281         }
10282 
10283         if (grav < 0) {
10284             if (left - hs > 0) {
10285                 hs = left;
10286             }
10287             if (right - hs < hspace) {
10288                 hs = right - hspace;
10289             }
10290         } else if (grav > 0) {
10291             if (right - hs < hspace) {
10292                 hs = right - hspace;
10293             }
10294             if (left - hs > 0) {
10295                 hs = left;
10296             }
10297         } else /* grav == 0 */ {
10298             if (right - left <= hspace) {
10299                 /*
10300                  * If the entire text fits, center it exactly.
10301                  */
10302                 hs = left - (hspace - (right - left)) / 2;
10303             } else if (x > right - hslack) {
10304                 /*
10305                  * If we are near the right edge, keep the right edge
10306                  * at the edge of the view.
10307                  */
10308                 hs = right - hspace;
10309             } else if (x < left + hslack) {
10310                 /*
10311                  * If we are near the left edge, keep the left edge
10312                  * at the edge of the view.
10313                  */
10314                 hs = left;
10315             } else if (left > hs) {
10316                 /*
10317                  * Is there whitespace visible at the left?  Fix it if so.
10318                  */
10319                 hs = left;
10320             } else if (right < hs + hspace) {
10321                 /*
10322                  * Is there whitespace visible at the right?  Fix it if so.
10323                  */
10324                 hs = right - hspace;
10325             } else {
10326                 /*
10327                  * Otherwise, float as needed.
10328                  */
10329                 if (x - hs < hslack) {
10330                     hs = x - hslack;
10331                 }
10332                 if (x - hs > hspace - hslack) {
10333                     hs = x - (hspace - hslack);
10334                 }
10335             }
10336         }
10337 
10338         if (hs != mScrollX || vs != mScrollY) {
10339             if (mScroller == null) {
10340                 scrollTo(hs, vs);
10341             } else {
10342                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
10343                 int dx = hs - mScrollX;
10344                 int dy = vs - mScrollY;
10345 
10346                 if (duration > ANIMATED_SCROLL_GAP) {
10347                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
10348                     awakenScrollBars(mScroller.getDuration());
10349                     invalidate();
10350                 } else {
10351                     if (!mScroller.isFinished()) {
10352                         mScroller.abortAnimation();
10353                     }
10354 
10355                     scrollBy(dx, dy);
10356                 }
10357 
10358                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
10359             }
10360 
10361             changed = true;
10362         }
10363 
10364         if (isFocused()) {
10365             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
10366             // requestRectangleOnScreen() is in terms of content coordinates.
10367 
10368             // The offsets here are to ensure the rectangle we are using is
10369             // within our view bounds, in case the cursor is on the far left
10370             // or right.  If it isn't withing the bounds, then this request
10371             // will be ignored.
10372             if (mTempRect == null) mTempRect = new Rect();
10373             mTempRect.set(x - 2, top, x + 2, bottom);
10374             getInterestingRect(mTempRect, line);
10375             mTempRect.offset(mScrollX, mScrollY);
10376 
10377             if (requestRectangleOnScreen(mTempRect)) {
10378                 changed = true;
10379             }
10380         }
10381 
10382         return changed;
10383     }
10384 
10385     /**
10386      * Move the cursor, if needed, so that it is at an offset that is visible
10387      * to the user.  This will not move the cursor if it represents more than
10388      * one character (a selection range).  This will only work if the
10389      * TextView contains spannable text; otherwise it will do nothing.
10390      *
10391      * @return True if the cursor was actually moved, false otherwise.
10392      */
moveCursorToVisibleOffset()10393     public boolean moveCursorToVisibleOffset() {
10394         if (!(mText instanceof Spannable)) {
10395             return false;
10396         }
10397         int start = getSelectionStart();
10398         int end = getSelectionEnd();
10399         if (start != end) {
10400             return false;
10401         }
10402 
10403         // First: make sure the line is visible on screen:
10404 
10405         int line = mLayout.getLineForOffset(start);
10406 
10407         final int top = mLayout.getLineTop(line);
10408         final int bottom = mLayout.getLineTop(line + 1);
10409         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10410         int vslack = (bottom - top) / 2;
10411         if (vslack > vspace / 4) {
10412             vslack = vspace / 4;
10413         }
10414         final int vs = mScrollY;
10415 
10416         if (top < (vs + vslack)) {
10417             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
10418         } else if (bottom > (vspace + vs - vslack)) {
10419             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
10420         }
10421 
10422         // Next: make sure the character is visible on screen:
10423 
10424         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10425         final int hs = mScrollX;
10426         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
10427         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
10428 
10429         // line might contain bidirectional text
10430         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
10431         final int highChar = leftChar > rightChar ? leftChar : rightChar;
10432 
10433         int newStart = start;
10434         if (newStart < lowChar) {
10435             newStart = lowChar;
10436         } else if (newStart > highChar) {
10437             newStart = highChar;
10438         }
10439 
10440         if (newStart != start) {
10441             Selection.setSelection(mSpannable, newStart);
10442             return true;
10443         }
10444 
10445         return false;
10446     }
10447 
10448     @Override
computeScroll()10449     public void computeScroll() {
10450         if (mScroller != null) {
10451             if (mScroller.computeScrollOffset()) {
10452                 mScrollX = mScroller.getCurrX();
10453                 mScrollY = mScroller.getCurrY();
10454                 invalidateParentCaches();
10455                 postInvalidate();  // So we draw again
10456             }
10457         }
10458     }
10459 
getInterestingRect(Rect r, int line)10460     private void getInterestingRect(Rect r, int line) {
10461         convertFromViewportToContentCoordinates(r);
10462 
10463         // Rectangle can can be expanded on first and last line to take
10464         // padding into account.
10465         // TODO Take left/right padding into account too?
10466         if (line == 0) r.top -= getExtendedPaddingTop();
10467         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
10468     }
10469 
convertFromViewportToContentCoordinates(Rect r)10470     private void convertFromViewportToContentCoordinates(Rect r) {
10471         final int horizontalOffset = viewportToContentHorizontalOffset();
10472         r.left += horizontalOffset;
10473         r.right += horizontalOffset;
10474 
10475         final int verticalOffset = viewportToContentVerticalOffset();
10476         r.top += verticalOffset;
10477         r.bottom += verticalOffset;
10478     }
10479 
viewportToContentHorizontalOffset()10480     int viewportToContentHorizontalOffset() {
10481         return getCompoundPaddingLeft() - mScrollX;
10482     }
10483 
10484     @UnsupportedAppUsage
viewportToContentVerticalOffset()10485     int viewportToContentVerticalOffset() {
10486         int offset = getExtendedPaddingTop() - mScrollY;
10487         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
10488             offset += getVerticalOffset(false);
10489         }
10490         return offset;
10491     }
10492 
10493     @Override
debug(int depth)10494     public void debug(int depth) {
10495         super.debug(depth);
10496 
10497         String output = debugIndent(depth);
10498         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
10499                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
10500                 + "} ";
10501 
10502         if (mText != null) {
10503 
10504             output += "mText=\"" + mText + "\" ";
10505             if (mLayout != null) {
10506                 output += "mLayout width=" + mLayout.getWidth()
10507                         + " height=" + mLayout.getHeight();
10508             }
10509         } else {
10510             output += "mText=NULL";
10511         }
10512         Log.d(VIEW_LOG_TAG, output);
10513     }
10514 
10515     /**
10516      * Convenience for {@link Selection#getSelectionStart}.
10517      */
10518     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()10519     public int getSelectionStart() {
10520         return Selection.getSelectionStart(getText());
10521     }
10522 
10523     /**
10524      * Convenience for {@link Selection#getSelectionEnd}.
10525      */
10526     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()10527     public int getSelectionEnd() {
10528         return Selection.getSelectionEnd(getText());
10529     }
10530 
10531     /**
10532      * Return true iff there is a selection of nonzero length inside this text view.
10533      */
hasSelection()10534     public boolean hasSelection() {
10535         final int selectionStart = getSelectionStart();
10536         final int selectionEnd = getSelectionEnd();
10537 
10538         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
10539     }
10540 
getSelectedText()10541     String getSelectedText() {
10542         if (!hasSelection()) {
10543             return null;
10544         }
10545 
10546         final int start = getSelectionStart();
10547         final int end = getSelectionEnd();
10548         return String.valueOf(
10549                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
10550     }
10551 
10552     /**
10553      * Sets the properties of this field (lines, horizontally scrolling,
10554      * transformation method) to be for a single-line input.
10555      *
10556      * @attr ref android.R.styleable#TextView_singleLine
10557      */
setSingleLine()10558     public void setSingleLine() {
10559         setSingleLine(true);
10560     }
10561 
10562     /**
10563      * Sets the properties of this field to transform input to ALL CAPS
10564      * display. This may use a "small caps" formatting if available.
10565      * This setting will be ignored if this field is editable or selectable.
10566      *
10567      * This call replaces the current transformation method. Disabling this
10568      * will not necessarily restore the previous behavior from before this
10569      * was enabled.
10570      *
10571      * @see #setTransformationMethod(TransformationMethod)
10572      * @attr ref android.R.styleable#TextView_textAllCaps
10573      */
10574     @android.view.RemotableViewMethod
setAllCaps(boolean allCaps)10575     public void setAllCaps(boolean allCaps) {
10576         if (allCaps) {
10577             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
10578         } else {
10579             setTransformationMethod(null);
10580         }
10581     }
10582 
10583     /**
10584      *
10585      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
10586      * @return Whether the current transformation method is for ALL CAPS.
10587      *
10588      * @see #setAllCaps(boolean)
10589      * @see #setTransformationMethod(TransformationMethod)
10590      */
10591     @InspectableProperty(name = "textAllCaps")
isAllCaps()10592     public boolean isAllCaps() {
10593         final TransformationMethod method = getTransformationMethod();
10594         return method != null && method instanceof AllCapsTransformationMethod;
10595     }
10596 
10597     /**
10598      * If true, sets the properties of this field (number of lines, horizontally scrolling,
10599      * transformation method) to be for a single-line input; if false, restores these to the default
10600      * conditions.
10601      *
10602      * Note that the default conditions are not necessarily those that were in effect prior this
10603      * method, and you may want to reset these properties to your custom values.
10604      *
10605      * Note that due to performance reasons, by setting single line for the EditText, the maximum
10606      * text length is set to 5000 if no other character limitation are applied.
10607      *
10608      * @attr ref android.R.styleable#TextView_singleLine
10609      */
10610     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)10611     public void setSingleLine(boolean singleLine) {
10612         // Could be used, but may break backward compatibility.
10613         // if (mSingleLine == singleLine) return;
10614         setInputTypeSingleLine(singleLine);
10615         applySingleLine(singleLine, true, true, true);
10616     }
10617 
10618     /**
10619      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
10620      * @param singleLine
10621      */
setInputTypeSingleLine(boolean singleLine)10622     private void setInputTypeSingleLine(boolean singleLine) {
10623         if (mEditor != null
10624                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
10625                         == EditorInfo.TYPE_CLASS_TEXT) {
10626             if (singleLine) {
10627                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10628             } else {
10629                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10630             }
10631         }
10632     }
10633 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)10634     private void applySingleLine(boolean singleLine, boolean applyTransformation,
10635             boolean changeMaxLines, boolean changeMaxLength) {
10636         mSingleLine = singleLine;
10637 
10638         if (singleLine) {
10639             setLines(1);
10640             setHorizontallyScrolling(true);
10641             if (applyTransformation) {
10642                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
10643             }
10644 
10645             if (!changeMaxLength) return;
10646 
10647             // Single line length filter is only applicable editable text.
10648             if (mBufferType != BufferType.EDITABLE) return;
10649 
10650             final InputFilter[] prevFilters = getFilters();
10651             for (InputFilter filter: getFilters()) {
10652                 // We don't add LengthFilter if already there.
10653                 if (filter instanceof InputFilter.LengthFilter) return;
10654             }
10655 
10656             if (mSingleLineLengthFilter == null) {
10657                 mSingleLineLengthFilter = new InputFilter.LengthFilter(
10658                     MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
10659             }
10660 
10661             final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1];
10662             System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length);
10663             newFilters[prevFilters.length] = mSingleLineLengthFilter;
10664 
10665             setFilters(newFilters);
10666 
10667             // Since filter doesn't apply to existing text, trigger filter by setting text.
10668             setText(getText());
10669         } else {
10670             if (changeMaxLines) {
10671                 setMaxLines(Integer.MAX_VALUE);
10672             }
10673             setHorizontallyScrolling(false);
10674             if (applyTransformation) {
10675                 setTransformationMethod(null);
10676             }
10677 
10678             if (!changeMaxLength) return;
10679 
10680             // Single line length filter is only applicable editable text.
10681             if (mBufferType != BufferType.EDITABLE) return;
10682 
10683             final InputFilter[] prevFilters = getFilters();
10684             if (prevFilters.length == 0) return;
10685 
10686             // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated
10687             // single line char limit filter.
10688             if (mSingleLineLengthFilter == null) return;
10689 
10690             // If we need to remove mSingleLineLengthFilter, we need to allocate another array.
10691             // Since filter list is expected to be small and want to avoid unnecessary array
10692             // allocation, check if there is mSingleLengthFilter first.
10693             int targetIndex = -1;
10694             for (int i = 0; i < prevFilters.length; ++i) {
10695                 if (prevFilters[i] == mSingleLineLengthFilter) {
10696                     targetIndex = i;
10697                     break;
10698                 }
10699             }
10700             if (targetIndex == -1) return;  // not found. Do nothing.
10701 
10702             if (prevFilters.length == 1) {
10703                 setFilters(NO_FILTERS);
10704                 return;
10705             }
10706 
10707             // Create new array which doesn't include mSingleLengthFilter.
10708             final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1];
10709             System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex);
10710             System.arraycopy(
10711                     prevFilters,
10712                     targetIndex + 1,
10713                     newFilters,
10714                     targetIndex,
10715                     prevFilters.length - targetIndex - 1);
10716             setFilters(newFilters);
10717             mSingleLineLengthFilter = null;
10718         }
10719     }
10720 
10721     /**
10722      * Causes words in the text that are longer than the view's width
10723      * to be ellipsized instead of broken in the middle.  You may also
10724      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
10725      * to constrain the text to a single line.  Use <code>null</code>
10726      * to turn off ellipsizing.
10727      *
10728      * If {@link #setMaxLines} has been used to set two or more lines,
10729      * only {@link android.text.TextUtils.TruncateAt#END} and
10730      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
10731      * (other ellipsizing types will not do anything).
10732      *
10733      * @attr ref android.R.styleable#TextView_ellipsize
10734      */
setEllipsize(TextUtils.TruncateAt where)10735     public void setEllipsize(TextUtils.TruncateAt where) {
10736         // TruncateAt is an enum. != comparison is ok between these singleton objects.
10737         if (mEllipsize != where) {
10738             mEllipsize = where;
10739 
10740             if (mLayout != null) {
10741                 nullLayouts();
10742                 requestLayout();
10743                 invalidate();
10744             }
10745         }
10746     }
10747 
10748     /**
10749      * Sets how many times to repeat the marquee animation. Only applied if the
10750      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
10751      *
10752      * @see #getMarqueeRepeatLimit()
10753      *
10754      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10755      */
setMarqueeRepeatLimit(int marqueeLimit)10756     public void setMarqueeRepeatLimit(int marqueeLimit) {
10757         mMarqueeRepeatLimit = marqueeLimit;
10758     }
10759 
10760     /**
10761      * Gets the number of times the marquee animation is repeated. Only meaningful if the
10762      * TextView has marquee enabled.
10763      *
10764      * @return the number of times the marquee animation is repeated. -1 if the animation
10765      * repeats indefinitely
10766      *
10767      * @see #setMarqueeRepeatLimit(int)
10768      *
10769      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10770      */
10771     @InspectableProperty
getMarqueeRepeatLimit()10772     public int getMarqueeRepeatLimit() {
10773         return mMarqueeRepeatLimit;
10774     }
10775 
10776     /**
10777      * Returns where, if anywhere, words that are longer than the view
10778      * is wide should be ellipsized.
10779      */
10780     @InspectableProperty
10781     @ViewDebug.ExportedProperty
getEllipsize()10782     public TextUtils.TruncateAt getEllipsize() {
10783         return mEllipsize;
10784     }
10785 
10786     /**
10787      * Set the TextView so that when it takes focus, all the text is
10788      * selected.
10789      *
10790      * @attr ref android.R.styleable#TextView_selectAllOnFocus
10791      */
10792     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)10793     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
10794         createEditorIfNeeded();
10795         mEditor.mSelectAllOnFocus = selectAllOnFocus;
10796 
10797         if (selectAllOnFocus && !(mText instanceof Spannable)) {
10798             setText(mText, BufferType.SPANNABLE);
10799         }
10800     }
10801 
10802     /**
10803      * Set whether the cursor is visible. The default is true. Note that this property only
10804      * makes sense for editable TextView. If IME is consuming the input, the cursor will always be
10805      * invisible, visibility will be updated as the last state when IME does not consume
10806      * the input anymore.
10807      *
10808      * @see #isCursorVisible()
10809      *
10810      * @attr ref android.R.styleable#TextView_cursorVisible
10811      */
10812     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)10813     public void setCursorVisible(boolean visible) {
10814         mCursorVisibleFromAttr = visible;
10815         updateCursorVisibleInternal();
10816     }
10817 
10818     /**
10819      * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput}
10820      * is {@code true}. Otherwise, make the cursor visible.
10821      *
10822      * @param imeConsumesInput {@code true} if IME is consuming the input
10823      *
10824      * @hide
10825      */
setImeConsumesInput(boolean imeConsumesInput)10826     public void setImeConsumesInput(boolean imeConsumesInput) {
10827         mImeIsConsumingInput = imeConsumesInput;
10828         updateCursorVisibleInternal();
10829     }
10830 
updateCursorVisibleInternal()10831     private void updateCursorVisibleInternal()  {
10832         boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput;
10833         if (visible && mEditor == null) return; // visible is the default value with no edit data
10834         createEditorIfNeeded();
10835         if (mEditor.mCursorVisible != visible) {
10836             mEditor.mCursorVisible = visible;
10837             invalidate();
10838 
10839             mEditor.makeBlink();
10840 
10841             // InsertionPointCursorController depends on mCursorVisible
10842             mEditor.prepareCursorControllers();
10843         }
10844     }
10845 
10846     /**
10847      * @return whether or not the cursor is visible (assuming this TextView is editable). This
10848      * method may return {@code false} when the IME is consuming the input even if the
10849      * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)}
10850      * is called.
10851      *
10852      * @see #setCursorVisible(boolean)
10853      *
10854      * @attr ref android.R.styleable#TextView_cursorVisible
10855      */
10856     @InspectableProperty
isCursorVisible()10857     public boolean isCursorVisible() {
10858         // true is the default value
10859         return mEditor == null ? true : mEditor.mCursorVisible;
10860     }
10861 
10862     /**
10863      * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}.
10864      * {@code true} is the default value.
10865      *
10866      * @see #setCursorVisible(boolean)
10867      * @hide
10868      */
isCursorVisibleFromAttr()10869     public boolean isCursorVisibleFromAttr() {
10870         return mCursorVisibleFromAttr;
10871     }
10872 
canMarquee()10873     private boolean canMarquee() {
10874         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10875         return width > 0 && (mLayout.getLineWidth(0) > width
10876                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
10877                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
10878     }
10879 
10880     /**
10881      * @hide
10882      */
10883     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()10884     protected void startMarquee() {
10885         // Do not ellipsize EditText
10886         if (getKeyListener() != null) return;
10887 
10888         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
10889             return;
10890         }
10891 
10892         if ((mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible()
10893                 && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) {
10894 
10895             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10896                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
10897                 final Layout tmp = mLayout;
10898                 mLayout = mSavedMarqueeModeLayout;
10899                 mSavedMarqueeModeLayout = tmp;
10900                 setHorizontalFadingEdgeEnabled(true);
10901                 requestLayout();
10902                 invalidate();
10903             }
10904 
10905             if (mMarquee == null) mMarquee = new Marquee(this);
10906             mMarquee.start(mMarqueeRepeatLimit);
10907         }
10908     }
10909 
10910     /**
10911      * @hide
10912      */
stopMarquee()10913     protected void stopMarquee() {
10914         if (mMarquee != null && !mMarquee.isStopped()) {
10915             mMarquee.stop();
10916         }
10917 
10918         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
10919             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10920             final Layout tmp = mSavedMarqueeModeLayout;
10921             mSavedMarqueeModeLayout = mLayout;
10922             mLayout = tmp;
10923             setHorizontalFadingEdgeEnabled(false);
10924             requestLayout();
10925             invalidate();
10926         }
10927     }
10928 
10929     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)10930     private void startStopMarquee(boolean start) {
10931         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10932             if (start) {
10933                 startMarquee();
10934             } else {
10935                 stopMarquee();
10936             }
10937         }
10938     }
10939 
10940     /**
10941      * This method is called when the text is changed, in case any subclasses
10942      * would like to know.
10943      *
10944      * Within <code>text</code>, the <code>lengthAfter</code> characters
10945      * beginning at <code>start</code> have just replaced old text that had
10946      * length <code>lengthBefore</code>. It is an error to attempt to make
10947      * changes to <code>text</code> from this callback.
10948      *
10949      * @param text The text the TextView is displaying
10950      * @param start The offset of the start of the range of the text that was
10951      * modified
10952      * @param lengthBefore The length of the former text that has been replaced
10953      * @param lengthAfter The length of the replacement modified text
10954      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10955     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
10956         // intentionally empty, template pattern method can be overridden by subclasses
10957     }
10958 
10959     /**
10960      * This method is called when the selection has changed, in case any
10961      * subclasses would like to know.
10962      * </p>
10963      * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs
10964      * the accessibility subsystem about the selection change.
10965      * </p>
10966      *
10967      * @param selStart The new selection start location.
10968      * @param selEnd The new selection end location.
10969      */
10970     @CallSuper
onSelectionChanged(int selStart, int selEnd)10971     protected void onSelectionChanged(int selStart, int selEnd) {
10972         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
10973     }
10974 
10975     /**
10976      * Adds a TextWatcher to the list of those whose methods are called
10977      * whenever this TextView's text changes.
10978      * <p>
10979      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
10980      * not called after {@link #setText} calls.  Now, doing {@link #setText}
10981      * if there are any text changed listeners forces the buffer type to
10982      * Editable if it would not otherwise be and does call this method.
10983      */
addTextChangedListener(TextWatcher watcher)10984     public void addTextChangedListener(TextWatcher watcher) {
10985         if (mListeners == null) {
10986             mListeners = new ArrayList<TextWatcher>();
10987         }
10988 
10989         mListeners.add(watcher);
10990     }
10991 
10992     /**
10993      * Removes the specified TextWatcher from the list of those whose
10994      * methods are called
10995      * whenever this TextView's text changes.
10996      */
removeTextChangedListener(TextWatcher watcher)10997     public void removeTextChangedListener(TextWatcher watcher) {
10998         if (mListeners != null) {
10999             int i = mListeners.indexOf(watcher);
11000 
11001             if (i >= 0) {
11002                 mListeners.remove(i);
11003             }
11004         }
11005     }
11006 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)11007     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
11008         if (mListeners != null) {
11009             final ArrayList<TextWatcher> list = mListeners;
11010             final int count = list.size();
11011             for (int i = 0; i < count; i++) {
11012                 list.get(i).beforeTextChanged(text, start, before, after);
11013             }
11014         }
11015 
11016         // The spans that are inside or intersect the modified region no longer make sense
11017         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
11018         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
11019     }
11020 
11021     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)11022     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
11023         if (!(mText instanceof Editable)) return;
11024         Editable text = (Editable) mText;
11025 
11026         T[] spans = text.getSpans(start, end, type);
11027         ArrayList<T> spansToRemove = new ArrayList<>();
11028         for (T span : spans) {
11029             final int spanStart = text.getSpanStart(span);
11030             final int spanEnd = text.getSpanEnd(span);
11031             if (spanEnd == start || spanStart == end) continue;
11032             spansToRemove.add(span);
11033         }
11034         for (T span : spansToRemove) {
11035             text.removeSpan(span);
11036         }
11037     }
11038 
removeAdjacentSuggestionSpans(final int pos)11039     void removeAdjacentSuggestionSpans(final int pos) {
11040         if (!(mText instanceof Editable)) return;
11041         final Editable text = (Editable) mText;
11042 
11043         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
11044         final int length = spans.length;
11045         for (int i = 0; i < length; i++) {
11046             final int spanStart = text.getSpanStart(spans[i]);
11047             final int spanEnd = text.getSpanEnd(spans[i]);
11048             if (spanEnd == pos || spanStart == pos) {
11049                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
11050                     text.removeSpan(spans[i]);
11051                 }
11052             }
11053         }
11054     }
11055 
11056     /**
11057      * Not private so it can be called from an inner class without going
11058      * through a thunk.
11059      */
sendOnTextChanged(CharSequence text, int start, int before, int after)11060     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
11061         if (mListeners != null) {
11062             final ArrayList<TextWatcher> list = mListeners;
11063             final int count = list.size();
11064             for (int i = 0; i < count; i++) {
11065                 list.get(i).onTextChanged(text, start, before, after);
11066             }
11067         }
11068 
11069         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
11070     }
11071 
11072     /**
11073      * Not private so it can be called from an inner class without going
11074      * through a thunk.
11075      */
sendAfterTextChanged(Editable text)11076     void sendAfterTextChanged(Editable text) {
11077         if (mListeners != null) {
11078             final ArrayList<TextWatcher> list = mListeners;
11079             final int count = list.size();
11080             for (int i = 0; i < count; i++) {
11081                 list.get(i).afterTextChanged(text);
11082             }
11083         }
11084 
11085         notifyListeningManagersAfterTextChanged();
11086 
11087         hideErrorIfUnchanged();
11088     }
11089 
11090     /**
11091      * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
11092      * interested on text changes.
11093      */
notifyListeningManagersAfterTextChanged()11094     private void notifyListeningManagersAfterTextChanged() {
11095 
11096         // Autofill
11097         if (isAutofillable()) {
11098             // It is important to not check whether the view is important for autofill
11099             // since the user can trigger autofill manually on not important views.
11100             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11101             if (afm != null) {
11102                 if (android.view.autofill.Helper.sVerbose) {
11103                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
11104                 }
11105                 afm.notifyValueChanged(TextView.this);
11106             }
11107         }
11108 
11109         notifyContentCaptureTextChanged();
11110     }
11111 
11112     /**
11113      * Notifies the ContentCapture service that the text of the view has changed (only if
11114      * ContentCapture has been notified of this view's existence already).
11115      *
11116      * @hide
11117      */
notifyContentCaptureTextChanged()11118     public void notifyContentCaptureTextChanged() {
11119         // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
11120         // of using isLaidout(), so it's not called in cases where it's laid out but a
11121         // notifyAppeared was not sent.
11122         if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) {
11123             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
11124             if (cm != null && cm.isContentCaptureEnabled()) {
11125                 final ContentCaptureSession session = getContentCaptureSession();
11126                 if (session != null) {
11127                     // TODO(b/111276913): pass flags when edited by user / add CTS test
11128                     session.notifyViewTextChanged(getAutofillId(), getText());
11129                 }
11130             }
11131         }
11132     }
11133 
isAutofillable()11134     private boolean isAutofillable() {
11135         // It is important to not check whether the view is important for autofill
11136         // since the user can trigger autofill manually on not important views.
11137         return getAutofillType() != AUTOFILL_TYPE_NONE;
11138     }
11139 
updateAfterEdit()11140     void updateAfterEdit() {
11141         invalidate();
11142         int curs = getSelectionStart();
11143 
11144         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
11145             registerForPreDraw();
11146         }
11147 
11148         checkForResize();
11149 
11150         if (curs >= 0) {
11151             mHighlightPathBogus = true;
11152             if (mEditor != null) mEditor.makeBlink();
11153             bringPointIntoView(curs);
11154         }
11155     }
11156 
11157     /**
11158      * Not private so it can be called from an inner class without going
11159      * through a thunk.
11160      */
handleTextChanged(CharSequence buffer, int start, int before, int after)11161     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
11162         sLastCutCopyOrTextChangedTime = 0;
11163 
11164         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
11165         if (ims == null || ims.mBatchEditNesting == 0) {
11166             updateAfterEdit();
11167         }
11168         if (ims != null) {
11169             ims.mContentChanged = true;
11170             if (ims.mChangedStart < 0) {
11171                 ims.mChangedStart = start;
11172                 ims.mChangedEnd = start + before;
11173             } else {
11174                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
11175                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
11176             }
11177             ims.mChangedDelta += after - before;
11178         }
11179         resetErrorChangedFlag();
11180         sendOnTextChanged(buffer, start, before, after);
11181         onTextChanged(buffer, start, before, after);
11182     }
11183 
11184     /**
11185      * Not private so it can be called from an inner class without going
11186      * through a thunk.
11187      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)11188     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
11189         // XXX Make the start and end move together if this ends up
11190         // spending too much time invalidating.
11191 
11192         boolean selChanged = false;
11193         int newSelStart = -1, newSelEnd = -1;
11194 
11195         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
11196 
11197         if (what == Selection.SELECTION_END) {
11198             selChanged = true;
11199             newSelEnd = newStart;
11200 
11201             if (oldStart >= 0 || newStart >= 0) {
11202                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
11203                 checkForResize();
11204                 registerForPreDraw();
11205                 if (mEditor != null) mEditor.makeBlink();
11206             }
11207         }
11208 
11209         if (what == Selection.SELECTION_START) {
11210             selChanged = true;
11211             newSelStart = newStart;
11212 
11213             if (oldStart >= 0 || newStart >= 0) {
11214                 int end = Selection.getSelectionEnd(buf);
11215                 invalidateCursor(end, oldStart, newStart);
11216             }
11217         }
11218 
11219         if (selChanged) {
11220             mHighlightPathBogus = true;
11221             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
11222 
11223             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
11224                 if (newSelStart < 0) {
11225                     newSelStart = Selection.getSelectionStart(buf);
11226                 }
11227                 if (newSelEnd < 0) {
11228                     newSelEnd = Selection.getSelectionEnd(buf);
11229                 }
11230 
11231                 if (mEditor != null) {
11232                     mEditor.refreshTextActionMode();
11233                     if (!hasSelection()
11234                             && mEditor.getTextActionMode() == null && hasTransientState()) {
11235                         // User generated selection has been removed.
11236                         setHasTransientState(false);
11237                     }
11238                 }
11239                 onSelectionChanged(newSelStart, newSelEnd);
11240             }
11241         }
11242 
11243         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
11244                 || what instanceof CharacterStyle) {
11245             if (ims == null || ims.mBatchEditNesting == 0) {
11246                 invalidate();
11247                 mHighlightPathBogus = true;
11248                 checkForResize();
11249             } else {
11250                 ims.mContentChanged = true;
11251             }
11252             if (mEditor != null) {
11253                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
11254                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
11255                 mEditor.invalidateHandlesAndActionMode();
11256             }
11257         }
11258 
11259         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
11260             mHighlightPathBogus = true;
11261             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
11262                 ims.mSelectionModeChanged = true;
11263             }
11264 
11265             if (Selection.getSelectionStart(buf) >= 0) {
11266                 if (ims == null || ims.mBatchEditNesting == 0) {
11267                     invalidateCursor();
11268                 } else {
11269                     ims.mCursorChanged = true;
11270                 }
11271             }
11272         }
11273 
11274         if (what instanceof ParcelableSpan) {
11275             // If this is a span that can be sent to a remote process,
11276             // the current extract editor would be interested in it.
11277             if (ims != null && ims.mExtractedTextRequest != null) {
11278                 if (ims.mBatchEditNesting != 0) {
11279                     if (oldStart >= 0) {
11280                         if (ims.mChangedStart > oldStart) {
11281                             ims.mChangedStart = oldStart;
11282                         }
11283                         if (ims.mChangedStart > oldEnd) {
11284                             ims.mChangedStart = oldEnd;
11285                         }
11286                     }
11287                     if (newStart >= 0) {
11288                         if (ims.mChangedStart > newStart) {
11289                             ims.mChangedStart = newStart;
11290                         }
11291                         if (ims.mChangedStart > newEnd) {
11292                             ims.mChangedStart = newEnd;
11293                         }
11294                     }
11295                 } else {
11296                     if (DEBUG_EXTRACT) {
11297                         Log.v(LOG_TAG, "Span change outside of batch: "
11298                                 + oldStart + "-" + oldEnd + ","
11299                                 + newStart + "-" + newEnd + " " + what);
11300                     }
11301                     ims.mContentChanged = true;
11302                 }
11303             }
11304         }
11305 
11306         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
11307                 && what instanceof SpellCheckSpan) {
11308             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
11309         }
11310     }
11311 
11312     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)11313     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
11314         if (isTemporarilyDetached()) {
11315             // If we are temporarily in the detach state, then do nothing.
11316             super.onFocusChanged(focused, direction, previouslyFocusedRect);
11317             return;
11318         }
11319 
11320         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
11321 
11322         if (focused) {
11323             if (mSpannable != null) {
11324                 MetaKeyKeyListener.resetMetaState(mSpannable);
11325             }
11326         }
11327 
11328         startStopMarquee(focused);
11329 
11330         if (mTransformation != null) {
11331             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
11332         }
11333 
11334         super.onFocusChanged(focused, direction, previouslyFocusedRect);
11335     }
11336 
11337     @Override
onWindowFocusChanged(boolean hasWindowFocus)11338     public void onWindowFocusChanged(boolean hasWindowFocus) {
11339         super.onWindowFocusChanged(hasWindowFocus);
11340 
11341         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
11342 
11343         startStopMarquee(hasWindowFocus);
11344     }
11345 
11346     @Override
onVisibilityChanged(View changedView, int visibility)11347     protected void onVisibilityChanged(View changedView, int visibility) {
11348         super.onVisibilityChanged(changedView, visibility);
11349         if (mEditor != null && visibility != VISIBLE) {
11350             mEditor.hideCursorAndSpanControllers();
11351             stopTextActionMode();
11352         }
11353     }
11354 
11355     @Override
onVisibilityAggregated(boolean isVisible)11356     public void onVisibilityAggregated(boolean isVisible) {
11357         super.onVisibilityAggregated(isVisible);
11358         startStopMarquee(isVisible);
11359     }
11360 
11361     /**
11362      * Use {@link BaseInputConnection#removeComposingSpans
11363      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
11364      * state from this text view.
11365      */
clearComposingText()11366     public void clearComposingText() {
11367         if (mText instanceof Spannable) {
11368             BaseInputConnection.removeComposingSpans(mSpannable);
11369         }
11370     }
11371 
11372     @Override
setSelected(boolean selected)11373     public void setSelected(boolean selected) {
11374         boolean wasSelected = isSelected();
11375 
11376         super.setSelected(selected);
11377 
11378         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
11379             if (selected) {
11380                 startMarquee();
11381             } else {
11382                 stopMarquee();
11383             }
11384         }
11385     }
11386 
11387     /**
11388      * Called from onTouchEvent() to prevent the touches by secondary fingers.
11389      * Dragging on handles can revise cursor/selection, so can dragging on the text view.
11390      * This method is a lock to avoid processing multiple fingers on both text view and handles.
11391      * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work.
11392      *
11393      * @param event The motion event that is being handled and carries the pointer info.
11394      * @param fromHandleView true if the event is delivered to selection handle or insertion
11395      * handle; false if this event is delivered to TextView.
11396      * @return Returns true to indicate that onTouchEvent() can continue processing the motion
11397      * event, otherwise false.
11398      *  - Always returns true for the first finger.
11399      *  - For secondary fingers, if the first or current finger is from TextView, returns false.
11400      *    This is to make touch mutually exclusive between the TextView and the handles, but
11401      *    not among the handles.
11402      */
isFromPrimePointer(MotionEvent event, boolean fromHandleView)11403     boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) {
11404         boolean res = true;
11405         if (mPrimePointerId == NO_POINTER_ID)  {
11406             mPrimePointerId = event.getPointerId(0);
11407             mIsPrimePointerFromHandleView = fromHandleView;
11408         } else if (mPrimePointerId != event.getPointerId(0)) {
11409             res = mIsPrimePointerFromHandleView && fromHandleView;
11410         }
11411         if (event.getActionMasked() == MotionEvent.ACTION_UP
11412             || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
11413             mPrimePointerId = -1;
11414         }
11415         return res;
11416     }
11417 
11418     @Override
onTouchEvent(MotionEvent event)11419     public boolean onTouchEvent(MotionEvent event) {
11420         if (DEBUG_CURSOR) {
11421             logCursor("onTouchEvent", "%d: %s (%f,%f)",
11422                     event.getSequenceNumber(),
11423                     MotionEvent.actionToString(event.getActionMasked()),
11424                     event.getX(), event.getY());
11425         }
11426         final int action = event.getActionMasked();
11427         if (mEditor != null) {
11428             if (!isFromPrimePointer(event, false)) {
11429                 return true;
11430             }
11431 
11432             mEditor.onTouchEvent(event);
11433 
11434             if (mEditor.mInsertionPointCursorController != null
11435                     && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
11436                 return true;
11437             }
11438             if (mEditor.mSelectionModifierCursorController != null
11439                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
11440                 return true;
11441             }
11442         }
11443 
11444         final boolean superResult = super.onTouchEvent(event);
11445         if (DEBUG_CURSOR) {
11446             logCursor("onTouchEvent", "superResult=%s", superResult);
11447         }
11448 
11449         /*
11450          * Don't handle the release after a long press, because it will move the selection away from
11451          * whatever the menu action was trying to affect. If the long press should have triggered an
11452          * insertion action mode, we can now actually show it.
11453          */
11454         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
11455             mEditor.mDiscardNextActionUp = false;
11456             if (DEBUG_CURSOR) {
11457                 logCursor("onTouchEvent", "release after long press detected");
11458             }
11459             if (mEditor.mIsInsertionActionModeStartPending) {
11460                 mEditor.startInsertionActionMode();
11461                 mEditor.mIsInsertionActionModeStartPending = false;
11462             }
11463             return superResult;
11464         }
11465 
11466         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
11467                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
11468 
11469         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
11470                 && mText instanceof Spannable && mLayout != null) {
11471             boolean handled = false;
11472 
11473             if (mMovement != null) {
11474                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
11475             }
11476 
11477             final boolean textIsSelectable = isTextSelectable();
11478             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
11479                 // The LinkMovementMethod which should handle taps on links has not been installed
11480                 // on non editable text that support text selection.
11481                 // We reproduce its behavior here to open links for these.
11482                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
11483                     getSelectionEnd(), ClickableSpan.class);
11484 
11485                 if (links.length > 0) {
11486                     links[0].onClick(this);
11487                     handled = true;
11488                 }
11489             }
11490 
11491             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
11492                 // Show the IME, except when selecting in read-only text.
11493                 final InputMethodManager imm = getInputMethodManager();
11494                 viewClicked(imm);
11495                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null
11496                         && !showAutofillDialog()) {
11497                     imm.showSoftInput(this, 0);
11498                 }
11499 
11500                 // The above condition ensures that the mEditor is not null
11501                 mEditor.onTouchUpEvent(event);
11502 
11503                 handled = true;
11504             }
11505 
11506             if (handled) {
11507                 return true;
11508             }
11509         }
11510 
11511         return superResult;
11512     }
11513 
11514     /**
11515      * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI.
11516      * If autofill suggestions are available when the user clicks on a field that supports filling
11517      * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area
11518      * to display the datasets, so it is easy for users to pay attention to the datasets and
11519      * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better
11520      * experience is not to show the IME if there is a fill dialog.
11521      */
showAutofillDialog()11522     private boolean showAutofillDialog() {
11523         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11524         if (afm != null) {
11525             return afm.showAutofillDialog(this);
11526         }
11527         return false;
11528     }
11529 
11530     @Override
onGenericMotionEvent(MotionEvent event)11531     public boolean onGenericMotionEvent(MotionEvent event) {
11532         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
11533             try {
11534                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
11535                     return true;
11536                 }
11537             } catch (AbstractMethodError ex) {
11538                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
11539                 // Ignore its absence in case third party applications implemented the
11540                 // interface directly.
11541             }
11542         }
11543         return super.onGenericMotionEvent(event);
11544     }
11545 
11546     @Override
onCreateContextMenu(ContextMenu menu)11547     protected void onCreateContextMenu(ContextMenu menu) {
11548         if (mEditor != null) {
11549             mEditor.onCreateContextMenu(menu);
11550         }
11551     }
11552 
11553     @Override
showContextMenu()11554     public boolean showContextMenu() {
11555         if (mEditor != null) {
11556             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
11557         }
11558         return super.showContextMenu();
11559     }
11560 
11561     @Override
showContextMenu(float x, float y)11562     public boolean showContextMenu(float x, float y) {
11563         if (mEditor != null) {
11564             mEditor.setContextMenuAnchor(x, y);
11565         }
11566         return super.showContextMenu(x, y);
11567     }
11568 
11569     /**
11570      * @return True iff this TextView contains a text that can be edited, or if this is
11571      * a selectable TextView.
11572      */
11573     @UnsupportedAppUsage
isTextEditable()11574     boolean isTextEditable() {
11575         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
11576     }
11577 
11578     /**
11579      * Returns true, only while processing a touch gesture, if the initial
11580      * touch down event caused focus to move to the text view and as a result
11581      * its selection changed.  Only valid while processing the touch gesture
11582      * of interest, in an editable text view.
11583      */
didTouchFocusSelect()11584     public boolean didTouchFocusSelect() {
11585         return mEditor != null && mEditor.mTouchFocusSelected;
11586     }
11587 
11588     @Override
cancelLongPress()11589     public void cancelLongPress() {
11590         super.cancelLongPress();
11591         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
11592     }
11593 
11594     @Override
onTrackballEvent(MotionEvent event)11595     public boolean onTrackballEvent(MotionEvent event) {
11596         if (mMovement != null && mSpannable != null && mLayout != null) {
11597             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
11598                 return true;
11599             }
11600         }
11601 
11602         return super.onTrackballEvent(event);
11603     }
11604 
11605     /**
11606      * Sets the Scroller used for producing a scrolling animation
11607      *
11608      * @param s A Scroller instance
11609      */
setScroller(Scroller s)11610     public void setScroller(Scroller s) {
11611         mScroller = s;
11612     }
11613 
11614     @Override
getLeftFadingEdgeStrength()11615     protected float getLeftFadingEdgeStrength() {
11616         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11617             final Marquee marquee = mMarquee;
11618             if (marquee.shouldDrawLeftFade()) {
11619                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
11620             } else {
11621                 return 0.0f;
11622             }
11623         } else if (getLineCount() == 1) {
11624             final float lineLeft = getLayout().getLineLeft(0);
11625             if (lineLeft > mScrollX) return 0.0f;
11626             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
11627         }
11628         return super.getLeftFadingEdgeStrength();
11629     }
11630 
11631     @Override
getRightFadingEdgeStrength()11632     protected float getRightFadingEdgeStrength() {
11633         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11634             final Marquee marquee = mMarquee;
11635             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
11636         } else if (getLineCount() == 1) {
11637             final float rightEdge = mScrollX +
11638                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
11639             final float lineRight = getLayout().getLineRight(0);
11640             if (lineRight < rightEdge) return 0.0f;
11641             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
11642         }
11643         return super.getRightFadingEdgeStrength();
11644     }
11645 
11646     /**
11647      * Calculates the fading edge strength as the ratio of the distance between two
11648      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
11649      * value for the distance calculation.
11650      *
11651      * @param position1 A horizontal position.
11652      * @param position2 A horizontal position.
11653      * @return Fading edge strength between [0.0f, 1.0f].
11654      */
11655     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)11656     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
11657         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
11658         if (horizontalFadingEdgeLength == 0) return 0.0f;
11659         final float diff = Math.abs(position1 - position2);
11660         if (diff > horizontalFadingEdgeLength) return 1.0f;
11661         return diff / horizontalFadingEdgeLength;
11662     }
11663 
isMarqueeFadeEnabled()11664     private boolean isMarqueeFadeEnabled() {
11665         return mEllipsize == TextUtils.TruncateAt.MARQUEE
11666                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
11667     }
11668 
11669     @Override
computeHorizontalScrollRange()11670     protected int computeHorizontalScrollRange() {
11671         if (mLayout != null) {
11672             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
11673                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
11674         }
11675 
11676         return super.computeHorizontalScrollRange();
11677     }
11678 
11679     @Override
computeVerticalScrollRange()11680     protected int computeVerticalScrollRange() {
11681         if (mLayout != null) {
11682             return mLayout.getHeight();
11683         }
11684         return super.computeVerticalScrollRange();
11685     }
11686 
11687     @Override
computeVerticalScrollExtent()11688     protected int computeVerticalScrollExtent() {
11689         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
11690     }
11691 
11692     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11693     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
11694         super.findViewsWithText(outViews, searched, flags);
11695         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
11696                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
11697             String searchedLowerCase = searched.toString().toLowerCase();
11698             String textLowerCase = mText.toString().toLowerCase();
11699             if (textLowerCase.contains(searchedLowerCase)) {
11700                 outViews.add(this);
11701             }
11702         }
11703     }
11704 
11705     /**
11706      * Type of the text buffer that defines the characteristics of the text such as static,
11707      * styleable, or editable.
11708      */
11709     public enum BufferType {
11710         NORMAL, SPANNABLE, EDITABLE
11711     }
11712 
11713     /**
11714      * Returns the TextView_textColor attribute from the TypedArray, if set, or
11715      * the TextAppearance_textColor from the TextView_textAppearance attribute,
11716      * if TextView_textColor was not set directly.
11717      *
11718      * @removed
11719      */
getTextColors(Context context, TypedArray attrs)11720     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
11721         if (attrs == null) {
11722             // Preserve behavior prior to removal of this API.
11723             throw new NullPointerException();
11724         }
11725 
11726         // It's not safe to use this method from apps. The parameter 'attrs'
11727         // must have been obtained using the TextView filter array which is not
11728         // available to the SDK. As such, we grab a default TypedArray with the
11729         // right filter instead here.
11730         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
11731         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
11732         if (colors == null) {
11733             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
11734             if (ap != 0) {
11735                 final TypedArray appearance = context.obtainStyledAttributes(
11736                         ap, R.styleable.TextAppearance);
11737                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
11738                 appearance.recycle();
11739             }
11740         }
11741         a.recycle();
11742 
11743         return colors;
11744     }
11745 
11746     /**
11747      * Returns the default color from the TextView_textColor attribute from the
11748      * AttributeSet, if set, or the default color from the
11749      * TextAppearance_textColor from the TextView_textAppearance attribute, if
11750      * TextView_textColor was not set directly.
11751      *
11752      * @removed
11753      */
getTextColor(Context context, TypedArray attrs, int def)11754     public static int getTextColor(Context context, TypedArray attrs, int def) {
11755         final ColorStateList colors = getTextColors(context, attrs);
11756         if (colors == null) {
11757             return def;
11758         } else {
11759             return colors.getDefaultColor();
11760         }
11761     }
11762 
11763     @Override
onKeyShortcut(int keyCode, KeyEvent event)11764     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
11765         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
11766             // Handle Ctrl-only shortcuts.
11767             switch (keyCode) {
11768                 case KeyEvent.KEYCODE_A:
11769                     if (canSelectText()) {
11770                         return onTextContextMenuItem(ID_SELECT_ALL);
11771                     }
11772                     break;
11773                 case KeyEvent.KEYCODE_Z:
11774                     if (canUndo()) {
11775                         return onTextContextMenuItem(ID_UNDO);
11776                     }
11777                     break;
11778                 case KeyEvent.KEYCODE_X:
11779                     if (canCut()) {
11780                         return onTextContextMenuItem(ID_CUT);
11781                     }
11782                     break;
11783                 case KeyEvent.KEYCODE_C:
11784                     if (canCopy()) {
11785                         return onTextContextMenuItem(ID_COPY);
11786                     }
11787                     break;
11788                 case KeyEvent.KEYCODE_V:
11789                     if (canPaste()) {
11790                         return onTextContextMenuItem(ID_PASTE);
11791                     }
11792                     break;
11793             }
11794         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
11795             // Handle Ctrl-Shift shortcuts.
11796             switch (keyCode) {
11797                 case KeyEvent.KEYCODE_Z:
11798                     if (canRedo()) {
11799                         return onTextContextMenuItem(ID_REDO);
11800                     }
11801                     break;
11802                 case KeyEvent.KEYCODE_V:
11803                     if (canPaste()) {
11804                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
11805                     }
11806             }
11807         }
11808         return super.onKeyShortcut(keyCode, event);
11809     }
11810 
11811     /**
11812      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
11813      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
11814      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
11815      * sufficient.
11816      */
canSelectText()11817     boolean canSelectText() {
11818         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
11819     }
11820 
11821     /**
11822      * Test based on the <i>intrinsic</i> charateristics of the TextView.
11823      * The text must be spannable and the movement method must allow for arbitary selection.
11824      *
11825      * See also {@link #canSelectText()}.
11826      */
textCanBeSelected()11827     boolean textCanBeSelected() {
11828         // prepareCursorController() relies on this method.
11829         // If you change this condition, make sure prepareCursorController is called anywhere
11830         // the value of this condition might be changed.
11831         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
11832         return isTextEditable()
11833                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
11834     }
11835 
11836     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)11837     private Locale getTextServicesLocale(boolean allowNullLocale) {
11838         // Start fetching the text services locale asynchronously.
11839         updateTextServicesLocaleAsync();
11840         // If !allowNullLocale and there is no cached text services locale, just return the default
11841         // locale.
11842         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
11843                 : mCurrentSpellCheckerLocaleCache;
11844     }
11845 
11846     /**
11847      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
11848      * this {@link TextView}.
11849      *
11850      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
11851      * other apps may need to set this so that the system can user right user's resources and
11852      * services such as input methods and spell checkers.</p>
11853      *
11854      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
11855      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
11856      * @hide
11857      */
11858     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)11859     public final void setTextOperationUser(@Nullable UserHandle user) {
11860         if (Objects.equals(mTextOperationUser, user)) {
11861             return;
11862         }
11863         if (user != null && !Process.myUserHandle().equals(user)) {
11864             // Just for preventing people from accidentally using this hidden API without
11865             // the required permission.  The same permission is also checked in the system server.
11866             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
11867                     != PackageManager.PERMISSION_GRANTED) {
11868                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
11869                         + " userId=" + user.getIdentifier()
11870                         + " callingUserId" + UserHandle.myUserId());
11871             }
11872         }
11873         mTextOperationUser = user;
11874         // Invalidate some resources
11875         mCurrentSpellCheckerLocaleCache = null;
11876         if (mEditor != null) {
11877             mEditor.onTextOperationUserChanged();
11878         }
11879     }
11880 
11881     @Nullable
getTextServicesManagerForUser()11882     final TextServicesManager getTextServicesManagerForUser() {
11883         return getServiceManagerForUser("android", TextServicesManager.class);
11884     }
11885 
11886     @Nullable
getClipboardManagerForUser()11887     final ClipboardManager getClipboardManagerForUser() {
11888         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
11889     }
11890 
11891     @Nullable
getTextClassificationManagerForUser()11892     final TextClassificationManager getTextClassificationManagerForUser() {
11893         return getServiceManagerForUser(
11894                 getContext().getPackageName(), TextClassificationManager.class);
11895     }
11896 
11897     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)11898     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
11899         if (mTextOperationUser == null) {
11900             return getContext().getSystemService(managerClazz);
11901         }
11902         try {
11903             Context context = getContext().createPackageContextAsUser(
11904                     packageName, 0 /* flags */, mTextOperationUser);
11905             return context.getSystemService(managerClazz);
11906         } catch (PackageManager.NameNotFoundException e) {
11907             return null;
11908         }
11909     }
11910 
11911     /**
11912      * Starts {@link Activity} as a text-operation user if it is specified with
11913      * {@link #setTextOperationUser(UserHandle)}.
11914      *
11915      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
11916      *
11917      * @param intent The description of the activity to start.
11918      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11919     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
11920         if (mTextOperationUser != null) {
11921             getContext().startActivityAsUser(intent, mTextOperationUser);
11922         } else {
11923             getContext().startActivity(intent);
11924         }
11925     }
11926 
11927     /**
11928      * This is a temporary method. Future versions may support multi-locale text.
11929      * Caveat: This method may not return the latest text services locale, but this should be
11930      * acceptable and it's more important to make this method asynchronous.
11931      *
11932      * @return The locale that should be used for a word iterator
11933      * in this TextView, based on the current spell checker settings,
11934      * the current IME's locale, or the system default locale.
11935      * Please note that a word iterator in this TextView is different from another word iterator
11936      * used by SpellChecker.java of TextView. This method should be used for the former.
11937      * @hide
11938      */
11939     // TODO: Support multi-locale
11940     // TODO: Update the text services locale immediately after the keyboard locale is switched
11941     // by catching intent of keyboard switch event
getTextServicesLocale()11942     public Locale getTextServicesLocale() {
11943         return getTextServicesLocale(false /* allowNullLocale */);
11944     }
11945 
11946     /**
11947      * @return {@code true} if this TextView is specialized for showing and interacting with the
11948      * extracted text in a full-screen input method.
11949      * @hide
11950      */
isInExtractedMode()11951     public boolean isInExtractedMode() {
11952         return false;
11953     }
11954 
11955     /**
11956      * @return {@code true} if this widget supports auto-sizing text and has been configured to
11957      * auto-size.
11958      */
isAutoSizeEnabled()11959     private boolean isAutoSizeEnabled() {
11960         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
11961     }
11962 
11963     /**
11964      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
11965      * @hide
11966      */
supportsAutoSizeText()11967     protected boolean supportsAutoSizeText() {
11968         return true;
11969     }
11970 
11971     /**
11972      * This is a temporary method. Future versions may support multi-locale text.
11973      * Caveat: This method may not return the latest spell checker locale, but this should be
11974      * acceptable and it's more important to make this method asynchronous.
11975      *
11976      * @return The locale that should be used for a spell checker in this TextView,
11977      * based on the current spell checker settings, the current IME's locale, or the system default
11978      * locale.
11979      * @hide
11980      */
getSpellCheckerLocale()11981     public Locale getSpellCheckerLocale() {
11982         return getTextServicesLocale(true /* allowNullLocale */);
11983     }
11984 
updateTextServicesLocaleAsync()11985     private void updateTextServicesLocaleAsync() {
11986         // AsyncTask.execute() uses a serial executor which means we don't have
11987         // to lock around updateTextServicesLocaleLocked() to prevent it from
11988         // being executed n times in parallel.
11989         AsyncTask.execute(new Runnable() {
11990             @Override
11991             public void run() {
11992                 updateTextServicesLocaleLocked();
11993             }
11994         });
11995     }
11996 
11997     @UnsupportedAppUsage
updateTextServicesLocaleLocked()11998     private void updateTextServicesLocaleLocked() {
11999         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
12000         if (textServicesManager == null) {
12001             return;
12002         }
12003         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
12004         final Locale locale;
12005         if (subtype != null) {
12006             locale = subtype.getLocaleObject();
12007         } else {
12008             locale = null;
12009         }
12010         mCurrentSpellCheckerLocaleCache = locale;
12011     }
12012 
onLocaleChanged()12013     void onLocaleChanged() {
12014         mEditor.onLocaleChanged();
12015     }
12016 
12017     /**
12018      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
12019      * Made available to achieve a consistent behavior.
12020      * @hide
12021      */
getWordIterator()12022     public WordIterator getWordIterator() {
12023         if (mEditor != null) {
12024             return mEditor.getWordIterator();
12025         } else {
12026             return null;
12027         }
12028     }
12029 
12030     /** @hide */
12031     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)12032     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
12033         super.onPopulateAccessibilityEventInternal(event);
12034 
12035         final CharSequence text = getTextForAccessibility();
12036         if (!TextUtils.isEmpty(text)) {
12037             event.getText().add(text);
12038         }
12039     }
12040 
12041     @Override
getAccessibilityClassName()12042     public CharSequence getAccessibilityClassName() {
12043         return TextView.class.getName();
12044     }
12045 
12046     /** @hide */
12047     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)12048     protected void onProvideStructure(@NonNull ViewStructure structure,
12049             @ViewStructureType int viewFor, int flags) {
12050         super.onProvideStructure(structure, viewFor, flags);
12051 
12052         final boolean isPassword = hasPasswordTransformationMethod()
12053                 || isPasswordInputType(getInputType());
12054         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
12055                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
12056             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
12057                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
12058             }
12059             if (mTextId != Resources.ID_NULL) {
12060                 try {
12061                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
12062                 } catch (Resources.NotFoundException e) {
12063                     if (android.view.autofill.Helper.sVerbose) {
12064                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
12065                                 + mTextId + ": " + e.getMessage());
12066                     }
12067                 }
12068             }
12069             String[] mimeTypes = getReceiveContentMimeTypes();
12070             if (mimeTypes == null && mEditor != null) {
12071                 // If the app hasn't set a listener for receiving content on this view (ie,
12072                 // getReceiveContentMimeTypes() returns null), check if it implements the
12073                 // keyboard image API and, if possible, use those MIME types as fallback.
12074                 // This fallback is only in place for autofill, not other mechanisms for
12075                 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER
12076                 // in TextViewOnReceiveContentListener for more info.
12077                 mimeTypes = mEditor.getDefaultOnReceiveContentListener()
12078                         .getFallbackMimeTypesForAutofill(this);
12079             }
12080             structure.setReceiveContentMimeTypes(mimeTypes);
12081         }
12082 
12083         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
12084                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
12085             if (mLayout == null) {
12086                 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
12087                     Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
12088                 }
12089                 assumeLayout();
12090             }
12091             Layout layout = mLayout;
12092             final int lineCount = layout.getLineCount();
12093             if (lineCount <= 1) {
12094                 // Simple case: this is a single line.
12095                 final CharSequence text = getText();
12096                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
12097                     structure.setText(text);
12098                 } else {
12099                     structure.setText(text, getSelectionStart(), getSelectionEnd());
12100                 }
12101             } else {
12102                 // Complex case: multi-line, could be scrolled or within a scroll container
12103                 // so some lines are not visible.
12104                 final int[] tmpCords = new int[2];
12105                 getLocationInWindow(tmpCords);
12106                 final int topWindowLocation = tmpCords[1];
12107                 View root = this;
12108                 ViewParent viewParent = getParent();
12109                 while (viewParent instanceof View) {
12110                     root = (View) viewParent;
12111                     viewParent = root.getParent();
12112                 }
12113                 final int windowHeight = root.getHeight();
12114                 final int topLine;
12115                 final int bottomLine;
12116                 if (topWindowLocation >= 0) {
12117                     // The top of the view is fully within its window; start text at line 0.
12118                     topLine = getLineAtCoordinateUnclamped(0);
12119                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
12120                 } else {
12121                     // The top of hte window has scrolled off the top of the window; figure out
12122                     // the starting line for this.
12123                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
12124                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
12125                 }
12126                 // We want to return some contextual lines above/below the lines that are
12127                 // actually visible.
12128                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
12129                 if (expandedTopLine < 0) {
12130                     expandedTopLine = 0;
12131                 }
12132                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
12133                 if (expandedBottomLine >= lineCount) {
12134                     expandedBottomLine = lineCount - 1;
12135                 }
12136 
12137                 // Convert lines into character offsets.
12138                 int expandedTopChar = layout.getLineStart(expandedTopLine);
12139                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
12140 
12141                 // Take into account selection -- if there is a selection, we need to expand
12142                 // the text we are returning to include that selection.
12143                 final int selStart = getSelectionStart();
12144                 final int selEnd = getSelectionEnd();
12145                 if (selStart < selEnd) {
12146                     if (selStart < expandedTopChar) {
12147                         expandedTopChar = selStart;
12148                     }
12149                     if (selEnd > expandedBottomChar) {
12150                         expandedBottomChar = selEnd;
12151                     }
12152                 }
12153 
12154                 // Get the text and trim it to the range we are reporting.
12155                 CharSequence text = getText();
12156 
12157                 if (text != null) {
12158                     if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
12159                         // Cap the offsets to avoid an OOB exception. That can happen if the
12160                         // displayed/layout text, on which these offsets are calculated, is longer
12161                         // than the original text (such as when the view is translated by the
12162                         // platform intelligence).
12163                         // TODO(b/196433694): Figure out how to better handle the offset
12164                         // calculations for this case (so we don't unnecessarily cutoff the original
12165                         // text, for example).
12166                         expandedTopChar = Math.min(expandedTopChar, text.length());
12167                         expandedBottomChar = Math.min(expandedBottomChar, text.length());
12168                         text = text.subSequence(expandedTopChar, expandedBottomChar);
12169                     }
12170 
12171                     if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
12172                         structure.setText(text);
12173                     } else {
12174                         structure.setText(text,
12175                                 selStart - expandedTopChar,
12176                                 selEnd - expandedTopChar);
12177 
12178                         final int[] lineOffsets = new int[bottomLine - topLine + 1];
12179                         final int[] lineBaselines = new int[bottomLine - topLine + 1];
12180                         final int baselineOffset = getBaselineOffset();
12181                         for (int i = topLine; i <= bottomLine; i++) {
12182                             lineOffsets[i - topLine] = layout.getLineStart(i);
12183                             lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
12184                         }
12185                         structure.setTextLines(lineOffsets, lineBaselines);
12186                     }
12187                 }
12188             }
12189 
12190             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
12191                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
12192                 // Extract style information that applies to the TextView as a whole.
12193                 int style = 0;
12194                 int typefaceStyle = getTypefaceStyle();
12195                 if ((typefaceStyle & Typeface.BOLD) != 0) {
12196                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
12197                 }
12198                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
12199                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
12200                 }
12201 
12202                 // Global styles can also be set via TextView.setPaintFlags().
12203                 int paintFlags = mTextPaint.getFlags();
12204                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
12205                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
12206                 }
12207                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
12208                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
12209                 }
12210                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
12211                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
12212                 }
12213 
12214                 // TextView does not have its own text background color. A background is either part
12215                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
12216                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
12217                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
12218             }
12219             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
12220                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
12221                 structure.setMinTextEms(getMinEms());
12222                 structure.setMaxTextEms(getMaxEms());
12223                 int maxLength = -1;
12224                 for (InputFilter filter: getFilters()) {
12225                     if (filter instanceof InputFilter.LengthFilter) {
12226                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
12227                         break;
12228                     }
12229                 }
12230                 structure.setMaxTextLength(maxLength);
12231             }
12232         }
12233         if (mHintId != Resources.ID_NULL) {
12234             try {
12235                 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId));
12236             } catch (Resources.NotFoundException e) {
12237                 if (android.view.autofill.Helper.sVerbose) {
12238                     Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id "
12239                             + mHintId + ": " + e.getMessage());
12240                 }
12241             }
12242         }
12243         structure.setHint(getHint());
12244         structure.setInputType(getInputType());
12245     }
12246 
canRequestAutofill()12247     boolean canRequestAutofill() {
12248         if (!isAutofillable()) {
12249             return false;
12250         }
12251         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
12252         if (afm != null) {
12253             return afm.isEnabled();
12254         }
12255         return false;
12256     }
12257 
requestAutofill()12258     private void requestAutofill() {
12259         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
12260         if (afm != null) {
12261             afm.requestAutofill(this);
12262         }
12263     }
12264 
12265     @Override
autofill(AutofillValue value)12266     public void autofill(AutofillValue value) {
12267         if (!isTextEditable()) {
12268             Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
12269             return;
12270         }
12271         if (!value.isText()) {
12272             Log.w(LOG_TAG, "value of type " + value.describeContents()
12273                     + " cannot be autofilled into " + this);
12274             return;
12275         }
12276         final ClipData clip = ClipData.newPlainText("", value.getTextValue());
12277         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build();
12278         performReceiveContent(payload);
12279     }
12280 
12281     @Override
getAutofillType()12282     public @AutofillType int getAutofillType() {
12283         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
12284     }
12285 
12286     /**
12287      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
12288      * {@code char}s if longer.
12289      *
12290      * @return current text, {@code null} if the text is not editable
12291      *
12292      * @see View#getAutofillValue()
12293      */
12294     @Override
12295     @Nullable
getAutofillValue()12296     public AutofillValue getAutofillValue() {
12297         if (isTextEditable()) {
12298             final CharSequence text = TextUtils.trimToParcelableSize(getText());
12299             return AutofillValue.forText(text);
12300         }
12301         return null;
12302     }
12303 
12304     /** @hide */
12305     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)12306     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
12307         super.onInitializeAccessibilityEventInternal(event);
12308 
12309         final boolean isPassword = hasPasswordTransformationMethod();
12310         event.setPassword(isPassword);
12311 
12312         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
12313             event.setFromIndex(Selection.getSelectionStart(mText));
12314             event.setToIndex(Selection.getSelectionEnd(mText));
12315             event.setItemCount(mText.length());
12316         }
12317     }
12318 
12319     /** @hide */
12320     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)12321     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
12322         super.onInitializeAccessibilityNodeInfoInternal(info);
12323 
12324         final boolean isPassword = hasPasswordTransformationMethod();
12325         info.setPassword(isPassword);
12326         info.setText(getTextForAccessibility());
12327         info.setHintText(mHint);
12328         info.setShowingHintText(isShowingHint());
12329 
12330         if (mBufferType == BufferType.EDITABLE) {
12331             info.setEditable(true);
12332             if (isEnabled()) {
12333                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
12334             }
12335         }
12336 
12337         if (mEditor != null) {
12338             info.setInputType(mEditor.mInputType);
12339 
12340             if (mEditor.mError != null) {
12341                 info.setContentInvalid(true);
12342                 info.setError(mEditor.mError);
12343             }
12344             // TextView will expose this action if it is editable and has focus.
12345             if (isTextEditable() && isFocused()) {
12346                 CharSequence imeActionLabel = mContext.getResources().getString(
12347                         com.android.internal.R.string.keyboardview_keycode_enter);
12348                 if (getImeActionLabel() != null) {
12349                     imeActionLabel = getImeActionLabel();
12350                 }
12351                 AccessibilityNodeInfo.AccessibilityAction action =
12352                         new AccessibilityNodeInfo.AccessibilityAction(
12353                                 R.id.accessibilityActionImeEnter, imeActionLabel);
12354                 info.addAction(action);
12355             }
12356         }
12357 
12358         if (!TextUtils.isEmpty(mText)) {
12359             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
12360             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
12361             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
12362                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
12363                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
12364                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
12365                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
12366             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
12367             info.setAvailableExtraData(Arrays.asList(
12368                     EXTRA_DATA_RENDERING_INFO_KEY,
12369                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
12370             ));
12371             info.setTextSelectable(isTextSelectable() || isTextEditable());
12372         } else {
12373             info.setAvailableExtraData(Arrays.asList(
12374                     EXTRA_DATA_RENDERING_INFO_KEY
12375             ));
12376         }
12377 
12378         if (isFocused()) {
12379             if (canCopy()) {
12380                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
12381             }
12382             if (canPaste()) {
12383                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
12384             }
12385             if (canCut()) {
12386                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
12387             }
12388             if (canReplace()) {
12389                 info.addAction(
12390                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS);
12391             }
12392             if (canShare()) {
12393                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
12394                         ACCESSIBILITY_ACTION_SHARE,
12395                         getResources().getString(com.android.internal.R.string.share)));
12396             }
12397             if (canProcessText()) {  // also implies mEditor is not null.
12398                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
12399                 mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info);
12400             }
12401         }
12402 
12403         // Check for known input filter types.
12404         final int numFilters = mFilters.length;
12405         for (int i = 0; i < numFilters; i++) {
12406             final InputFilter filter = mFilters[i];
12407             if (filter instanceof InputFilter.LengthFilter) {
12408                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
12409             }
12410         }
12411 
12412         if (!isSingleLine()) {
12413             info.setMultiLine(true);
12414         }
12415 
12416         // A view should not be exposed as clickable/long-clickable to a service because of a
12417         // LinkMovementMethod or because it has selectable and non-editable text.
12418         if ((info.isClickable() || info.isLongClickable())
12419                 && (mMovement instanceof LinkMovementMethod
12420                 || (isTextSelectable() && !isTextEditable()))) {
12421             if (!hasOnClickListeners()) {
12422                 info.setClickable(false);
12423                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
12424             }
12425             if (!hasOnLongClickListeners()) {
12426                 info.setLongClickable(false);
12427                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
12428             }
12429         }
12430     }
12431 
12432     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)12433     public void addExtraDataToAccessibilityNodeInfo(
12434             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
12435         if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
12436             int positionInfoStartIndex = arguments.getInt(
12437                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
12438             int positionInfoLength = arguments.getInt(
12439                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
12440             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
12441                     || (positionInfoStartIndex >= mText.length())) {
12442                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
12443                 return;
12444             }
12445             RectF[] boundingRects = new RectF[positionInfoLength];
12446             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
12447             populateCharacterBounds(builder, positionInfoStartIndex,
12448                     positionInfoStartIndex + positionInfoLength,
12449                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
12450             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
12451             for (int i = 0; i < positionInfoLength; i++) {
12452                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
12453                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
12454                     RectF bounds = cursorAnchorInfo
12455                             .getCharacterBounds(positionInfoStartIndex + i);
12456                     if (bounds != null) {
12457                         mapRectFromViewToScreenCoords(bounds, true);
12458                         boundingRects[i] = bounds;
12459                     }
12460                 }
12461             }
12462             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
12463             return;
12464         }
12465         if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) {
12466             final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo =
12467                     AccessibilityNodeInfo.ExtraRenderingInfo.obtain();
12468             extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height);
12469             extraRenderingInfo.setTextSizeInPx(getTextSize());
12470             extraRenderingInfo.setTextSizeUnit(getTextSizeUnit());
12471             info.setExtraRenderingInfo(extraRenderingInfo);
12472         }
12473     }
12474 
12475     /**
12476      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
12477      *
12478      * @param builder The builder to populate
12479      * @param startIndex The starting character index to populate
12480      * @param endIndex The ending character index to populate
12481      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
12482      * content
12483      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
12484      * @hide
12485      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)12486     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
12487             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
12488             float viewportToContentVerticalOffset) {
12489         final int minLine = mLayout.getLineForOffset(startIndex);
12490         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
12491         for (int line = minLine; line <= maxLine; ++line) {
12492             final int lineStart = mLayout.getLineStart(line);
12493             final int lineEnd = mLayout.getLineEnd(line);
12494             final int offsetStart = Math.max(lineStart, startIndex);
12495             final int offsetEnd = Math.min(lineEnd, endIndex);
12496             final boolean ltrLine =
12497                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
12498             final float[] widths = new float[offsetEnd - offsetStart];
12499             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
12500             final float top = mLayout.getLineTop(line);
12501             final float bottom = mLayout.getLineBottom(line);
12502             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
12503                 final float charWidth = widths[offset - offsetStart];
12504                 final boolean isRtl = mLayout.isRtlCharAt(offset);
12505                 final float primary = mLayout.getPrimaryHorizontal(offset);
12506                 final float secondary = mLayout.getSecondaryHorizontal(offset);
12507                 // TODO: This doesn't work perfectly for text with custom styles and
12508                 // TAB chars.
12509                 final float left;
12510                 final float right;
12511                 if (ltrLine) {
12512                     if (isRtl) {
12513                         left = secondary - charWidth;
12514                         right = secondary;
12515                     } else {
12516                         left = primary;
12517                         right = primary + charWidth;
12518                     }
12519                 } else {
12520                     if (!isRtl) {
12521                         left = secondary;
12522                         right = secondary + charWidth;
12523                     } else {
12524                         left = primary - charWidth;
12525                         right = primary;
12526                     }
12527                 }
12528                 // TODO: Check top-right and bottom-left as well.
12529                 final float localLeft = left + viewportToContentHorizontalOffset;
12530                 final float localRight = right + viewportToContentHorizontalOffset;
12531                 final float localTop = top + viewportToContentVerticalOffset;
12532                 final float localBottom = bottom + viewportToContentVerticalOffset;
12533                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
12534                 final boolean isBottomRightVisible =
12535                         isPositionVisible(localRight, localBottom);
12536                 int characterBoundsFlags = 0;
12537                 if (isTopLeftVisible || isBottomRightVisible) {
12538                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
12539                 }
12540                 if (!isTopLeftVisible || !isBottomRightVisible) {
12541                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
12542                 }
12543                 if (isRtl) {
12544                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
12545                 }
12546                 // Here offset is the index in Java chars.
12547                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
12548                         localBottom, characterBoundsFlags);
12549             }
12550         }
12551     }
12552 
12553     /**
12554      * @hide
12555      */
isPositionVisible(final float positionX, final float positionY)12556     public boolean isPositionVisible(final float positionX, final float positionY) {
12557         synchronized (TEMP_POSITION) {
12558             final float[] position = TEMP_POSITION;
12559             position[0] = positionX;
12560             position[1] = positionY;
12561             View view = this;
12562 
12563             while (view != null) {
12564                 if (view != this) {
12565                     // Local scroll is already taken into account in positionX/Y
12566                     position[0] -= view.getScrollX();
12567                     position[1] -= view.getScrollY();
12568                 }
12569 
12570                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
12571                         || position[1] > view.getHeight()) {
12572                     return false;
12573                 }
12574 
12575                 if (!view.getMatrix().isIdentity()) {
12576                     view.getMatrix().mapPoints(position);
12577                 }
12578 
12579                 position[0] += view.getLeft();
12580                 position[1] += view.getTop();
12581 
12582                 final ViewParent parent = view.getParent();
12583                 if (parent instanceof View) {
12584                     view = (View) parent;
12585                 } else {
12586                     // We've reached the ViewRoot, stop iterating
12587                     view = null;
12588                 }
12589             }
12590         }
12591 
12592         // We've been able to walk up the view hierarchy and the position was never clipped
12593         return true;
12594     }
12595 
12596     /**
12597      * Performs an accessibility action after it has been offered to the
12598      * delegate.
12599      *
12600      * @hide
12601      */
12602     @Override
performAccessibilityActionInternal(int action, Bundle arguments)12603     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
12604         if (mEditor != null) {
12605             if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)
12606                     || mEditor.performSmartActionsAccessibilityAction(action)) {
12607                 return true;
12608             }
12609         }
12610         switch (action) {
12611             case AccessibilityNodeInfo.ACTION_CLICK: {
12612                 return performAccessibilityActionClick(arguments);
12613             }
12614             case AccessibilityNodeInfo.ACTION_COPY: {
12615                 if (isFocused() && canCopy()) {
12616                     if (onTextContextMenuItem(ID_COPY)) {
12617                         return true;
12618                     }
12619                 }
12620             } return false;
12621             case AccessibilityNodeInfo.ACTION_PASTE: {
12622                 if (isFocused() && canPaste()) {
12623                     if (onTextContextMenuItem(ID_PASTE)) {
12624                         return true;
12625                     }
12626                 }
12627             } return false;
12628             case AccessibilityNodeInfo.ACTION_CUT: {
12629                 if (isFocused() && canCut()) {
12630                     if (onTextContextMenuItem(ID_CUT)) {
12631                         return true;
12632                     }
12633                 }
12634             } return false;
12635             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
12636                 ensureIterableTextForAccessibilitySelectable();
12637                 CharSequence text = getIterableTextForAccessibility();
12638                 if (text == null) {
12639                     return false;
12640                 }
12641                 final int start = (arguments != null) ? arguments.getInt(
12642                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
12643                 final int end = (arguments != null) ? arguments.getInt(
12644                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
12645                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
12646                     // No arguments clears the selection.
12647                     if (start == end && end == -1) {
12648                         Selection.removeSelection((Spannable) text);
12649                         return true;
12650                     }
12651                     if (start >= 0 && start <= end && end <= text.length()) {
12652                         requestFocusOnNonEditableSelectableText();
12653                         Selection.setSelection((Spannable) text, start, end);
12654                         // Make sure selection mode is engaged.
12655                         if (mEditor != null) {
12656                             mEditor.startSelectionActionModeAsync(false);
12657                         }
12658                         return true;
12659                     }
12660                 }
12661             } return false;
12662             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
12663             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
12664                 ensureIterableTextForAccessibilitySelectable();
12665                 return super.performAccessibilityActionInternal(action, arguments);
12666             }
12667             case ACCESSIBILITY_ACTION_SHARE: {
12668                 if (isFocused() && canShare()) {
12669                     if (onTextContextMenuItem(ID_SHARE)) {
12670                         return true;
12671                     }
12672                 }
12673             } return false;
12674             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
12675                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
12676                     return false;
12677                 }
12678                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
12679                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
12680                 setText(text);
12681                 if (mText != null) {
12682                     int updatedTextLength = mText.length();
12683                     if (updatedTextLength > 0) {
12684                         Selection.setSelection(mSpannable, updatedTextLength);
12685                     }
12686                 }
12687             } return true;
12688             case R.id.accessibilityActionImeEnter: {
12689                 if (isFocused() && isTextEditable()) {
12690                     onEditorAction(getImeActionId());
12691                 }
12692             } return true;
12693             case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
12694                 if (isLongClickable()) {
12695                     boolean handled;
12696                     if (isEnabled() && (mBufferType == BufferType.EDITABLE)) {
12697                         mEditor.mIsBeingLongClickedByAccessibility = true;
12698                         try {
12699                             handled = performLongClick();
12700                         } finally {
12701                             mEditor.mIsBeingLongClickedByAccessibility = false;
12702                         }
12703                     } else {
12704                         handled = performLongClick();
12705                     }
12706                     return handled;
12707                 }
12708             }
12709             return false;
12710             default: {
12711                 // New ids have static blocks to assign values, so they can't be used in a case
12712                 // block.
12713                 if (action == R.id.accessibilityActionShowTextSuggestions) {
12714                     return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE);
12715                 }
12716                 return super.performAccessibilityActionInternal(action, arguments);
12717             }
12718         }
12719     }
12720 
performAccessibilityActionClick(Bundle arguments)12721     private boolean performAccessibilityActionClick(Bundle arguments) {
12722         boolean handled = false;
12723 
12724         if (!isEnabled()) {
12725             return false;
12726         }
12727 
12728         if (isClickable() || isLongClickable()) {
12729             // Simulate View.onTouchEvent for an ACTION_UP event
12730             if (isFocusable() && !isFocused()) {
12731                 requestFocus();
12732             }
12733 
12734             performClick();
12735             handled = true;
12736         }
12737 
12738         // Show the IME, except when selecting in read-only text.
12739         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
12740                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
12741             final InputMethodManager imm = getInputMethodManager();
12742             viewClicked(imm);
12743             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
12744                 handled |= imm.showSoftInput(this, 0);
12745             }
12746         }
12747 
12748         return handled;
12749     }
12750 
requestFocusOnNonEditableSelectableText()12751     private void requestFocusOnNonEditableSelectableText() {
12752         if (!isTextEditable() && isTextSelectable()) {
12753             if (!isEnabled()) {
12754                 return;
12755             }
12756 
12757             if (isFocusable() && !isFocused()) {
12758                 requestFocus();
12759             }
12760         }
12761     }
12762 
hasSpannableText()12763     private boolean hasSpannableText() {
12764         return mText != null && mText instanceof Spannable;
12765     }
12766 
12767     /** @hide */
12768     @Override
sendAccessibilityEventInternal(int eventType)12769     public void sendAccessibilityEventInternal(int eventType) {
12770         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
12771             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
12772         }
12773 
12774         super.sendAccessibilityEventInternal(eventType);
12775     }
12776 
12777     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)12778     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
12779         // Do not send scroll events since first they are not interesting for
12780         // accessibility and second such events a generated too frequently.
12781         // For details see the implementation of bringTextIntoView().
12782         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
12783             return;
12784         }
12785         super.sendAccessibilityEventUnchecked(event);
12786     }
12787 
12788     /**
12789      * Returns the text that should be exposed to accessibility services.
12790      * <p>
12791      * This approximates what is displayed visually.
12792      *
12793      * @return the text that should be exposed to accessibility services, may
12794      *         be {@code null} if no text is set
12795      */
12796     @Nullable
12797     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTextForAccessibility()12798     private CharSequence getTextForAccessibility() {
12799         // If the text is empty, we must be showing the hint text.
12800         if (TextUtils.isEmpty(mText)) {
12801             return mHint;
12802         }
12803 
12804         // Otherwise, return whatever text is being displayed.
12805         return TextUtils.trimToParcelableSize(mTransformed);
12806     }
12807 
isVisibleToAccessibility()12808     boolean isVisibleToAccessibility() {
12809         return AccessibilityManager.getInstance(mContext).isEnabled()
12810                 && (isFocused() || (isSelected() && isShown()));
12811     }
12812 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12813     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
12814             int fromIndex, int removedCount, int addedCount) {
12815         AccessibilityEvent event =
12816                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
12817         event.setFromIndex(fromIndex);
12818         event.setRemovedCount(removedCount);
12819         event.setAddedCount(addedCount);
12820         event.setBeforeText(beforeText);
12821         sendAccessibilityEventUnchecked(event);
12822     }
12823 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int toIndex)12824     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
12825             int fromIndex, int toIndex) {
12826         AccessibilityEvent event =
12827                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
12828         event.setFromIndex(fromIndex);
12829         event.setToIndex(toIndex);
12830         event.setBeforeText(beforeText);
12831         sendAccessibilityEventUnchecked(event);
12832     }
12833 
getInputMethodManager()12834     private InputMethodManager getInputMethodManager() {
12835         return getContext().getSystemService(InputMethodManager.class);
12836     }
12837 
12838     /**
12839      * Returns whether this text view is a current input method target.  The
12840      * default implementation just checks with {@link InputMethodManager}.
12841      * @return True if the TextView is a current input method target; false otherwise.
12842      */
isInputMethodTarget()12843     public boolean isInputMethodTarget() {
12844         InputMethodManager imm = getInputMethodManager();
12845         return imm != null && imm.isActive(this);
12846     }
12847 
12848     static final int ID_SELECT_ALL = android.R.id.selectAll;
12849     static final int ID_UNDO = android.R.id.undo;
12850     static final int ID_REDO = android.R.id.redo;
12851     static final int ID_CUT = android.R.id.cut;
12852     static final int ID_COPY = android.R.id.copy;
12853     static final int ID_PASTE = android.R.id.paste;
12854     static final int ID_SHARE = android.R.id.shareText;
12855     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
12856     static final int ID_REPLACE = android.R.id.replaceText;
12857     static final int ID_ASSIST = android.R.id.textAssist;
12858     static final int ID_AUTOFILL = android.R.id.autofill;
12859 
12860     /**
12861      * Called when a context menu option for the text view is selected.  Currently
12862      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
12863      * {@link android.R.id#copy}, {@link android.R.id#paste},
12864      * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or
12865      * {@link android.R.id#shareText}.
12866      *
12867      * @return true if the context menu item action was performed.
12868      */
onTextContextMenuItem(int id)12869     public boolean onTextContextMenuItem(int id) {
12870         int min = 0;
12871         int max = mText.length();
12872 
12873         if (isFocused()) {
12874             final int selStart = getSelectionStart();
12875             final int selEnd = getSelectionEnd();
12876 
12877             min = Math.max(0, Math.min(selStart, selEnd));
12878             max = Math.max(0, Math.max(selStart, selEnd));
12879         }
12880 
12881         switch (id) {
12882             case ID_SELECT_ALL:
12883                 final boolean hadSelection = hasSelection();
12884                 selectAllText();
12885                 if (mEditor != null && hadSelection) {
12886                     mEditor.invalidateActionModeAsync();
12887                 }
12888                 return true;
12889 
12890             case ID_UNDO:
12891                 if (mEditor != null) {
12892                     mEditor.undo();
12893                 }
12894                 return true;  // Returns true even if nothing was undone.
12895 
12896             case ID_REDO:
12897                 if (mEditor != null) {
12898                     mEditor.redo();
12899                 }
12900                 return true;  // Returns true even if nothing was undone.
12901 
12902             case ID_PASTE:
12903                 paste(true /* withFormatting */);
12904                 return true;
12905 
12906             case ID_PASTE_AS_PLAIN_TEXT:
12907                 paste(false /* withFormatting */);
12908                 return true;
12909 
12910             case ID_CUT:
12911                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
12912                 if (setPrimaryClip(cutData)) {
12913                     deleteText_internal(min, max);
12914                 } else {
12915                     Toast.makeText(getContext(),
12916                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12917                             Toast.LENGTH_SHORT).show();
12918                 }
12919                 return true;
12920 
12921             case ID_COPY:
12922                 // For link action mode in a non-selectable/non-focusable TextView,
12923                 // make sure that we set the appropriate min/max.
12924                 final int selStart = getSelectionStart();
12925                 final int selEnd = getSelectionEnd();
12926                 min = Math.max(0, Math.min(selStart, selEnd));
12927                 max = Math.max(0, Math.max(selStart, selEnd));
12928                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
12929                 if (setPrimaryClip(copyData)) {
12930                     stopTextActionMode();
12931                 } else {
12932                     Toast.makeText(getContext(),
12933                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12934                             Toast.LENGTH_SHORT).show();
12935                 }
12936                 return true;
12937 
12938             case ID_REPLACE:
12939                 if (mEditor != null) {
12940                     mEditor.replace();
12941                 }
12942                 return true;
12943 
12944             case ID_SHARE:
12945                 shareSelectedText();
12946                 return true;
12947 
12948             case ID_AUTOFILL:
12949                 requestAutofill();
12950                 stopTextActionMode();
12951                 return true;
12952         }
12953         return false;
12954     }
12955 
12956     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTransformedText(int start, int end)12957     CharSequence getTransformedText(int start, int end) {
12958         return removeSuggestionSpans(mTransformed.subSequence(start, end));
12959     }
12960 
12961     @Override
performLongClick()12962     public boolean performLongClick() {
12963         if (DEBUG_CURSOR) {
12964             logCursor("performLongClick", null);
12965         }
12966 
12967         boolean handled = false;
12968         boolean performedHapticFeedback = false;
12969 
12970         if (mEditor != null) {
12971             mEditor.mIsBeingLongClicked = true;
12972         }
12973 
12974         if (super.performLongClick()) {
12975             handled = true;
12976             performedHapticFeedback = true;
12977         }
12978 
12979         if (mEditor != null) {
12980             handled |= mEditor.performLongClick(handled);
12981             mEditor.mIsBeingLongClicked = false;
12982         }
12983 
12984         if (handled) {
12985             if (!performedHapticFeedback) {
12986               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
12987             }
12988             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
12989         } else {
12990             MetricsLogger.action(
12991                     mContext,
12992                     MetricsEvent.TEXT_LONGPRESS,
12993                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
12994         }
12995 
12996         return handled;
12997     }
12998 
12999     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)13000     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
13001         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
13002         if (mEditor != null) {
13003             mEditor.onScrollChanged();
13004         }
13005     }
13006 
13007     /**
13008      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
13009      * by the IME or by the spell checker as the user types. This is done by adding
13010      * {@link SuggestionSpan}s to the text.
13011      *
13012      * When suggestions are enabled (default), this list of suggestions will be displayed when the
13013      * user asks for them on these parts of the text. This value depends on the inputType of this
13014      * TextView.
13015      *
13016      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
13017      *
13018      * In addition, the type variation must be one of
13019      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
13020      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
13021      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
13022      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
13023      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
13024      *
13025      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
13026      *
13027      * @return true if the suggestions popup window is enabled, based on the inputType.
13028      */
isSuggestionsEnabled()13029     public boolean isSuggestionsEnabled() {
13030         if (mEditor == null) return false;
13031         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
13032             return false;
13033         }
13034         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
13035 
13036         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
13037         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
13038                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
13039                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
13040                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
13041                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
13042     }
13043 
13044     /**
13045      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
13046      * selection is initiated in this View.
13047      *
13048      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
13049      * Paste, Replace and Share actions, depending on what this View supports.
13050      *
13051      * <p>A custom implementation can add new entries in the default menu in its
13052      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
13053      * method. The default actions can also be removed from the menu using
13054      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
13055      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
13056      * {@link android.R.id#pasteAsPlainText} (starting at API level 23),
13057      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
13058      *
13059      * <p>Returning false from
13060      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
13061      * will prevent the action mode from being started.
13062      *
13063      * <p>Action click events should be handled by the custom implementation of
13064      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
13065      * android.view.MenuItem)}.
13066      *
13067      * <p>Note that text selection mode is not started when a TextView receives focus and the
13068      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
13069      * that case, to allow for quick replacement.
13070      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)13071     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
13072         createEditorIfNeeded();
13073         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
13074     }
13075 
13076     /**
13077      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
13078      *
13079      * @return The current custom selection callback.
13080      */
getCustomSelectionActionModeCallback()13081     public ActionMode.Callback getCustomSelectionActionModeCallback() {
13082         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
13083     }
13084 
13085     /**
13086      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
13087      * insertion is initiated in this View.
13088      * The standard implementation populates the menu with a subset of Select All,
13089      * Paste and Replace actions, depending on what this View supports.
13090      *
13091      * <p>A custom implementation can add new entries in the default menu in its
13092      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
13093      * android.view.Menu)} method. The default actions can also be removed from the menu using
13094      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
13095      * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API
13096      * level 23) or {@link android.R.id#replaceText} ids as parameters.</p>
13097      *
13098      * <p>Returning false from
13099      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
13100      * android.view.Menu)} will prevent the action mode from being started.</p>
13101      *
13102      * <p>Action click events should be handled by the custom implementation of
13103      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
13104      * android.view.MenuItem)}.</p>
13105      *
13106      * <p>Note that text insertion mode is not started when a TextView receives focus and the
13107      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
13108      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)13109     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
13110         createEditorIfNeeded();
13111         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
13112     }
13113 
13114     /**
13115      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
13116      *
13117      * @return The current custom insertion callback.
13118      */
getCustomInsertionActionModeCallback()13119     public ActionMode.Callback getCustomInsertionActionModeCallback() {
13120         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
13121     }
13122 
13123     /**
13124      * Sets the {@link TextClassifier} for this TextView.
13125      */
setTextClassifier(@ullable TextClassifier textClassifier)13126     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
13127         mTextClassifier = textClassifier;
13128     }
13129 
13130     /**
13131      * Returns the {@link TextClassifier} used by this TextView.
13132      * If no TextClassifier has been set, this TextView uses the default set by the
13133      * {@link TextClassificationManager}.
13134      */
13135     @NonNull
getTextClassifier()13136     public TextClassifier getTextClassifier() {
13137         if (mTextClassifier == null) {
13138             final TextClassificationManager tcm = getTextClassificationManagerForUser();
13139             if (tcm != null) {
13140                 return tcm.getTextClassifier();
13141             }
13142             return TextClassifier.NO_OP;
13143         }
13144         return mTextClassifier;
13145     }
13146 
13147     /**
13148      * Returns a session-aware text classifier.
13149      * This method creates one if none already exists or the current one is destroyed.
13150      */
13151     @NonNull
getTextClassificationSession()13152     TextClassifier getTextClassificationSession() {
13153         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
13154             final TextClassificationManager tcm = getTextClassificationManagerForUser();
13155             if (tcm != null) {
13156                 final String widgetType;
13157                 if (isTextEditable()) {
13158                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
13159                 } else if (isTextSelectable()) {
13160                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
13161                 } else {
13162                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
13163                 }
13164                 mTextClassificationContext = new TextClassificationContext.Builder(
13165                         mContext.getPackageName(), widgetType)
13166                         .build();
13167                 if (mTextClassifier != null) {
13168                     mTextClassificationSession = tcm.createTextClassificationSession(
13169                             mTextClassificationContext, mTextClassifier);
13170                 } else {
13171                     mTextClassificationSession = tcm.createTextClassificationSession(
13172                             mTextClassificationContext);
13173                 }
13174             } else {
13175                 mTextClassificationSession = TextClassifier.NO_OP;
13176             }
13177         }
13178         return mTextClassificationSession;
13179     }
13180 
13181     /**
13182      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
13183      * @see #getTextClassificationSession()
13184      */
13185     @Nullable
getTextClassificationContext()13186     TextClassificationContext getTextClassificationContext() {
13187         return mTextClassificationContext;
13188     }
13189 
13190     /**
13191      * Returns true if this TextView uses a no-op TextClassifier.
13192      */
usesNoOpTextClassifier()13193     boolean usesNoOpTextClassifier() {
13194         return getTextClassifier() == TextClassifier.NO_OP;
13195     }
13196 
13197     /**
13198      * Starts an ActionMode for the specified TextLinkSpan.
13199      *
13200      * @return Whether or not we're attempting to start the action mode.
13201      * @hide
13202      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)13203     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
13204         Preconditions.checkNotNull(clickedSpan);
13205 
13206         if (!(mText instanceof Spanned)) {
13207             return false;
13208         }
13209 
13210         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
13211         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
13212 
13213         if (start < 0 || end > mText.length() || start >= end) {
13214             return false;
13215         }
13216 
13217         createEditorIfNeeded();
13218         mEditor.startLinkActionModeAsync(start, end);
13219         return true;
13220     }
13221 
13222     /**
13223      * Handles a click on the specified TextLinkSpan.
13224      *
13225      * @return Whether or not the click is being handled.
13226      * @hide
13227      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)13228     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
13229         Preconditions.checkNotNull(clickedSpan);
13230         if (mText instanceof Spanned) {
13231             final Spanned spanned = (Spanned) mText;
13232             final int start = spanned.getSpanStart(clickedSpan);
13233             final int end = spanned.getSpanEnd(clickedSpan);
13234             if (start >= 0 && end <= mText.length() && start < end) {
13235                 final TextClassification.Request request = new TextClassification.Request.Builder(
13236                         mText, start, end)
13237                         .setDefaultLocales(getTextLocales())
13238                         .build();
13239                 final Supplier<TextClassification> supplier = () ->
13240                         getTextClassificationSession().classifyText(request);
13241                 final Consumer<TextClassification> consumer = classification -> {
13242                     if (classification != null) {
13243                         if (!classification.getActions().isEmpty()) {
13244                             try {
13245                                 classification.getActions().get(0).getActionIntent().send();
13246                             } catch (PendingIntent.CanceledException e) {
13247                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
13248                             }
13249                         } else {
13250                             Log.d(LOG_TAG, "No link action to perform");
13251                         }
13252                     } else {
13253                         // classification == null
13254                         Log.d(LOG_TAG, "Timeout while classifying text");
13255                     }
13256                 };
13257                 CompletableFuture.supplyAsync(supplier)
13258                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
13259                         .thenAccept(consumer);
13260                 return true;
13261             }
13262         }
13263         return false;
13264     }
13265 
13266     /**
13267      * @hide
13268      */
13269     @UnsupportedAppUsage
stopTextActionMode()13270     protected void stopTextActionMode() {
13271         if (mEditor != null) {
13272             mEditor.stopTextActionMode();
13273         }
13274     }
13275 
13276     /** @hide */
hideFloatingToolbar(int durationMs)13277     public void hideFloatingToolbar(int durationMs) {
13278         if (mEditor != null) {
13279             mEditor.hideFloatingToolbar(durationMs);
13280         }
13281     }
13282 
canUndo()13283     boolean canUndo() {
13284         return mEditor != null && mEditor.canUndo();
13285     }
13286 
canRedo()13287     boolean canRedo() {
13288         return mEditor != null && mEditor.canRedo();
13289     }
13290 
canCut()13291     boolean canCut() {
13292         if (hasPasswordTransformationMethod()) {
13293             return false;
13294         }
13295 
13296         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
13297                 && mEditor.mKeyListener != null) {
13298             return true;
13299         }
13300 
13301         return false;
13302     }
13303 
canCopy()13304     boolean canCopy() {
13305         if (hasPasswordTransformationMethod()) {
13306             return false;
13307         }
13308 
13309         if (mText.length() > 0 && hasSelection() && mEditor != null) {
13310             return true;
13311         }
13312 
13313         return false;
13314     }
13315 
canReplace()13316     boolean canReplace() {
13317         if (hasPasswordTransformationMethod()) {
13318             return false;
13319         }
13320 
13321         return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null)
13322                 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions();
13323     }
13324 
canShare()13325     boolean canShare() {
13326         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
13327             return false;
13328         }
13329         return canCopy();
13330     }
13331 
isDeviceProvisioned()13332     boolean isDeviceProvisioned() {
13333         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
13334             mDeviceProvisionedState = Settings.Global.getInt(
13335                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
13336                     ? DEVICE_PROVISIONED_YES
13337                     : DEVICE_PROVISIONED_NO;
13338         }
13339         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
13340     }
13341 
13342     @UnsupportedAppUsage
canPaste()13343     boolean canPaste() {
13344         return (mText instanceof Editable
13345                 && mEditor != null && mEditor.mKeyListener != null
13346                 && getSelectionStart() >= 0
13347                 && getSelectionEnd() >= 0
13348                 && getClipboardManagerForUser().hasPrimaryClip());
13349     }
13350 
canPasteAsPlainText()13351     boolean canPasteAsPlainText() {
13352         if (!canPaste()) {
13353             return false;
13354         }
13355 
13356         final ClipDescription description =
13357                 getClipboardManagerForUser().getPrimaryClipDescription();
13358         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
13359         return (isPlainType && description.isStyledText())
13360                 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
13361     }
13362 
canProcessText()13363     boolean canProcessText() {
13364         if (getId() == View.NO_ID) {
13365             return false;
13366         }
13367         return canShare();
13368     }
13369 
canSelectAllText()13370     boolean canSelectAllText() {
13371         return canSelectText() && !hasPasswordTransformationMethod()
13372                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
13373     }
13374 
selectAllText()13375     boolean selectAllText() {
13376         if (mEditor != null) {
13377             // Hide the toolbar before changing the selection to avoid flickering.
13378             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
13379         }
13380         final int length = mText.length();
13381         Selection.setSelection(mSpannable, 0, length);
13382         return length > 0;
13383     }
13384 
paste(boolean withFormatting)13385     private void paste(boolean withFormatting) {
13386         ClipboardManager clipboard = getClipboardManagerForUser();
13387         ClipData clip = clipboard.getPrimaryClip();
13388         if (clip == null) {
13389             return;
13390         }
13391         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD)
13392                 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT)
13393                 .build();
13394         performReceiveContent(payload);
13395         sLastCutCopyOrTextChangedTime = 0;
13396     }
13397 
shareSelectedText()13398     private void shareSelectedText() {
13399         String selectedText = getSelectedText();
13400         if (selectedText != null && !selectedText.isEmpty()) {
13401             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
13402             sharingIntent.setType("text/plain");
13403             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
13404             selectedText = TextUtils.trimToParcelableSize(selectedText);
13405             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
13406             getContext().startActivity(Intent.createChooser(sharingIntent, null));
13407             Selection.setSelection(mSpannable, getSelectionEnd());
13408         }
13409     }
13410 
13411     @CheckResult
setPrimaryClip(ClipData clip)13412     private boolean setPrimaryClip(ClipData clip) {
13413         ClipboardManager clipboard = getClipboardManagerForUser();
13414         try {
13415             clipboard.setPrimaryClip(clip);
13416         } catch (Throwable t) {
13417             return false;
13418         }
13419         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
13420         return true;
13421     }
13422 
13423     /**
13424      * Get the character offset closest to the specified absolute position. A typical use case is to
13425      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
13426      *
13427      * @param x The horizontal absolute position of a point on screen
13428      * @param y The vertical absolute position of a point on screen
13429      * @return the character offset for the character whose position is closest to the specified
13430      *  position. Returns -1 if there is no layout.
13431      */
getOffsetForPosition(float x, float y)13432     public int getOffsetForPosition(float x, float y) {
13433         if (getLayout() == null) return -1;
13434         final int line = getLineAtCoordinate(y);
13435         final int offset = getOffsetAtCoordinate(line, x);
13436         return offset;
13437     }
13438 
convertToLocalHorizontalCoordinate(float x)13439     float convertToLocalHorizontalCoordinate(float x) {
13440         x -= getTotalPaddingLeft();
13441         // Clamp the position to inside of the view.
13442         x = Math.max(0.0f, x);
13443         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
13444         x += getScrollX();
13445         return x;
13446     }
13447 
13448     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getLineAtCoordinate(float y)13449     int getLineAtCoordinate(float y) {
13450         y -= getTotalPaddingTop();
13451         // Clamp the position to inside of the view.
13452         y = Math.max(0.0f, y);
13453         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
13454         y += getScrollY();
13455         return getLayout().getLineForVertical((int) y);
13456     }
13457 
getLineAtCoordinateUnclamped(float y)13458     int getLineAtCoordinateUnclamped(float y) {
13459         y -= getTotalPaddingTop();
13460         y += getScrollY();
13461         return getLayout().getLineForVertical((int) y);
13462     }
13463 
getOffsetAtCoordinate(int line, float x)13464     int getOffsetAtCoordinate(int line, float x) {
13465         x = convertToLocalHorizontalCoordinate(x);
13466         return getLayout().getOffsetForHorizontal(line, x);
13467     }
13468 
13469     /**
13470      * Handles drag events sent by the system following a call to
13471      * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
13472      * startDragAndDrop()}.
13473      *
13474      * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent}
13475      * implementation.
13476      *
13477      * <p>If this text view is editable, accepts all drag actions (returns true for an
13478      * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all
13479      * subsequent drag events). While the drag is in progress, updates the cursor position
13480      * to follow the touch location. Once a drop event is received, handles content insertion
13481      * via {@link #performReceiveContent}.
13482      *
13483      * @param event The {@link android.view.DragEvent} sent by the system.
13484      * The {@link android.view.DragEvent#getAction()} method returns an action type constant
13485      * defined in DragEvent, indicating the type of drag event represented by this object.
13486      * @return Returns true if this text view is editable and delegates to super otherwise.
13487      * See {@link View#onDragEvent}.
13488      */
13489     @Override
onDragEvent(DragEvent event)13490     public boolean onDragEvent(DragEvent event) {
13491         if (mEditor == null || !mEditor.hasInsertionController()) {
13492             // If this TextView is not editable, defer to the default View implementation. This
13493             // will check for the presence of an OnReceiveContentListener and accept/reject
13494             // drag events depending on whether the listener is/isn't set.
13495             return super.onDragEvent(event);
13496         }
13497         switch (event.getAction()) {
13498             case DragEvent.ACTION_DRAG_STARTED:
13499                 return true;
13500 
13501             case DragEvent.ACTION_DRAG_ENTERED:
13502                 TextView.this.requestFocus();
13503                 return true;
13504 
13505             case DragEvent.ACTION_DRAG_LOCATION:
13506                 if (mText instanceof Spannable) {
13507                     final int offset = getOffsetForPosition(event.getX(), event.getY());
13508                     Selection.setSelection(mSpannable, offset);
13509                 }
13510                 return true;
13511 
13512             case DragEvent.ACTION_DROP:
13513                 if (mEditor != null) mEditor.onDrop(event);
13514                 return true;
13515 
13516             case DragEvent.ACTION_DRAG_ENDED:
13517             case DragEvent.ACTION_DRAG_EXITED:
13518             default:
13519                 return true;
13520         }
13521     }
13522 
isInBatchEditMode()13523     boolean isInBatchEditMode() {
13524         if (mEditor == null) return false;
13525         final Editor.InputMethodState ims = mEditor.mInputMethodState;
13526         if (ims != null) {
13527             return ims.mBatchEditNesting > 0;
13528         }
13529         return mEditor.mInBatchEditControllers;
13530     }
13531 
13532     @Override
onRtlPropertiesChanged(int layoutDirection)13533     public void onRtlPropertiesChanged(int layoutDirection) {
13534         super.onRtlPropertiesChanged(layoutDirection);
13535 
13536         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
13537         if (mTextDir != newTextDir) {
13538             mTextDir = newTextDir;
13539             if (mLayout != null) {
13540                 checkForRelayout();
13541             }
13542         }
13543     }
13544 
13545     /**
13546      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
13547      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
13548      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
13549      * return value may not be the same as the one TextView uses if the View's layout direction is
13550      * not resolved or detached from parent root view.
13551      */
getTextDirectionHeuristic()13552     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
13553         if (hasPasswordTransformationMethod()) {
13554             // passwords fields should be LTR
13555             return TextDirectionHeuristics.LTR;
13556         }
13557 
13558         if (mEditor != null
13559                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
13560                     == EditorInfo.TYPE_CLASS_PHONE) {
13561             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
13562             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
13563             // RTL digits.
13564             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
13565             final String zero = symbols.getDigitStrings()[0];
13566             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
13567             // direction.
13568             final int firstCodepoint = zero.codePointAt(0);
13569             final byte digitDirection = Character.getDirectionality(firstCodepoint);
13570             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
13571                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
13572                 return TextDirectionHeuristics.RTL;
13573             } else {
13574                 return TextDirectionHeuristics.LTR;
13575             }
13576         }
13577 
13578         // Always need to resolve layout direction first
13579         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
13580 
13581         // Now, we can select the heuristic
13582         switch (getTextDirection()) {
13583             default:
13584             case TEXT_DIRECTION_FIRST_STRONG:
13585                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
13586                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
13587             case TEXT_DIRECTION_ANY_RTL:
13588                 return TextDirectionHeuristics.ANYRTL_LTR;
13589             case TEXT_DIRECTION_LTR:
13590                 return TextDirectionHeuristics.LTR;
13591             case TEXT_DIRECTION_RTL:
13592                 return TextDirectionHeuristics.RTL;
13593             case TEXT_DIRECTION_LOCALE:
13594                 return TextDirectionHeuristics.LOCALE;
13595             case TEXT_DIRECTION_FIRST_STRONG_LTR:
13596                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
13597             case TEXT_DIRECTION_FIRST_STRONG_RTL:
13598                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
13599         }
13600     }
13601 
13602     /**
13603      * @hide
13604      */
13605     @Override
onResolveDrawables(int layoutDirection)13606     public void onResolveDrawables(int layoutDirection) {
13607         // No need to resolve twice
13608         if (mLastLayoutDirection == layoutDirection) {
13609             return;
13610         }
13611         mLastLayoutDirection = layoutDirection;
13612 
13613         // Resolve drawables
13614         if (mDrawables != null) {
13615             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
13616                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
13617                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
13618                 applyCompoundDrawableTint();
13619             }
13620         }
13621     }
13622 
13623     /**
13624      * Prepares a drawable for display by propagating layout direction and
13625      * drawable state.
13626      *
13627      * @param dr the drawable to prepare
13628      */
prepareDrawableForDisplay(@ullable Drawable dr)13629     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
13630         if (dr == null) {
13631             return;
13632         }
13633 
13634         dr.setLayoutDirection(getLayoutDirection());
13635 
13636         if (dr.isStateful()) {
13637             dr.setState(getDrawableState());
13638             dr.jumpToCurrentState();
13639         }
13640     }
13641 
13642     /**
13643      * @hide
13644      */
resetResolvedDrawables()13645     protected void resetResolvedDrawables() {
13646         super.resetResolvedDrawables();
13647         mLastLayoutDirection = -1;
13648     }
13649 
13650     /**
13651      * @hide
13652      */
viewClicked(InputMethodManager imm)13653     protected void viewClicked(InputMethodManager imm) {
13654         if (imm != null) {
13655             imm.viewClicked(this);
13656         }
13657     }
13658 
13659     /**
13660      * Deletes the range of text [start, end[.
13661      * @hide
13662      */
13663     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
deleteText_internal(int start, int end)13664     protected void deleteText_internal(int start, int end) {
13665         ((Editable) mText).delete(start, end);
13666     }
13667 
13668     /**
13669      * Replaces the range of text [start, end[ by replacement text
13670      * @hide
13671      */
replaceText_internal(int start, int end, CharSequence text)13672     protected void replaceText_internal(int start, int end, CharSequence text) {
13673         ((Editable) mText).replace(start, end, text);
13674     }
13675 
13676     /**
13677      * Sets a span on the specified range of text
13678      * @hide
13679      */
setSpan_internal(Object span, int start, int end, int flags)13680     protected void setSpan_internal(Object span, int start, int end, int flags) {
13681         ((Editable) mText).setSpan(span, start, end, flags);
13682     }
13683 
13684     /**
13685      * Moves the cursor to the specified offset position in text
13686      * @hide
13687      */
setCursorPosition_internal(int start, int end)13688     protected void setCursorPosition_internal(int start, int end) {
13689         Selection.setSelection(((Editable) mText), start, end);
13690     }
13691 
13692     /**
13693      * An Editor should be created as soon as any of the editable-specific fields (grouped
13694      * inside the Editor object) is assigned to a non-default value.
13695      * This method will create the Editor if needed.
13696      *
13697      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
13698      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
13699      * Editor for backward compatibility, as soon as one of these fields is assigned.
13700      *
13701      * Also note that for performance reasons, the mEditor is created when needed, but not
13702      * reset when no more edit-specific fields are needed.
13703      */
13704     @UnsupportedAppUsage
createEditorIfNeeded()13705     private void createEditorIfNeeded() {
13706         if (mEditor == null) {
13707             mEditor = new Editor(this);
13708         }
13709     }
13710 
13711     /**
13712      * @hide
13713      */
13714     @Override
13715     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getIterableTextForAccessibility()13716     public CharSequence getIterableTextForAccessibility() {
13717         return mText;
13718     }
13719 
ensureIterableTextForAccessibilitySelectable()13720     private void ensureIterableTextForAccessibilitySelectable() {
13721         if (!(mText instanceof Spannable)) {
13722             setText(mText, BufferType.SPANNABLE);
13723         }
13724     }
13725 
13726     /**
13727      * @hide
13728      */
13729     @Override
getIteratorForGranularity(int granularity)13730     public TextSegmentIterator getIteratorForGranularity(int granularity) {
13731         switch (granularity) {
13732             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
13733                 Spannable text = (Spannable) getIterableTextForAccessibility();
13734                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
13735                     AccessibilityIterators.LineTextSegmentIterator iterator =
13736                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
13737                     iterator.initialize(text, getLayout());
13738                     return iterator;
13739                 }
13740             } break;
13741             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
13742                 Spannable text = (Spannable) getIterableTextForAccessibility();
13743                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
13744                     AccessibilityIterators.PageTextSegmentIterator iterator =
13745                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
13746                     iterator.initialize(this);
13747                     return iterator;
13748                 }
13749             } break;
13750         }
13751         return super.getIteratorForGranularity(granularity);
13752     }
13753 
13754     /**
13755      * @hide
13756      */
13757     @Override
getAccessibilitySelectionStart()13758     public int getAccessibilitySelectionStart() {
13759         return getSelectionStart();
13760     }
13761 
13762     /**
13763      * @hide
13764      */
isAccessibilitySelectionExtendable()13765     public boolean isAccessibilitySelectionExtendable() {
13766         return true;
13767     }
13768 
13769     /**
13770      * @hide
13771      */
prepareForExtendedAccessibilitySelection()13772     public void prepareForExtendedAccessibilitySelection() {
13773         requestFocusOnNonEditableSelectableText();
13774     }
13775 
13776     /**
13777      * @hide
13778      */
13779     @Override
getAccessibilitySelectionEnd()13780     public int getAccessibilitySelectionEnd() {
13781         return getSelectionEnd();
13782     }
13783 
13784     /**
13785      * @hide
13786      */
13787     @Override
setAccessibilitySelection(int start, int end)13788     public void setAccessibilitySelection(int start, int end) {
13789         if (getAccessibilitySelectionStart() == start
13790                 && getAccessibilitySelectionEnd() == end) {
13791             return;
13792         }
13793         CharSequence text = getIterableTextForAccessibility();
13794         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
13795             Selection.setSelection((Spannable) text, start, end);
13796         } else {
13797             Selection.removeSelection((Spannable) text);
13798         }
13799         // Hide all selection controllers used for adjusting selection
13800         // since we are doing so explicitlty by other means and these
13801         // controllers interact with how selection behaves.
13802         if (mEditor != null) {
13803             mEditor.hideCursorAndSpanControllers();
13804             mEditor.stopTextActionMode();
13805         }
13806     }
13807 
13808     /** @hide */
13809     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)13810     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
13811         super.encodeProperties(stream);
13812 
13813         TruncateAt ellipsize = getEllipsize();
13814         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
13815         stream.addProperty("text:textSize", getTextSize());
13816         stream.addProperty("text:scaledTextSize", getScaledTextSize());
13817         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
13818         stream.addProperty("text:selectionStart", getSelectionStart());
13819         stream.addProperty("text:selectionEnd", getSelectionEnd());
13820         stream.addProperty("text:curTextColor", mCurTextColor);
13821         stream.addUserProperty("text:text", mText == null ? null : mText.toString());
13822         stream.addProperty("text:gravity", mGravity);
13823     }
13824 
13825     /**
13826      * User interface state that is stored by TextView for implementing
13827      * {@link View#onSaveInstanceState}.
13828      */
13829     public static class SavedState extends BaseSavedState {
13830         int selStart = -1;
13831         int selEnd = -1;
13832         @UnsupportedAppUsage
13833         CharSequence text;
13834         boolean frozenWithFocus;
13835         CharSequence error;
13836         ParcelableParcel editorState;  // Optional state from Editor.
13837 
SavedState(Parcelable superState)13838         SavedState(Parcelable superState) {
13839             super(superState);
13840         }
13841 
13842         @Override
writeToParcel(Parcel out, int flags)13843         public void writeToParcel(Parcel out, int flags) {
13844             super.writeToParcel(out, flags);
13845             out.writeInt(selStart);
13846             out.writeInt(selEnd);
13847             out.writeInt(frozenWithFocus ? 1 : 0);
13848             TextUtils.writeToParcel(text, out, flags);
13849 
13850             if (error == null) {
13851                 out.writeInt(0);
13852             } else {
13853                 out.writeInt(1);
13854                 TextUtils.writeToParcel(error, out, flags);
13855             }
13856 
13857             if (editorState == null) {
13858                 out.writeInt(0);
13859             } else {
13860                 out.writeInt(1);
13861                 editorState.writeToParcel(out, flags);
13862             }
13863         }
13864 
13865         @Override
toString()13866         public String toString() {
13867             String str = "TextView.SavedState{"
13868                     + Integer.toHexString(System.identityHashCode(this))
13869                     + " start=" + selStart + " end=" + selEnd;
13870             if (text != null) {
13871                 str += " text=" + text;
13872             }
13873             return str + "}";
13874         }
13875 
13876         @SuppressWarnings("hiding")
13877         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
13878                 new Parcelable.Creator<SavedState>() {
13879                     public SavedState createFromParcel(Parcel in) {
13880                         return new SavedState(in);
13881                     }
13882 
13883                     public SavedState[] newArray(int size) {
13884                         return new SavedState[size];
13885                     }
13886                 };
13887 
SavedState(Parcel in)13888         private SavedState(Parcel in) {
13889             super(in);
13890             selStart = in.readInt();
13891             selEnd = in.readInt();
13892             frozenWithFocus = (in.readInt() != 0);
13893             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13894 
13895             if (in.readInt() != 0) {
13896                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13897             }
13898 
13899             if (in.readInt() != 0) {
13900                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
13901             }
13902         }
13903     }
13904 
13905     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
13906         @NonNull
13907         private char[] mChars;
13908         private int mStart, mLength;
13909 
CharWrapper(@onNull char[] chars, int start, int len)13910         CharWrapper(@NonNull char[] chars, int start, int len) {
13911             mChars = chars;
13912             mStart = start;
13913             mLength = len;
13914         }
13915 
set(@onNull char[] chars, int start, int len)13916         /* package */ void set(@NonNull char[] chars, int start, int len) {
13917             mChars = chars;
13918             mStart = start;
13919             mLength = len;
13920         }
13921 
length()13922         public int length() {
13923             return mLength;
13924         }
13925 
charAt(int off)13926         public char charAt(int off) {
13927             return mChars[off + mStart];
13928         }
13929 
13930         @Override
toString()13931         public String toString() {
13932             return new String(mChars, mStart, mLength);
13933         }
13934 
subSequence(int start, int end)13935         public CharSequence subSequence(int start, int end) {
13936             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13937                 throw new IndexOutOfBoundsException(start + ", " + end);
13938             }
13939 
13940             return new String(mChars, start + mStart, end - start);
13941         }
13942 
getChars(int start, int end, char[] buf, int off)13943         public void getChars(int start, int end, char[] buf, int off) {
13944             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13945                 throw new IndexOutOfBoundsException(start + ", " + end);
13946             }
13947 
13948             System.arraycopy(mChars, start + mStart, buf, off, end - start);
13949         }
13950 
13951         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13952         public void drawText(BaseCanvas c, int start, int end,
13953                              float x, float y, Paint p) {
13954             c.drawText(mChars, start + mStart, end - start, x, y, p);
13955         }
13956 
13957         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13958         public void drawTextRun(BaseCanvas c, int start, int end,
13959                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
13960             int count = end - start;
13961             int contextCount = contextEnd - contextStart;
13962             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
13963                     contextCount, x, y, isRtl, p);
13964         }
13965 
measureText(int start, int end, Paint p)13966         public float measureText(int start, int end, Paint p) {
13967             return p.measureText(mChars, start + mStart, end - start);
13968         }
13969 
getTextWidths(int start, int end, float[] widths, Paint p)13970         public int getTextWidths(int start, int end, float[] widths, Paint p) {
13971             return p.getTextWidths(mChars, start + mStart, end - start, widths);
13972         }
13973 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13974         public float getTextRunAdvances(int start, int end, int contextStart,
13975                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
13976                 Paint p) {
13977             int count = end - start;
13978             int contextCount = contextEnd - contextStart;
13979             return p.getTextRunAdvances(mChars, start + mStart, count,
13980                     contextStart + mStart, contextCount, isRtl, advances,
13981                     advancesIndex);
13982         }
13983 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13984         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
13985                 int offset, int cursorOpt, Paint p) {
13986             int contextCount = contextEnd - contextStart;
13987             return p.getTextRunCursor(mChars, contextStart + mStart,
13988                     contextCount, isRtl, offset + mStart, cursorOpt);
13989         }
13990     }
13991 
13992     private static final class Marquee {
13993         // TODO: Add an option to configure this
13994         private static final float MARQUEE_DELTA_MAX = 0.07f;
13995         private static final int MARQUEE_DELAY = 1200;
13996         private static final int MARQUEE_DP_PER_SECOND = 30;
13997 
13998         private static final byte MARQUEE_STOPPED = 0x0;
13999         private static final byte MARQUEE_STARTING = 0x1;
14000         private static final byte MARQUEE_RUNNING = 0x2;
14001 
14002         private final WeakReference<TextView> mView;
14003         private final Choreographer mChoreographer;
14004 
14005         private byte mStatus = MARQUEE_STOPPED;
14006         private final float mPixelsPerMs;
14007         private float mMaxScroll;
14008         private float mMaxFadeScroll;
14009         private float mGhostStart;
14010         private float mGhostOffset;
14011         private float mFadeStop;
14012         private int mRepeatLimit;
14013 
14014         private float mScroll;
14015         private long mLastAnimationMs;
14016 
Marquee(TextView v)14017         Marquee(TextView v) {
14018             final float density = v.getContext().getResources().getDisplayMetrics().density;
14019             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
14020             mView = new WeakReference<TextView>(v);
14021             mChoreographer = Choreographer.getInstance();
14022         }
14023 
14024         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
14025             @Override
14026             public void doFrame(long frameTimeNanos) {
14027                 tick();
14028             }
14029         };
14030 
14031         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
14032             @Override
14033             public void doFrame(long frameTimeNanos) {
14034                 mStatus = MARQUEE_RUNNING;
14035                 mLastAnimationMs = mChoreographer.getFrameTime();
14036                 tick();
14037             }
14038         };
14039 
14040         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
14041             @Override
14042             public void doFrame(long frameTimeNanos) {
14043                 if (mStatus == MARQUEE_RUNNING) {
14044                     if (mRepeatLimit >= 0) {
14045                         mRepeatLimit--;
14046                     }
14047                     start(mRepeatLimit);
14048                 }
14049             }
14050         };
14051 
tick()14052         void tick() {
14053             if (mStatus != MARQUEE_RUNNING) {
14054                 return;
14055             }
14056 
14057             mChoreographer.removeFrameCallback(mTickCallback);
14058 
14059             final TextView textView = mView.get();
14060             if (textView != null && textView.isAggregatedVisible()
14061                     && (textView.isFocused() || textView.isSelected())) {
14062                 long currentMs = mChoreographer.getFrameTime();
14063                 long deltaMs = currentMs - mLastAnimationMs;
14064                 mLastAnimationMs = currentMs;
14065                 float deltaPx = deltaMs * mPixelsPerMs;
14066                 mScroll += deltaPx;
14067                 if (mScroll > mMaxScroll) {
14068                     mScroll = mMaxScroll;
14069                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
14070                 } else {
14071                     mChoreographer.postFrameCallback(mTickCallback);
14072                 }
14073                 textView.invalidate();
14074             }
14075         }
14076 
stop()14077         void stop() {
14078             mStatus = MARQUEE_STOPPED;
14079             mChoreographer.removeFrameCallback(mStartCallback);
14080             mChoreographer.removeFrameCallback(mRestartCallback);
14081             mChoreographer.removeFrameCallback(mTickCallback);
14082             resetScroll();
14083         }
14084 
resetScroll()14085         private void resetScroll() {
14086             mScroll = 0.0f;
14087             final TextView textView = mView.get();
14088             if (textView != null) textView.invalidate();
14089         }
14090 
start(int repeatLimit)14091         void start(int repeatLimit) {
14092             if (repeatLimit == 0) {
14093                 stop();
14094                 return;
14095             }
14096             mRepeatLimit = repeatLimit;
14097             final TextView textView = mView.get();
14098             if (textView != null && textView.mLayout != null) {
14099                 mStatus = MARQUEE_STARTING;
14100                 mScroll = 0.0f;
14101                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
14102                         - textView.getCompoundPaddingRight();
14103                 final float lineWidth = textView.mLayout.getLineWidth(0);
14104                 final float gap = textWidth / 3.0f;
14105                 mGhostStart = lineWidth - textWidth + gap;
14106                 mMaxScroll = mGhostStart + textWidth;
14107                 mGhostOffset = lineWidth + gap;
14108                 mFadeStop = lineWidth + textWidth / 6.0f;
14109                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
14110 
14111                 textView.invalidate();
14112                 mChoreographer.postFrameCallback(mStartCallback);
14113             }
14114         }
14115 
getGhostOffset()14116         float getGhostOffset() {
14117             return mGhostOffset;
14118         }
14119 
getScroll()14120         float getScroll() {
14121             return mScroll;
14122         }
14123 
getMaxFadeScroll()14124         float getMaxFadeScroll() {
14125             return mMaxFadeScroll;
14126         }
14127 
shouldDrawLeftFade()14128         boolean shouldDrawLeftFade() {
14129             return mScroll <= mFadeStop;
14130         }
14131 
shouldDrawGhost()14132         boolean shouldDrawGhost() {
14133             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
14134         }
14135 
isRunning()14136         boolean isRunning() {
14137             return mStatus == MARQUEE_RUNNING;
14138         }
14139 
isStopped()14140         boolean isStopped() {
14141             return mStatus == MARQUEE_STOPPED;
14142         }
14143     }
14144 
14145     private class ChangeWatcher implements TextWatcher, SpanWatcher {
14146 
14147         private CharSequence mBeforeText;
14148 
beforeTextChanged(CharSequence buffer, int start, int before, int after)14149         public void beforeTextChanged(CharSequence buffer, int start,
14150                                       int before, int after) {
14151             if (DEBUG_EXTRACT) {
14152                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
14153                         + " before=" + before + " after=" + after + ": " + buffer);
14154             }
14155 
14156             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
14157                 mBeforeText = mTransformed.toString();
14158             }
14159 
14160             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
14161         }
14162 
onTextChanged(CharSequence buffer, int start, int before, int after)14163         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
14164             if (DEBUG_EXTRACT) {
14165                 Log.v(LOG_TAG, "onTextChanged start=" + start
14166                         + " before=" + before + " after=" + after + ": " + buffer);
14167             }
14168             TextView.this.handleTextChanged(buffer, start, before, after);
14169 
14170             if (isVisibleToAccessibility()) {
14171                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
14172                 mBeforeText = null;
14173             }
14174         }
14175 
afterTextChanged(Editable buffer)14176         public void afterTextChanged(Editable buffer) {
14177             if (DEBUG_EXTRACT) {
14178                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
14179             }
14180             TextView.this.sendAfterTextChanged(buffer);
14181 
14182             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
14183                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
14184             }
14185         }
14186 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)14187         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
14188             if (DEBUG_EXTRACT) {
14189                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
14190                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
14191             }
14192             TextView.this.spanChange(buf, what, s, st, e, en);
14193         }
14194 
onSpanAdded(Spannable buf, Object what, int s, int e)14195         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
14196             if (DEBUG_EXTRACT) {
14197                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
14198             }
14199             TextView.this.spanChange(buf, what, -1, s, -1, e);
14200         }
14201 
onSpanRemoved(Spannable buf, Object what, int s, int e)14202         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
14203             if (DEBUG_EXTRACT) {
14204                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
14205             }
14206             TextView.this.spanChange(buf, what, s, -1, e, -1);
14207         }
14208     }
14209 
14210     /** @hide */
14211     @Override
onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)14212     public void onInputConnectionOpenedInternal(@NonNull InputConnection ic,
14213             @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
14214         if (mEditor != null) {
14215             mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic,
14216                     editorInfo);
14217         }
14218     }
14219 
14220     /** @hide */
14221     @Override
onInputConnectionClosedInternal()14222     public void onInputConnectionClosedInternal() {
14223         if (mEditor != null) {
14224             mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo();
14225         }
14226     }
14227 
14228     /**
14229      * Default {@link TextView} implementation for receiving content. Apps wishing to provide
14230      * custom behavior should configure a listener via {@link #setOnReceiveContentListener}.
14231      *
14232      * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in
14233      * content without acting on it).
14234      *
14235      * <p>For editable TextViews the default behavior is to insert text into the view, coercing
14236      * non-text content to text as needed. The MIME types "text/plain" and "text/html" have
14237      * well-defined behavior for this, while other MIME types have reasonable fallback behavior
14238      * (see {@link ClipData.Item#coerceToStyledText}).
14239      *
14240      * @param payload The content to insert and related metadata.
14241      *
14242      * @return The portion of the passed-in content that was not handled (may be all, some, or none
14243      * of the passed-in content).
14244      */
14245     @Nullable
14246     @Override
onReceiveContent(@onNull ContentInfo payload)14247     public ContentInfo onReceiveContent(@NonNull ContentInfo payload) {
14248         if (mEditor != null) {
14249             return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload);
14250         }
14251         return payload;
14252     }
14253 
logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)14254     private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
14255         if (msgFormat == null) {
14256             Log.d(LOG_TAG, location);
14257         } else {
14258             Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs));
14259         }
14260     }
14261 
14262     /**
14263      * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
14264      * the view.
14265      *
14266      * <p>NOTE: When overriding the method, it should not collect a request to translate this
14267      * TextView if it is displaying a password.
14268      *
14269      * @param supportedFormats the supported translation format. The value could be {@link
14270      *                         android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
14271      * @param requestsCollector {@link Consumer} to receiver the {@link ViewTranslationRequest}
14272      *                                         which contains the information to be translated.
14273      */
14274     @Override
onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)14275     public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats,
14276             @NonNull Consumer<ViewTranslationRequest> requestsCollector) {
14277         if (supportedFormats == null || supportedFormats.length == 0) {
14278             if (UiTranslationController.DEBUG) {
14279                 Log.w(LOG_TAG, "Do not provide the support translation formats.");
14280             }
14281             return;
14282         }
14283         ViewTranslationRequest.Builder requestBuilder =
14284                 new ViewTranslationRequest.Builder(getAutofillId());
14285         // Support Text translation
14286         if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) {
14287             if (mText == null || mText.length() == 0) {
14288                 if (UiTranslationController.DEBUG) {
14289                     Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
14290                 }
14291                 return;
14292             }
14293             boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
14294             if (isTextEditable() || isPassword) {
14295                 Log.w(LOG_TAG, "Cannot create translation request. editable = "
14296                         + isTextEditable() + ", isPassword = " + isPassword);
14297                 return;
14298             }
14299             // TODO(b/176488462): apply the view's important for translation
14300             requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
14301                     TranslationRequestValue.forText(mText));
14302             if (!TextUtils.isEmpty(getContentDescription())) {
14303                 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION,
14304                         TranslationRequestValue.forText(getContentDescription()));
14305             }
14306         }
14307         requestsCollector.accept(requestBuilder.build());
14308     }
14309 }
14310