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.FontScaleConverterFactory;
66 import android.content.res.Resources;
67 import android.content.res.TypedArray;
68 import android.content.res.XmlResourceParser;
69 import android.graphics.BaseCanvas;
70 import android.graphics.BlendMode;
71 import android.graphics.Canvas;
72 import android.graphics.Color;
73 import android.graphics.Insets;
74 import android.graphics.Matrix;
75 import android.graphics.Paint;
76 import android.graphics.Paint.FontMetricsInt;
77 import android.graphics.Path;
78 import android.graphics.PointF;
79 import android.graphics.PorterDuff;
80 import android.graphics.Rect;
81 import android.graphics.RectF;
82 import android.graphics.Typeface;
83 import android.graphics.drawable.Drawable;
84 import android.graphics.fonts.FontStyle;
85 import android.graphics.fonts.FontVariationAxis;
86 import android.graphics.text.LineBreakConfig;
87 import android.icu.text.DecimalFormatSymbols;
88 import android.os.AsyncTask;
89 import android.os.Build;
90 import android.os.Build.VERSION_CODES;
91 import android.os.Bundle;
92 import android.os.CancellationSignal;
93 import android.os.Handler;
94 import android.os.LocaleList;
95 import android.os.Parcel;
96 import android.os.Parcelable;
97 import android.os.ParcelableParcel;
98 import android.os.Process;
99 import android.os.SystemClock;
100 import android.os.UserHandle;
101 import android.provider.Settings;
102 import android.text.BoringLayout;
103 import android.text.DynamicLayout;
104 import android.text.Editable;
105 import android.text.GetChars;
106 import android.text.GraphemeClusterSegmentFinder;
107 import android.text.GraphicsOperations;
108 import android.text.Highlights;
109 import android.text.InputFilter;
110 import android.text.InputType;
111 import android.text.Layout;
112 import android.text.NoCopySpan;
113 import android.text.ParcelableSpan;
114 import android.text.PrecomputedText;
115 import android.text.SegmentFinder;
116 import android.text.Selection;
117 import android.text.SpanWatcher;
118 import android.text.Spannable;
119 import android.text.SpannableStringBuilder;
120 import android.text.Spanned;
121 import android.text.SpannedString;
122 import android.text.StaticLayout;
123 import android.text.TextDirectionHeuristic;
124 import android.text.TextDirectionHeuristics;
125 import android.text.TextPaint;
126 import android.text.TextUtils;
127 import android.text.TextUtils.TruncateAt;
128 import android.text.TextWatcher;
129 import android.text.WordSegmentFinder;
130 import android.text.method.AllCapsTransformationMethod;
131 import android.text.method.ArrowKeyMovementMethod;
132 import android.text.method.DateKeyListener;
133 import android.text.method.DateTimeKeyListener;
134 import android.text.method.DialerKeyListener;
135 import android.text.method.DigitsKeyListener;
136 import android.text.method.KeyListener;
137 import android.text.method.LinkMovementMethod;
138 import android.text.method.MetaKeyKeyListener;
139 import android.text.method.MovementMethod;
140 import android.text.method.OffsetMapping;
141 import android.text.method.PasswordTransformationMethod;
142 import android.text.method.SingleLineTransformationMethod;
143 import android.text.method.TextKeyListener;
144 import android.text.method.TimeKeyListener;
145 import android.text.method.TransformationMethod;
146 import android.text.method.TransformationMethod2;
147 import android.text.method.WordIterator;
148 import android.text.style.CharacterStyle;
149 import android.text.style.ClickableSpan;
150 import android.text.style.ParagraphStyle;
151 import android.text.style.SpellCheckSpan;
152 import android.text.style.SuggestionSpan;
153 import android.text.style.URLSpan;
154 import android.text.style.UpdateAppearance;
155 import android.text.util.Linkify;
156 import android.util.ArraySet;
157 import android.util.AttributeSet;
158 import android.util.DisplayMetrics;
159 import android.util.IntArray;
160 import android.util.Log;
161 import android.util.SparseIntArray;
162 import android.util.TypedValue;
163 import android.view.AccessibilityIterators.TextSegmentIterator;
164 import android.view.ActionMode;
165 import android.view.Choreographer;
166 import android.view.ContentInfo;
167 import android.view.ContextMenu;
168 import android.view.DragEvent;
169 import android.view.Gravity;
170 import android.view.HapticFeedbackConstants;
171 import android.view.InputDevice;
172 import android.view.KeyCharacterMap;
173 import android.view.KeyEvent;
174 import android.view.MotionEvent;
175 import android.view.PointerIcon;
176 import android.view.View;
177 import android.view.ViewConfiguration;
178 import android.view.ViewDebug;
179 import android.view.ViewGroup.LayoutParams;
180 import android.view.ViewHierarchyEncoder;
181 import android.view.ViewParent;
182 import android.view.ViewRootImpl;
183 import android.view.ViewStructure;
184 import android.view.ViewTreeObserver;
185 import android.view.accessibility.AccessibilityEvent;
186 import android.view.accessibility.AccessibilityManager;
187 import android.view.accessibility.AccessibilityNodeInfo;
188 import android.view.animation.AnimationUtils;
189 import android.view.autofill.AutofillManager;
190 import android.view.autofill.AutofillValue;
191 import android.view.contentcapture.ContentCaptureManager;
192 import android.view.contentcapture.ContentCaptureSession;
193 import android.view.inputmethod.BaseInputConnection;
194 import android.view.inputmethod.CompletionInfo;
195 import android.view.inputmethod.CorrectionInfo;
196 import android.view.inputmethod.CursorAnchorInfo;
197 import android.view.inputmethod.DeleteGesture;
198 import android.view.inputmethod.DeleteRangeGesture;
199 import android.view.inputmethod.EditorBoundsInfo;
200 import android.view.inputmethod.EditorInfo;
201 import android.view.inputmethod.ExtractedText;
202 import android.view.inputmethod.ExtractedTextRequest;
203 import android.view.inputmethod.HandwritingGesture;
204 import android.view.inputmethod.InputConnection;
205 import android.view.inputmethod.InputMethodManager;
206 import android.view.inputmethod.InsertGesture;
207 import android.view.inputmethod.InsertModeGesture;
208 import android.view.inputmethod.JoinOrSplitGesture;
209 import android.view.inputmethod.PreviewableHandwritingGesture;
210 import android.view.inputmethod.RemoveSpaceGesture;
211 import android.view.inputmethod.SelectGesture;
212 import android.view.inputmethod.SelectRangeGesture;
213 import android.view.inputmethod.TextAppearanceInfo;
214 import android.view.inputmethod.TextBoundsInfo;
215 import android.view.inspector.InspectableProperty;
216 import android.view.inspector.InspectableProperty.EnumEntry;
217 import android.view.inspector.InspectableProperty.FlagEntry;
218 import android.view.textclassifier.TextClassification;
219 import android.view.textclassifier.TextClassificationContext;
220 import android.view.textclassifier.TextClassificationManager;
221 import android.view.textclassifier.TextClassifier;
222 import android.view.textclassifier.TextLinks;
223 import android.view.textservice.SpellCheckerSubtype;
224 import android.view.textservice.TextServicesManager;
225 import android.view.translation.TranslationRequestValue;
226 import android.view.translation.TranslationSpec;
227 import android.view.translation.UiTranslationController;
228 import android.view.translation.ViewTranslationCallback;
229 import android.view.translation.ViewTranslationRequest;
230 import android.widget.RemoteViews.RemoteView;
231 
232 import com.android.internal.accessibility.util.AccessibilityUtils;
233 import com.android.internal.annotations.VisibleForTesting;
234 import com.android.internal.graphics.ColorUtils;
235 import com.android.internal.inputmethod.EditableInputConnection;
236 import com.android.internal.logging.MetricsLogger;
237 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
238 import com.android.internal.util.ArrayUtils;
239 import com.android.internal.util.FastMath;
240 import com.android.internal.util.Preconditions;
241 
242 import libcore.util.EmptyArray;
243 
244 import org.xmlpull.v1.XmlPullParserException;
245 
246 import java.io.IOException;
247 import java.lang.annotation.Retention;
248 import java.lang.annotation.RetentionPolicy;
249 import java.lang.ref.WeakReference;
250 import java.util.ArrayList;
251 import java.util.Arrays;
252 import java.util.List;
253 import java.util.Locale;
254 import java.util.Objects;
255 import java.util.Set;
256 import java.util.concurrent.CompletableFuture;
257 import java.util.concurrent.TimeUnit;
258 import java.util.function.Consumer;
259 import java.util.function.Supplier;
260 import java.util.regex.Matcher;
261 import java.util.regex.Pattern;
262 
263 /**
264  * A user interface element that displays text to the user.
265  * To provide user-editable text, see {@link EditText}.
266  * <p>
267  * The following code sample shows a typical use, with an XML layout
268  * and code to modify the contents of the text view:
269  * </p>
270 
271  * <pre>
272  * &lt;LinearLayout
273        xmlns:android="http://schemas.android.com/apk/res/android"
274        android:layout_width="match_parent"
275        android:layout_height="match_parent"&gt;
276  *    &lt;TextView
277  *        android:id="@+id/text_view_id"
278  *        android:layout_height="wrap_content"
279  *        android:layout_width="wrap_content"
280  *        android:text="@string/hello" /&gt;
281  * &lt;/LinearLayout&gt;
282  * </pre>
283  * <p>
284  * This code sample demonstrates how to modify the contents of the text view
285  * defined in the previous XML layout:
286  * </p>
287  * <pre>
288  * public class MainActivity extends Activity {
289  *
290  *    protected void onCreate(Bundle savedInstanceState) {
291  *         super.onCreate(savedInstanceState);
292  *         setContentView(R.layout.activity_main);
293  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
294  *         helloTextView.setText(R.string.user_greeting);
295  *     }
296  * }
297  * </pre>
298  * <p>
299  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
300  * </p>
301  * <p>
302  * <b>XML attributes</b>
303  * <p>
304  * See {@link android.R.styleable#TextView TextView Attributes},
305  * {@link android.R.styleable#View View Attributes}
306  *
307  * @attr ref android.R.styleable#TextView_text
308  * @attr ref android.R.styleable#TextView_bufferType
309  * @attr ref android.R.styleable#TextView_hint
310  * @attr ref android.R.styleable#TextView_textColor
311  * @attr ref android.R.styleable#TextView_textColorHighlight
312  * @attr ref android.R.styleable#TextView_textColorHint
313  * @attr ref android.R.styleable#TextView_textAppearance
314  * @attr ref android.R.styleable#TextView_textColorLink
315  * @attr ref android.R.styleable#TextView_textFontWeight
316  * @attr ref android.R.styleable#TextView_textSize
317  * @attr ref android.R.styleable#TextView_textScaleX
318  * @attr ref android.R.styleable#TextView_fontFamily
319  * @attr ref android.R.styleable#TextView_typeface
320  * @attr ref android.R.styleable#TextView_textStyle
321  * @attr ref android.R.styleable#TextView_cursorVisible
322  * @attr ref android.R.styleable#TextView_maxLines
323  * @attr ref android.R.styleable#TextView_maxHeight
324  * @attr ref android.R.styleable#TextView_lines
325  * @attr ref android.R.styleable#TextView_height
326  * @attr ref android.R.styleable#TextView_minLines
327  * @attr ref android.R.styleable#TextView_minHeight
328  * @attr ref android.R.styleable#TextView_maxEms
329  * @attr ref android.R.styleable#TextView_maxWidth
330  * @attr ref android.R.styleable#TextView_ems
331  * @attr ref android.R.styleable#TextView_width
332  * @attr ref android.R.styleable#TextView_minEms
333  * @attr ref android.R.styleable#TextView_minWidth
334  * @attr ref android.R.styleable#TextView_gravity
335  * @attr ref android.R.styleable#TextView_scrollHorizontally
336  * @attr ref android.R.styleable#TextView_password
337  * @attr ref android.R.styleable#TextView_singleLine
338  * @attr ref android.R.styleable#TextView_selectAllOnFocus
339  * @attr ref android.R.styleable#TextView_includeFontPadding
340  * @attr ref android.R.styleable#TextView_maxLength
341  * @attr ref android.R.styleable#TextView_shadowColor
342  * @attr ref android.R.styleable#TextView_shadowDx
343  * @attr ref android.R.styleable#TextView_shadowDy
344  * @attr ref android.R.styleable#TextView_shadowRadius
345  * @attr ref android.R.styleable#TextView_autoLink
346  * @attr ref android.R.styleable#TextView_linksClickable
347  * @attr ref android.R.styleable#TextView_numeric
348  * @attr ref android.R.styleable#TextView_digits
349  * @attr ref android.R.styleable#TextView_phoneNumber
350  * @attr ref android.R.styleable#TextView_inputMethod
351  * @attr ref android.R.styleable#TextView_capitalize
352  * @attr ref android.R.styleable#TextView_autoText
353  * @attr ref android.R.styleable#TextView_editable
354  * @attr ref android.R.styleable#TextView_freezesText
355  * @attr ref android.R.styleable#TextView_ellipsize
356  * @attr ref android.R.styleable#TextView_drawableTop
357  * @attr ref android.R.styleable#TextView_drawableBottom
358  * @attr ref android.R.styleable#TextView_drawableRight
359  * @attr ref android.R.styleable#TextView_drawableLeft
360  * @attr ref android.R.styleable#TextView_drawableStart
361  * @attr ref android.R.styleable#TextView_drawableEnd
362  * @attr ref android.R.styleable#TextView_drawablePadding
363  * @attr ref android.R.styleable#TextView_drawableTint
364  * @attr ref android.R.styleable#TextView_drawableTintMode
365  * @attr ref android.R.styleable#TextView_lineSpacingExtra
366  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
367  * @attr ref android.R.styleable#TextView_justificationMode
368  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
369  * @attr ref android.R.styleable#TextView_inputType
370  * @attr ref android.R.styleable#TextView_imeOptions
371  * @attr ref android.R.styleable#TextView_privateImeOptions
372  * @attr ref android.R.styleable#TextView_imeActionLabel
373  * @attr ref android.R.styleable#TextView_imeActionId
374  * @attr ref android.R.styleable#TextView_editorExtras
375  * @attr ref android.R.styleable#TextView_elegantTextHeight
376  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
377  * @attr ref android.R.styleable#TextView_letterSpacing
378  * @attr ref android.R.styleable#TextView_fontFeatureSettings
379  * @attr ref android.R.styleable#TextView_fontVariationSettings
380  * @attr ref android.R.styleable#TextView_breakStrategy
381  * @attr ref android.R.styleable#TextView_hyphenationFrequency
382  * @attr ref android.R.styleable#TextView_lineBreakStyle
383  * @attr ref android.R.styleable#TextView_lineBreakWordStyle
384  * @attr ref android.R.styleable#TextView_autoSizeTextType
385  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
386  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
387  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
388  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
389  * @attr ref android.R.styleable#TextView_textCursorDrawable
390  * @attr ref android.R.styleable#TextView_textSelectHandle
391  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
392  * @attr ref android.R.styleable#TextView_textSelectHandleRight
393  * @attr ref android.R.styleable#TextView_allowUndo
394  * @attr ref android.R.styleable#TextView_enabled
395  */
396 @RemoteView
397 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
398     static final String LOG_TAG = "TextView";
399     static final boolean DEBUG_EXTRACT = false;
400     static final boolean DEBUG_CURSOR = false;
401 
402     private static final float[] TEMP_POSITION = new float[2];
403 
404     // Enum for the "typeface" XML parameter.
405     // TODO: How can we get this from the XML instead of hardcoding it here?
406     /** @hide */
407     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
408     @Retention(RetentionPolicy.SOURCE)
409     public @interface XMLTypefaceAttr{}
410     private static final int DEFAULT_TYPEFACE = -1;
411     private static final int SANS = 1;
412     private static final int SERIF = 2;
413     private static final int MONOSPACE = 3;
414 
415     // Enum for the "ellipsize" XML parameter.
416     private static final int ELLIPSIZE_NOT_SET = -1;
417     private static final int ELLIPSIZE_NONE = 0;
418     private static final int ELLIPSIZE_START = 1;
419     private static final int ELLIPSIZE_MIDDLE = 2;
420     private static final int ELLIPSIZE_END = 3;
421     private static final int ELLIPSIZE_MARQUEE = 4;
422 
423     // Bitfield for the "numeric" XML parameter.
424     // TODO: How can we get this from the XML instead of hardcoding it here?
425     private static final int SIGNED = 2;
426     private static final int DECIMAL = 4;
427 
428     /**
429      * Draw marquee text with fading edges as usual
430      */
431     private static final int MARQUEE_FADE_NORMAL = 0;
432 
433     /**
434      * Draw marquee text as ellipsize end while inactive instead of with the fade.
435      * (Useful for devices where the fade can be expensive if overdone)
436      */
437     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
438 
439     /**
440      * Draw marquee text with fading edges because it is currently active/animating.
441      */
442     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
443 
444     @UnsupportedAppUsage
445     private static final int LINES = 1;
446     private static final int EMS = LINES;
447     private static final int PIXELS = 2;
448 
449     // Maximum text length for single line input.
450     private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
451     private InputFilter.LengthFilter mSingleLineLengthFilter = null;
452 
453     private static final RectF TEMP_RECTF = new RectF();
454 
455     /** @hide */
456     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
457     private static final int ANIMATED_SCROLL_GAP = 250;
458 
459     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
460     private static final Spanned EMPTY_SPANNED = new SpannedString("");
461 
462     private static final int CHANGE_WATCHER_PRIORITY = 100;
463 
464     /**
465      * The span priority of the {@link OffsetMapping} that is set on the text. It must be
466      * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is
467      * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered
468      * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}.
469      */
470     private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200;
471 
472     // New state used to change background based on whether this TextView is multiline.
473     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
474 
475     // Accessibility action to share selected text.
476     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
477 
478     /**
479      * @hide
480      */
481     // Accessibility action start id for "process text" actions.
482     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
483 
484     /** Accessibility action start id for "smart" actions. @hide */
485     static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000;
486 
487     /**
488      * @hide
489      */
490     @TestApi
491     public static final int PROCESS_TEXT_REQUEST_CODE = 100;
492 
493     /**
494      *  Return code of {@link #doKeyDown}.
495      */
496     private static final int KEY_EVENT_NOT_HANDLED = 0;
497     private static final int KEY_EVENT_HANDLED = -1;
498     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
499     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
500 
501     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
502 
503     // The default value of the line break style.
504     private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE;
505 
506     // The default value of the line break word style.
507     private static final int DEFAULT_LINE_BREAK_WORD_STYLE =
508             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
509 
510     /**
511      * This change ID enables the fallback text line spacing (line height) for BoringLayout.
512      * @hide
513      */
514     @ChangeId
515     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
516     public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id
517 
518     /**
519      * This change ID enables the fallback text line spacing (line height) for StaticLayout.
520      * @hide
521      */
522     @ChangeId
523     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P)
524     public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id
525 
526     // System wide time for last cut, copy or text changed action.
527     static long sLastCutCopyOrTextChangedTime;
528 
529     private ColorStateList mTextColor;
530     private ColorStateList mHintTextColor;
531     private ColorStateList mLinkTextColor;
532     @ViewDebug.ExportedProperty(category = "text")
533 
534     /**
535      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
536      */
537     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
538     private int mCurTextColor;
539 
540     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
541     private int mCurHintTextColor;
542     private boolean mFreezesText;
543 
544     @UnsupportedAppUsage
545     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
546     @UnsupportedAppUsage
547     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
548 
549     @UnsupportedAppUsage
550     private float mShadowRadius;
551     @UnsupportedAppUsage
552     private float mShadowDx;
553     @UnsupportedAppUsage
554     private float mShadowDy;
555     private int mShadowColor;
556 
557     private boolean mPreDrawRegistered;
558     private boolean mPreDrawListenerDetached;
559 
560     private TextClassifier mTextClassifier;
561     private TextClassifier mTextClassificationSession;
562     private TextClassificationContext mTextClassificationContext;
563 
564     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
565     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
566     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
567     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
568     // into by the user holding the movement key down) then we shouldn't prevent the focus from
569     // changing.
570     private boolean mPreventDefaultMovement;
571 
572     private TextUtils.TruncateAt mEllipsize;
573 
574     // A flag to indicate the cursor was hidden by IME.
575     private boolean mImeIsConsumingInput;
576 
577     // Whether cursor is visible without regard to {@link mImeConsumesInput}.
578     // {@code true} is the default value.
579     private boolean mCursorVisibleFromAttr = true;
580 
581     static class Drawables {
582         static final int LEFT = 0;
583         static final int TOP = 1;
584         static final int RIGHT = 2;
585         static final int BOTTOM = 3;
586 
587         static final int DRAWABLE_NONE = -1;
588         static final int DRAWABLE_RIGHT = 0;
589         static final int DRAWABLE_LEFT = 1;
590 
591         final Rect mCompoundRect = new Rect();
592 
593         final Drawable[] mShowing = new Drawable[4];
594 
595         ColorStateList mTintList;
596         BlendMode mBlendMode;
597         boolean mHasTint;
598         boolean mHasTintMode;
599 
600         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
601         Drawable mDrawableLeftInitial, mDrawableRightInitial;
602 
603         boolean mIsRtlCompatibilityMode;
604         boolean mOverride;
605 
606         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
607                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
608 
609         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
610                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
611 
612         int mDrawablePadding;
613 
614         int mDrawableSaved = DRAWABLE_NONE;
615 
Drawables(Context context)616         public Drawables(Context context) {
617             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
618             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
619                     || !context.getApplicationInfo().hasRtlSupport();
620             mOverride = false;
621         }
622 
623         /**
624          * @return {@code true} if this object contains metadata that needs to
625          *         be retained, {@code false} otherwise
626          */
627         public boolean hasMetadata() {
628             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
629         }
630 
631         /**
632          * Updates the list of displayed drawables to account for the current
633          * layout direction.
634          *
635          * @param layoutDirection the current layout direction
636          * @return {@code true} if the displayed drawables changed
637          */
638         public boolean resolveWithLayoutDirection(int layoutDirection) {
639             final Drawable previousLeft = mShowing[Drawables.LEFT];
640             final Drawable previousRight = mShowing[Drawables.RIGHT];
641 
642             // First reset "left" and "right" drawables to their initial values
643             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
644             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
645 
646             if (mIsRtlCompatibilityMode) {
647                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
648                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
649                     mShowing[Drawables.LEFT] = mDrawableStart;
650                     mDrawableSizeLeft = mDrawableSizeStart;
651                     mDrawableHeightLeft = mDrawableHeightStart;
652                 }
653                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
654                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
655                     mShowing[Drawables.RIGHT] = mDrawableEnd;
656                     mDrawableSizeRight = mDrawableSizeEnd;
657                     mDrawableHeightRight = mDrawableHeightEnd;
658                 }
659             } else {
660                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
661                 // drawable if and only if they have been defined
662                 switch(layoutDirection) {
663                     case LAYOUT_DIRECTION_RTL:
664                         if (mOverride) {
665                             mShowing[Drawables.RIGHT] = mDrawableStart;
666                             mDrawableSizeRight = mDrawableSizeStart;
667                             mDrawableHeightRight = mDrawableHeightStart;
668 
669                             mShowing[Drawables.LEFT] = mDrawableEnd;
670                             mDrawableSizeLeft = mDrawableSizeEnd;
671                             mDrawableHeightLeft = mDrawableHeightEnd;
672                         }
673                         break;
674 
675                     case LAYOUT_DIRECTION_LTR:
676                     default:
677                         if (mOverride) {
678                             mShowing[Drawables.LEFT] = mDrawableStart;
679                             mDrawableSizeLeft = mDrawableSizeStart;
680                             mDrawableHeightLeft = mDrawableHeightStart;
681 
682                             mShowing[Drawables.RIGHT] = mDrawableEnd;
683                             mDrawableSizeRight = mDrawableSizeEnd;
684                             mDrawableHeightRight = mDrawableHeightEnd;
685                         }
686                         break;
687                 }
688             }
689 
690             applyErrorDrawableIfNeeded(layoutDirection);
691 
692             return mShowing[Drawables.LEFT] != previousLeft
693                     || mShowing[Drawables.RIGHT] != previousRight;
694         }
695 
696         public void setErrorDrawable(Drawable dr, TextView tv) {
697             if (mDrawableError != dr && mDrawableError != null) {
698                 mDrawableError.setCallback(null);
699             }
700             mDrawableError = dr;
701 
702             if (mDrawableError != null) {
703                 final Rect compoundRect = mCompoundRect;
704                 final int[] state = tv.getDrawableState();
705 
706                 mDrawableError.setState(state);
707                 mDrawableError.copyBounds(compoundRect);
708                 mDrawableError.setCallback(tv);
709                 mDrawableSizeError = compoundRect.width();
710                 mDrawableHeightError = compoundRect.height();
711             } else {
712                 mDrawableSizeError = mDrawableHeightError = 0;
713             }
714         }
715 
716         private void applyErrorDrawableIfNeeded(int layoutDirection) {
717             // first restore the initial state if needed
718             switch (mDrawableSaved) {
719                 case DRAWABLE_LEFT:
720                     mShowing[Drawables.LEFT] = mDrawableTemp;
721                     mDrawableSizeLeft = mDrawableSizeTemp;
722                     mDrawableHeightLeft = mDrawableHeightTemp;
723                     break;
724                 case DRAWABLE_RIGHT:
725                     mShowing[Drawables.RIGHT] = mDrawableTemp;
726                     mDrawableSizeRight = mDrawableSizeTemp;
727                     mDrawableHeightRight = mDrawableHeightTemp;
728                     break;
729                 case DRAWABLE_NONE:
730                 default:
731             }
732             // then, if needed, assign the Error drawable to the correct location
733             if (mDrawableError != null) {
734                 switch(layoutDirection) {
735                     case LAYOUT_DIRECTION_RTL:
736                         mDrawableSaved = DRAWABLE_LEFT;
737 
738                         mDrawableTemp = mShowing[Drawables.LEFT];
739                         mDrawableSizeTemp = mDrawableSizeLeft;
740                         mDrawableHeightTemp = mDrawableHeightLeft;
741 
742                         mShowing[Drawables.LEFT] = mDrawableError;
743                         mDrawableSizeLeft = mDrawableSizeError;
744                         mDrawableHeightLeft = mDrawableHeightError;
745                         break;
746                     case LAYOUT_DIRECTION_LTR:
747                     default:
748                         mDrawableSaved = DRAWABLE_RIGHT;
749 
750                         mDrawableTemp = mShowing[Drawables.RIGHT];
751                         mDrawableSizeTemp = mDrawableSizeRight;
752                         mDrawableHeightTemp = mDrawableHeightRight;
753 
754                         mShowing[Drawables.RIGHT] = mDrawableError;
755                         mDrawableSizeRight = mDrawableSizeError;
756                         mDrawableHeightRight = mDrawableHeightError;
757                         break;
758                 }
759             }
760         }
761     }
762 
763     @UnsupportedAppUsage
764     Drawables mDrawables;
765 
766     @UnsupportedAppUsage
767     private CharWrapper mCharWrapper;
768 
769     @UnsupportedAppUsage(trackingBug = 124050217)
770     private Marquee mMarquee;
771     @UnsupportedAppUsage
772     private boolean mRestartMarquee;
773 
774     private int mMarqueeRepeatLimit = 3;
775 
776     private int mLastLayoutDirection = -1;
777 
778     /**
779      * On some devices the fading edges add a performance penalty if used
780      * extensively in the same layout. This mode indicates how the marquee
781      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
782      */
783     @UnsupportedAppUsage
784     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
785 
786     /**
787      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
788      * the layout that should be used when the mode switches.
789      */
790     @UnsupportedAppUsage
791     private Layout mSavedMarqueeModeLayout;
792 
793     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
794     @ViewDebug.ExportedProperty(category = "text")
795     @UnsupportedAppUsage
796     private @Nullable CharSequence mText;
797     private @Nullable Spannable mSpannable;
798     private @Nullable PrecomputedText mPrecomputed;
799 
800     @UnsupportedAppUsage
801     private CharSequence mTransformed;
802     @UnsupportedAppUsage
803     private BufferType mBufferType = BufferType.NORMAL;
804 
805     private CharSequence mHint;
806     @UnsupportedAppUsage
807     private Layout mHintLayout;
808     private boolean mHideHint;
809 
810     private MovementMethod mMovement;
811 
812     private TransformationMethod mTransformation;
813     @UnsupportedAppUsage
814     private boolean mAllowTransformationLengthChange;
815     @UnsupportedAppUsage
816     private ChangeWatcher mChangeWatcher;
817 
818     @UnsupportedAppUsage(trackingBug = 123769451)
819     private ArrayList<TextWatcher> mListeners;
820 
821     // display attributes
822     @UnsupportedAppUsage
823     private final TextPaint mTextPaint;
824     @UnsupportedAppUsage
825     private boolean mUserSetTextScaleX;
826     @UnsupportedAppUsage
827     private Layout mLayout;
828     private boolean mLocalesChanged = false;
829     private int mTextSizeUnit = -1;
830     private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
831     private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
832 
833     // This is used to reflect the current user preference for changing font weight and making text
834     // more bold.
835     private int mFontWeightAdjustment;
836     private Typeface mOriginalTypeface;
837 
838     // True if setKeyListener() has been explicitly called
839     private boolean mListenerChanged = false;
840     // True if internationalized input should be used for numbers and date and time.
841     private final boolean mUseInternationalizedInput;
842 
843     // Fallback fonts that end up getting used should be allowed to affect line spacing.
844     private static final int FALLBACK_LINE_SPACING_NONE = 0;
845     private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1;
846     private static final int FALLBACK_LINE_SPACING_ALL = 2;
847 
848     private int mUseFallbackLineSpacing;
849     // True if the view text can be padded for compat reasons, when the view is translated.
850     private final boolean mUseTextPaddingForUiTranslation;
851 
852     @ViewDebug.ExportedProperty(category = "text")
853     @UnsupportedAppUsage
854     private int mGravity = Gravity.TOP | Gravity.START;
855     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
856     private boolean mHorizontallyScrolling;
857 
858     private int mAutoLinkMask;
859     private boolean mLinksClickable = true;
860 
861     @UnsupportedAppUsage
862     private float mSpacingMult = 1.0f;
863     @UnsupportedAppUsage
864     private float mSpacingAdd = 0.0f;
865 
866     /**
867      * Remembers what line height was set to originally, before we broke it down into raw pixels.
868      *
869      * <p>This is stored as a complex dimension with both value and unit packed into one field!
870      * {@see TypedValue}
871      */
872     private int mLineHeightComplexDimen;
873 
874     private int mBreakStrategy;
875     private int mHyphenationFrequency;
876     private int mJustificationMode;
877 
878     @UnsupportedAppUsage
879     private int mMaximum = Integer.MAX_VALUE;
880     @UnsupportedAppUsage
881     private int mMaxMode = LINES;
882     @UnsupportedAppUsage
883     private int mMinimum = 0;
884     @UnsupportedAppUsage
885     private int mMinMode = LINES;
886 
887     @UnsupportedAppUsage
888     private int mOldMaximum = mMaximum;
889     @UnsupportedAppUsage
890     private int mOldMaxMode = mMaxMode;
891 
892     @UnsupportedAppUsage
893     private int mMaxWidth = Integer.MAX_VALUE;
894     @UnsupportedAppUsage
895     private int mMaxWidthMode = PIXELS;
896     @UnsupportedAppUsage
897     private int mMinWidth = 0;
898     @UnsupportedAppUsage
899     private int mMinWidthMode = PIXELS;
900 
901     @UnsupportedAppUsage
902     private boolean mSingleLine;
903     @UnsupportedAppUsage
904     private int mDesiredHeightAtMeasure = -1;
905     @UnsupportedAppUsage
906     private boolean mIncludePad = true;
907     private int mDeferScroll = -1;
908 
909     // tmp primitives, so we don't alloc them on each draw
910     private Rect mTempRect;
911     private long mLastScroll;
912     private Scroller mScroller;
913     private TextPaint mTempTextPaint;
914 
915     private Object mTempCursor;
916 
917     @UnsupportedAppUsage
918     private BoringLayout.Metrics mBoring;
919     @UnsupportedAppUsage
920     private BoringLayout.Metrics mHintBoring;
921     @UnsupportedAppUsage
922     private BoringLayout mSavedLayout;
923     @UnsupportedAppUsage
924     private BoringLayout mSavedHintLayout;
925 
926     @UnsupportedAppUsage
927     private TextDirectionHeuristic mTextDir;
928 
929     private InputFilter[] mFilters = NO_FILTERS;
930 
931     /**
932      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
933      * the same as {@link Process#myUserHandle()}.
934      *
935      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
936      * other apps may need to set this so that the system can use right user's resources and
937      * services such as input methods and spell checkers.</p>
938      *
939      * @see #setTextOperationUser(UserHandle)
940      */
941     @Nullable
942     private UserHandle mTextOperationUser;
943 
944     private volatile Locale mCurrentSpellCheckerLocaleCache;
945 
946     // It is possible to have a selection even when mEditor is null (programmatically set, like when
947     // a link is pressed). These highlight-related fields do not go in mEditor.
948     @UnsupportedAppUsage
949     int mHighlightColor = 0x6633B5E5;
950     private Path mHighlightPath;
951     @UnsupportedAppUsage
952     private final Paint mHighlightPaint;
953     @UnsupportedAppUsage
954     private boolean mHighlightPathBogus = true;
955 
956     private List<Path> mHighlightPaths;
957     private List<Paint> mHighlightPaints;
958     private Highlights mHighlights;
959     private int[] mSearchResultHighlights = null;
960     private Paint mSearchResultHighlightPaint = null;
961     private Paint mFocusedSearchResultHighlightPaint = null;
962     private int mFocusedSearchResultHighlightColor = 0xFFFF9632;
963     private int mSearchResultHighlightColor = 0xFFFFFF00;
964 
965     private int mFocusedSearchResultIndex = -1;
966     private int mGesturePreviewHighlightStart = -1;
967     private int mGesturePreviewHighlightEnd = -1;
968     private Paint mGesturePreviewHighlightPaint;
969     private final List<Path> mPathRecyclePool = new ArrayList<>();
970     private boolean mHighlightPathsBogus = true;
971 
972     // Although these fields are specific to editable text, they are not added to Editor because
973     // they are defined by the TextView's style and are theme-dependent.
974     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
975     int mCursorDrawableRes;
976     private Drawable mCursorDrawable;
977     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
978     // by removing it, but we would break apps targeting <= P that use it by reflection.
979     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
980     int mTextSelectHandleLeftRes;
981     private Drawable mTextSelectHandleLeft;
982     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
983     // by removing it, but we would break apps targeting <= P that use it by reflection.
984     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
985     int mTextSelectHandleRightRes;
986     private Drawable mTextSelectHandleRight;
987     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
988     // by removing it, but we would break apps targeting <= P that use it by reflection.
989     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
990     int mTextSelectHandleRes;
991     private Drawable mTextSelectHandle;
992     int mTextEditSuggestionItemLayout;
993     int mTextEditSuggestionContainerLayout;
994     int mTextEditSuggestionHighlightStyle;
995 
996     private static final int NO_POINTER_ID = -1;
997     /**
998      * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among
999      * TextView and the handle views which are rendered on popup windows.
1000      */
1001     private int mPrimePointerId = NO_POINTER_ID;
1002 
1003     /**
1004      * Whether the prime pointer is from the event delivered to selection handle or insertion
1005      * handle.
1006      */
1007     private boolean mIsPrimePointerFromHandleView;
1008 
1009     /**
1010      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
1011      * See {@link #createEditorIfNeeded()}.
1012      */
1013     @UnsupportedAppUsage
1014     private Editor mEditor;
1015 
1016     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
1017     private static final int DEVICE_PROVISIONED_NO = 1;
1018     private static final int DEVICE_PROVISIONED_YES = 2;
1019 
1020     /**
1021      * Some special options such as sharing selected text should only be shown if the device
1022      * is provisioned. Only check the provisioned state once for a given view instance.
1023      */
1024     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
1025 
1026     /**
1027      * The last input source on this TextView.
1028      *
1029      * Use the SOURCE_TOUCHSCREEN as the default value for backward compatibility. There could be a
1030      * non UI event originated ActionMode initiation, e.g. API call, a11y events, etc.
1031      */
1032     private int mLastInputSource = InputDevice.SOURCE_TOUCHSCREEN;
1033 
1034     /**
1035      * The TextView does not auto-size text (default).
1036      */
1037     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
1038 
1039     /**
1040      * The TextView scales text size both horizontally and vertically to fit within the
1041      * container.
1042      */
1043     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
1044 
1045     /** @hide */
1046     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
1047             AUTO_SIZE_TEXT_TYPE_NONE,
1048             AUTO_SIZE_TEXT_TYPE_UNIFORM
1049     })
1050     @Retention(RetentionPolicy.SOURCE)
1051     public @interface AutoSizeTextType {}
1052     // Default minimum size for auto-sizing text in scaled pixels.
1053     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
1054     // Default maximum size for auto-sizing text in scaled pixels.
1055     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
1056     // Default value for the step size in pixels.
1057     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
1058     // Use this to specify that any of the auto-size configuration int values have not been set.
1059     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
1060     // Auto-size text type.
1061     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1062     // Specify if auto-size text is needed.
1063     private boolean mNeedsAutoSizeText = false;
1064     // Step size for auto-sizing in pixels.
1065     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1066     // Minimum text size for auto-sizing in pixels.
1067     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1068     // Maximum text size for auto-sizing in pixels.
1069     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1070     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
1071     // when auto-sizing text.
1072     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
1073     // Specifies whether auto-size should use the provided auto size steps set or if it should
1074     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
1075     // mAutoSizeStepGranularityInPx.
1076     private boolean mHasPresetAutoSizeValues = false;
1077 
1078     // Autofill-related attributes
1079     //
1080     // Indicates whether the text was set statically or dynamically, so it can be used to
1081     // sanitize autofill requests.
1082     private boolean mTextSetFromXmlOrResourceId = false;
1083     // Resource id used to set the text.
1084     private @StringRes int mTextId = Resources.ID_NULL;
1085     // Resource id used to set the hint.
1086     private @StringRes int mHintId = Resources.ID_NULL;
1087     //
1088     // End of autofill-related attributes
1089 
1090     private Pattern mWhitespacePattern;
1091 
1092     /**
1093      * Kick-start the font cache for the zygote process (to pay the cost of
1094      * initializing freetype for our default font only once).
1095      * @hide
1096      */
1097     public static void preloadFontCache() {
1098         if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
1099             return;
1100         }
1101         Paint p = new Paint();
1102         p.setAntiAlias(true);
1103         // Ensure that the Typeface is loaded here.
1104         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
1105         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
1106         // since Paint.measureText can not be called without Typeface static initializer.
1107         p.setTypeface(Typeface.DEFAULT);
1108         // We don't care about the result, just the side-effect of measuring.
1109         p.measureText("H");
1110     }
1111 
1112     /**
1113      * Interface definition for a callback to be invoked when an action is
1114      * performed on the editor.
1115      */
1116     public interface OnEditorActionListener {
1117         /**
1118          * Called when an action is being performed.
1119          *
1120          * @param v The view that was clicked.
1121          * @param actionId Identifier of the action.  This will be either the
1122          * identifier you supplied, or {@link EditorInfo#IME_NULL
1123          * EditorInfo.IME_NULL} if being called due to the enter key
1124          * being pressed. Starting from Android 14, the action identifier will
1125          * also be included when triggered by an enter key if the input is
1126          * constrained to a single line.
1127          * @param event If triggered by an enter key, this is the event;
1128          * otherwise, this is null.
1129          * @return Return true if you have consumed the action, else false.
1130          */
1131         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
1132     }
1133 
1134     public TextView(Context context) {
1135         this(context, null);
1136     }
1137 
1138     public TextView(Context context, @Nullable AttributeSet attrs) {
1139         this(context, attrs, com.android.internal.R.attr.textViewStyle);
1140     }
1141 
1142     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
1143         this(context, attrs, defStyleAttr, 0);
1144     }
1145 
1146     @SuppressWarnings("deprecation")
1147     public TextView(
1148             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1149         super(context, attrs, defStyleAttr, defStyleRes);
1150 
1151         // TextView is important by default, unless app developer overrode attribute.
1152         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
1153             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
1154         }
1155         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
1156             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
1157         }
1158 
1159         setTextInternal("");
1160 
1161         final Resources res = getResources();
1162         final CompatibilityInfo compat = res.getCompatibilityInfo();
1163 
1164         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
1165         mTextPaint.density = res.getDisplayMetrics().density;
1166         mTextPaint.setCompatibilityScaling(compat.applicationScale);
1167 
1168         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1169         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
1170 
1171         mMovement = getDefaultMovementMethod();
1172 
1173         mTransformation = null;
1174 
1175         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
1176         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
1177         attributes.mTextSize = 15;
1178         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1179         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1180         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1181 
1182         final Resources.Theme theme = context.getTheme();
1183 
1184         /*
1185          * Look the appearance up without checking first if it exists because
1186          * almost every TextView has one and it greatly simplifies the logic
1187          * to be able to parse the appearance first and then let specific tags
1188          * for this View override it.
1189          */
1190         TypedArray a = theme.obtainStyledAttributes(attrs,
1191                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1192         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1193                 attrs, a, defStyleAttr, defStyleRes);
1194         TypedArray appearance = null;
1195         int ap = a.getResourceId(
1196                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1197         a.recycle();
1198         if (ap != -1) {
1199             appearance = theme.obtainStyledAttributes(
1200                     ap, com.android.internal.R.styleable.TextAppearance);
1201             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1202                     null, appearance, 0, ap);
1203         }
1204         if (appearance != null) {
1205             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1206             attributes.mFontFamilyExplicit = false;
1207             appearance.recycle();
1208         }
1209 
1210         boolean editable = getDefaultEditable();
1211         CharSequence inputMethod = null;
1212         int numeric = 0;
1213         CharSequence digits = null;
1214         boolean phone = false;
1215         boolean autotext = false;
1216         int autocap = -1;
1217         int buffertype = 0;
1218         boolean selectallonfocus = false;
1219         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1220                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1221         ColorStateList drawableTint = null;
1222         BlendMode drawableTintMode = null;
1223         int drawablePadding = 0;
1224         int ellipsize = ELLIPSIZE_NOT_SET;
1225         boolean singleLine = false;
1226         int maxlength = -1;
1227         CharSequence text = "";
1228         CharSequence hint = null;
1229         boolean password = false;
1230         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1231         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1232         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1233         int inputType = EditorInfo.TYPE_NULL;
1234         a = theme.obtainStyledAttributes(
1235                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1236         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1237                 defStyleAttr, defStyleRes);
1238         int firstBaselineToTopHeight = -1;
1239         int lastBaselineToBottomHeight = -1;
1240         float lineHeight = -1f;
1241         int lineHeightUnit = -1;
1242 
1243         readTextAppearance(context, a, attributes, true /* styleArray */);
1244 
1245         int n = a.getIndexCount();
1246 
1247         // Must set id in a temporary variable because it will be reset by setText()
1248         boolean textIsSetFromXml = false;
1249         for (int i = 0; i < n; i++) {
1250             int attr = a.getIndex(i);
1251 
1252             switch (attr) {
1253                 case com.android.internal.R.styleable.TextView_editable:
1254                     editable = a.getBoolean(attr, editable);
1255                     break;
1256 
1257                 case com.android.internal.R.styleable.TextView_inputMethod:
1258                     inputMethod = a.getText(attr);
1259                     break;
1260 
1261                 case com.android.internal.R.styleable.TextView_numeric:
1262                     numeric = a.getInt(attr, numeric);
1263                     break;
1264 
1265                 case com.android.internal.R.styleable.TextView_digits:
1266                     digits = a.getText(attr);
1267                     break;
1268 
1269                 case com.android.internal.R.styleable.TextView_phoneNumber:
1270                     phone = a.getBoolean(attr, phone);
1271                     break;
1272 
1273                 case com.android.internal.R.styleable.TextView_autoText:
1274                     autotext = a.getBoolean(attr, autotext);
1275                     break;
1276 
1277                 case com.android.internal.R.styleable.TextView_capitalize:
1278                     autocap = a.getInt(attr, autocap);
1279                     break;
1280 
1281                 case com.android.internal.R.styleable.TextView_bufferType:
1282                     buffertype = a.getInt(attr, buffertype);
1283                     break;
1284 
1285                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1286                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1287                     break;
1288 
1289                 case com.android.internal.R.styleable.TextView_autoLink:
1290                     mAutoLinkMask = a.getInt(attr, 0);
1291                     break;
1292 
1293                 case com.android.internal.R.styleable.TextView_linksClickable:
1294                     mLinksClickable = a.getBoolean(attr, true);
1295                     break;
1296 
1297                 case com.android.internal.R.styleable.TextView_drawableLeft:
1298                     drawableLeft = a.getDrawable(attr);
1299                     break;
1300 
1301                 case com.android.internal.R.styleable.TextView_drawableTop:
1302                     drawableTop = a.getDrawable(attr);
1303                     break;
1304 
1305                 case com.android.internal.R.styleable.TextView_drawableRight:
1306                     drawableRight = a.getDrawable(attr);
1307                     break;
1308 
1309                 case com.android.internal.R.styleable.TextView_drawableBottom:
1310                     drawableBottom = a.getDrawable(attr);
1311                     break;
1312 
1313                 case com.android.internal.R.styleable.TextView_drawableStart:
1314                     drawableStart = a.getDrawable(attr);
1315                     break;
1316 
1317                 case com.android.internal.R.styleable.TextView_drawableEnd:
1318                     drawableEnd = a.getDrawable(attr);
1319                     break;
1320 
1321                 case com.android.internal.R.styleable.TextView_drawableTint:
1322                     drawableTint = a.getColorStateList(attr);
1323                     break;
1324 
1325                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1326                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1327                             drawableTintMode);
1328                     break;
1329 
1330                 case com.android.internal.R.styleable.TextView_drawablePadding:
1331                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1332                     break;
1333 
1334                 case com.android.internal.R.styleable.TextView_maxLines:
1335                     setMaxLines(a.getInt(attr, -1));
1336                     break;
1337 
1338                 case com.android.internal.R.styleable.TextView_maxHeight:
1339                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1340                     break;
1341 
1342                 case com.android.internal.R.styleable.TextView_lines:
1343                     setLines(a.getInt(attr, -1));
1344                     break;
1345 
1346                 case com.android.internal.R.styleable.TextView_height:
1347                     setHeight(a.getDimensionPixelSize(attr, -1));
1348                     break;
1349 
1350                 case com.android.internal.R.styleable.TextView_minLines:
1351                     setMinLines(a.getInt(attr, -1));
1352                     break;
1353 
1354                 case com.android.internal.R.styleable.TextView_minHeight:
1355                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1356                     break;
1357 
1358                 case com.android.internal.R.styleable.TextView_maxEms:
1359                     setMaxEms(a.getInt(attr, -1));
1360                     break;
1361 
1362                 case com.android.internal.R.styleable.TextView_maxWidth:
1363                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1364                     break;
1365 
1366                 case com.android.internal.R.styleable.TextView_ems:
1367                     setEms(a.getInt(attr, -1));
1368                     break;
1369 
1370                 case com.android.internal.R.styleable.TextView_width:
1371                     setWidth(a.getDimensionPixelSize(attr, -1));
1372                     break;
1373 
1374                 case com.android.internal.R.styleable.TextView_minEms:
1375                     setMinEms(a.getInt(attr, -1));
1376                     break;
1377 
1378                 case com.android.internal.R.styleable.TextView_minWidth:
1379                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1380                     break;
1381 
1382                 case com.android.internal.R.styleable.TextView_gravity:
1383                     setGravity(a.getInt(attr, -1));
1384                     break;
1385 
1386                 case com.android.internal.R.styleable.TextView_hint:
1387                     mHintId = a.getResourceId(attr, Resources.ID_NULL);
1388                     hint = a.getText(attr);
1389                     break;
1390 
1391                 case com.android.internal.R.styleable.TextView_text:
1392                     textIsSetFromXml = true;
1393                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1394                     text = a.getText(attr);
1395                     break;
1396 
1397                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1398                     if (a.getBoolean(attr, false)) {
1399                         setHorizontallyScrolling(true);
1400                     }
1401                     break;
1402 
1403                 case com.android.internal.R.styleable.TextView_singleLine:
1404                     singleLine = a.getBoolean(attr, singleLine);
1405                     break;
1406 
1407                 case com.android.internal.R.styleable.TextView_ellipsize:
1408                     ellipsize = a.getInt(attr, ellipsize);
1409                     break;
1410 
1411                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1412                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1413                     break;
1414 
1415                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1416                     if (!a.getBoolean(attr, true)) {
1417                         setIncludeFontPadding(false);
1418                     }
1419                     break;
1420 
1421                 case com.android.internal.R.styleable.TextView_cursorVisible:
1422                     if (!a.getBoolean(attr, true)) {
1423                         setCursorVisible(false);
1424                     }
1425                     break;
1426 
1427                 case com.android.internal.R.styleable.TextView_maxLength:
1428                     maxlength = a.getInt(attr, -1);
1429                     break;
1430 
1431                 case com.android.internal.R.styleable.TextView_textScaleX:
1432                     setTextScaleX(a.getFloat(attr, 1.0f));
1433                     break;
1434 
1435                 case com.android.internal.R.styleable.TextView_freezesText:
1436                     mFreezesText = a.getBoolean(attr, false);
1437                     break;
1438 
1439                 case com.android.internal.R.styleable.TextView_enabled:
1440                     setEnabled(a.getBoolean(attr, isEnabled()));
1441                     break;
1442 
1443                 case com.android.internal.R.styleable.TextView_password:
1444                     password = a.getBoolean(attr, password);
1445                     break;
1446 
1447                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1448                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1449                     break;
1450 
1451                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1452                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1453                     break;
1454 
1455                 case com.android.internal.R.styleable.TextView_inputType:
1456                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1457                     break;
1458 
1459                 case com.android.internal.R.styleable.TextView_allowUndo:
1460                     createEditorIfNeeded();
1461                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1462                     break;
1463 
1464                 case com.android.internal.R.styleable.TextView_imeOptions:
1465                     createEditorIfNeeded();
1466                     mEditor.createInputContentTypeIfNeeded();
1467                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1468                             mEditor.mInputContentType.imeOptions);
1469                     break;
1470 
1471                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1472                     createEditorIfNeeded();
1473                     mEditor.createInputContentTypeIfNeeded();
1474                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1475                     break;
1476 
1477                 case com.android.internal.R.styleable.TextView_imeActionId:
1478                     createEditorIfNeeded();
1479                     mEditor.createInputContentTypeIfNeeded();
1480                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1481                             mEditor.mInputContentType.imeActionId);
1482                     break;
1483 
1484                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1485                     setPrivateImeOptions(a.getString(attr));
1486                     break;
1487 
1488                 case com.android.internal.R.styleable.TextView_editorExtras:
1489                     try {
1490                         setInputExtras(a.getResourceId(attr, 0));
1491                     } catch (XmlPullParserException e) {
1492                         Log.w(LOG_TAG, "Failure reading input extras", e);
1493                     } catch (IOException e) {
1494                         Log.w(LOG_TAG, "Failure reading input extras", e);
1495                     }
1496                     break;
1497 
1498                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1499                     mCursorDrawableRes = a.getResourceId(attr, 0);
1500                     break;
1501 
1502                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1503                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1504                     break;
1505 
1506                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1507                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1508                     break;
1509 
1510                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1511                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1512                     break;
1513 
1514                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1515                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1516                     break;
1517 
1518                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1519                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1520                     break;
1521 
1522                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1523                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1524                     break;
1525 
1526                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1527                     setTextIsSelectable(a.getBoolean(attr, false));
1528                     break;
1529 
1530                 case com.android.internal.R.styleable.TextView_breakStrategy:
1531                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1532                     break;
1533 
1534                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1535                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1536                     break;
1537 
1538                 case com.android.internal.R.styleable.TextView_lineBreakStyle:
1539                     mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE);
1540                     break;
1541 
1542                 case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
1543                     mLineBreakWordStyle = a.getInt(attr,
1544                             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
1545                     break;
1546 
1547                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1548                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1549                     break;
1550 
1551                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1552                     autoSizeStepGranularityInPx = a.getDimension(attr,
1553                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1554                     break;
1555 
1556                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1557                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1558                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1559                     break;
1560 
1561                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1562                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1563                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1564                     break;
1565 
1566                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1567                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1568                     if (autoSizeStepSizeArrayResId > 0) {
1569                         final TypedArray autoSizePresetTextSizes = a.getResources()
1570                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1571                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1572                         autoSizePresetTextSizes.recycle();
1573                     }
1574                     break;
1575                 case com.android.internal.R.styleable.TextView_justificationMode:
1576                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1577                     break;
1578 
1579                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1580                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1581                     break;
1582 
1583                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1584                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1585                     break;
1586 
1587                 case com.android.internal.R.styleable.TextView_lineHeight:
1588                     TypedValue peekValue = a.peekValue(attr);
1589                     if (peekValue != null && peekValue.type == TypedValue.TYPE_DIMENSION) {
1590                         lineHeightUnit = peekValue.getComplexUnit();
1591                         lineHeight = TypedValue.complexToFloat(peekValue.data);
1592                     } else {
1593                         lineHeight = a.getDimensionPixelSize(attr, -1);
1594                     }
1595                     break;
1596             }
1597         }
1598 
1599         a.recycle();
1600 
1601         BufferType bufferType = BufferType.EDITABLE;
1602 
1603         final int variation =
1604                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1605         final boolean passwordInputType = variation
1606                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1607         final boolean webPasswordInputType = variation
1608                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1609         final boolean numberPasswordInputType = variation
1610                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1611 
1612         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1613         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1614         if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
1615             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL;
1616         } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) {
1617             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
1618         } else {
1619             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
1620         }
1621         // TODO(b/179693024): Use a ChangeId instead.
1622         mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R;
1623 
1624         if (inputMethod != null) {
1625             Class<?> c;
1626 
1627             try {
1628                 c = Class.forName(inputMethod.toString());
1629             } catch (ClassNotFoundException ex) {
1630                 throw new RuntimeException(ex);
1631             }
1632 
1633             try {
1634                 createEditorIfNeeded();
1635                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1636             } catch (InstantiationException ex) {
1637                 throw new RuntimeException(ex);
1638             } catch (IllegalAccessException ex) {
1639                 throw new RuntimeException(ex);
1640             }
1641             try {
1642                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1643                         ? inputType
1644                         : mEditor.mKeyListener.getInputType();
1645             } catch (IncompatibleClassChangeError e) {
1646                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1647             }
1648         } else if (digits != null) {
1649             createEditorIfNeeded();
1650             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1651             // If no input type was specified, we will default to generic
1652             // text, since we can't tell the IME about the set of digits
1653             // that was selected.
1654             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1655                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1656         } else if (inputType != EditorInfo.TYPE_NULL) {
1657             setInputType(inputType, true);
1658             // If set, the input type overrides what was set using the deprecated singleLine flag.
1659             singleLine = !isMultilineInputType(inputType);
1660         } else if (phone) {
1661             createEditorIfNeeded();
1662             mEditor.mKeyListener = DialerKeyListener.getInstance();
1663             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1664         } else if (numeric != 0) {
1665             createEditorIfNeeded();
1666             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1667                     null,  // locale
1668                     (numeric & SIGNED) != 0,
1669                     (numeric & DECIMAL) != 0);
1670             inputType = mEditor.mKeyListener.getInputType();
1671             mEditor.mInputType = inputType;
1672         } else if (autotext || autocap != -1) {
1673             TextKeyListener.Capitalize cap;
1674 
1675             inputType = EditorInfo.TYPE_CLASS_TEXT;
1676 
1677             switch (autocap) {
1678                 case 1:
1679                     cap = TextKeyListener.Capitalize.SENTENCES;
1680                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1681                     break;
1682 
1683                 case 2:
1684                     cap = TextKeyListener.Capitalize.WORDS;
1685                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1686                     break;
1687 
1688                 case 3:
1689                     cap = TextKeyListener.Capitalize.CHARACTERS;
1690                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1691                     break;
1692 
1693                 default:
1694                     cap = TextKeyListener.Capitalize.NONE;
1695                     break;
1696             }
1697 
1698             createEditorIfNeeded();
1699             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1700             mEditor.mInputType = inputType;
1701         } else if (editable) {
1702             createEditorIfNeeded();
1703             mEditor.mKeyListener = TextKeyListener.getInstance();
1704             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1705         } else if (isTextSelectable()) {
1706             // Prevent text changes from keyboard.
1707             if (mEditor != null) {
1708                 mEditor.mKeyListener = null;
1709                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1710             }
1711             bufferType = BufferType.SPANNABLE;
1712             // So that selection can be changed using arrow keys and touch is handled.
1713             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1714         } else {
1715             if (mEditor != null) mEditor.mKeyListener = null;
1716 
1717             switch (buffertype) {
1718                 case 0:
1719                     bufferType = BufferType.NORMAL;
1720                     break;
1721                 case 1:
1722                     bufferType = BufferType.SPANNABLE;
1723                     break;
1724                 case 2:
1725                     bufferType = BufferType.EDITABLE;
1726                     break;
1727             }
1728         }
1729 
1730         if (mEditor != null) {
1731             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1732                     numberPasswordInputType);
1733         }
1734 
1735         if (selectallonfocus) {
1736             createEditorIfNeeded();
1737             mEditor.mSelectAllOnFocus = true;
1738 
1739             if (bufferType == BufferType.NORMAL) {
1740                 bufferType = BufferType.SPANNABLE;
1741             }
1742         }
1743 
1744         // Set up the tint (if needed) before setting the drawables so that it
1745         // gets applied correctly.
1746         if (drawableTint != null || drawableTintMode != null) {
1747             if (mDrawables == null) {
1748                 mDrawables = new Drawables(context);
1749             }
1750             if (drawableTint != null) {
1751                 mDrawables.mTintList = drawableTint;
1752                 mDrawables.mHasTint = true;
1753             }
1754             if (drawableTintMode != null) {
1755                 mDrawables.mBlendMode = drawableTintMode;
1756                 mDrawables.mHasTintMode = true;
1757             }
1758         }
1759 
1760         // This call will save the initial left/right drawables
1761         setCompoundDrawablesWithIntrinsicBounds(
1762                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1763         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1764         setCompoundDrawablePadding(drawablePadding);
1765 
1766         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1767         // of lines of height are unchanged for multi-line TextViews.
1768         setInputTypeSingleLine(singleLine);
1769         applySingleLine(singleLine, singleLine, singleLine,
1770                 // Does not apply automated max length filter since length filter will be resolved
1771                 // later in this function.
1772                 false
1773         );
1774 
1775         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1776             ellipsize = ELLIPSIZE_END;
1777         }
1778 
1779         switch (ellipsize) {
1780             case ELLIPSIZE_START:
1781                 setEllipsize(TextUtils.TruncateAt.START);
1782                 break;
1783             case ELLIPSIZE_MIDDLE:
1784                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1785                 break;
1786             case ELLIPSIZE_END:
1787                 setEllipsize(TextUtils.TruncateAt.END);
1788                 break;
1789             case ELLIPSIZE_MARQUEE:
1790                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1791                     setHorizontalFadingEdgeEnabled(true);
1792                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1793                 } else {
1794                     setHorizontalFadingEdgeEnabled(false);
1795                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1796                 }
1797                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1798                 break;
1799         }
1800 
1801         final boolean isPassword = password || passwordInputType || webPasswordInputType
1802                 || numberPasswordInputType;
1803         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1804                 && (mEditor.mInputType
1805                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1806                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1807         if (isMonospaceEnforced) {
1808             attributes.mTypefaceIndex = MONOSPACE;
1809         }
1810 
1811         mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment;
1812         applyTextAppearance(attributes);
1813 
1814         if (isPassword) {
1815             setTransformationMethod(PasswordTransformationMethod.getInstance());
1816         }
1817 
1818         // For addressing b/145128646
1819         // For the performance reason, we limit characters for single line text field.
1820         if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) {
1821             mSingleLineLengthFilter = new InputFilter.LengthFilter(
1822                 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
1823         }
1824 
1825         if (mSingleLineLengthFilter != null) {
1826             setFilters(new InputFilter[] { mSingleLineLengthFilter });
1827         } else if (maxlength >= 0) {
1828             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1829         } else {
1830             setFilters(NO_FILTERS);
1831         }
1832 
1833         setText(text, bufferType);
1834         if (mText == null) {
1835             mText = "";
1836         }
1837         if (mTransformed == null) {
1838             mTransformed = "";
1839         }
1840 
1841         if (textIsSetFromXml) {
1842             mTextSetFromXmlOrResourceId = true;
1843         }
1844 
1845         if (hint != null) setHint(hint);
1846 
1847         /*
1848          * Views are not normally clickable unless specified to be.
1849          * However, TextViews that have input or movement methods *are*
1850          * clickable by default. By setting clickable here, we implicitly set focusable as well
1851          * if not overridden by the developer.
1852          */
1853         a = context.obtainStyledAttributes(
1854                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1855         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1856         boolean clickable = canInputOrMove || isClickable();
1857         boolean longClickable = canInputOrMove || isLongClickable();
1858         int focusable = getFocusable();
1859         boolean isAutoHandwritingEnabled = true;
1860 
1861         n = a.getIndexCount();
1862         for (int i = 0; i < n; i++) {
1863             int attr = a.getIndex(i);
1864 
1865             switch (attr) {
1866                 case com.android.internal.R.styleable.View_focusable:
1867                     TypedValue val = new TypedValue();
1868                     if (a.getValue(attr, val)) {
1869                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1870                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1871                                 : val.data;
1872                     }
1873                     break;
1874 
1875                 case com.android.internal.R.styleable.View_clickable:
1876                     clickable = a.getBoolean(attr, clickable);
1877                     break;
1878 
1879                 case com.android.internal.R.styleable.View_longClickable:
1880                     longClickable = a.getBoolean(attr, longClickable);
1881                     break;
1882 
1883                 case com.android.internal.R.styleable.View_autoHandwritingEnabled:
1884                     isAutoHandwritingEnabled = a.getBoolean(attr, true);
1885                     break;
1886             }
1887         }
1888         a.recycle();
1889 
1890         // Some apps were relying on the undefined behavior of focusable winning over
1891         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1892         // when starting with EditText and setting only focusable=false). To keep those apps from
1893         // breaking, re-apply the focusable attribute here.
1894         if (focusable != getFocusable()) {
1895             setFocusable(focusable);
1896         }
1897         setClickable(clickable);
1898         setLongClickable(longClickable);
1899         setAutoHandwritingEnabled(isAutoHandwritingEnabled);
1900 
1901         if (mEditor != null) mEditor.prepareCursorControllers();
1902 
1903         // If not explicitly specified this view is important for accessibility.
1904         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1905             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1906         }
1907 
1908         if (supportsAutoSizeText()) {
1909             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1910                 // If uniform auto-size has been specified but preset values have not been set then
1911                 // replace the auto-size configuration values that have not been specified with the
1912                 // defaults.
1913                 if (!mHasPresetAutoSizeValues) {
1914                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1915 
1916                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1917                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1918                                 TypedValue.COMPLEX_UNIT_SP,
1919                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1920                                 displayMetrics);
1921                     }
1922 
1923                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1924                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1925                                 TypedValue.COMPLEX_UNIT_SP,
1926                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1927                                 displayMetrics);
1928                     }
1929 
1930                     if (autoSizeStepGranularityInPx
1931                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1932                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1933                     }
1934 
1935                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1936                             autoSizeMaxTextSizeInPx,
1937                             autoSizeStepGranularityInPx);
1938                 }
1939 
1940                 setupAutoSizeText();
1941             }
1942         } else {
1943             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1944         }
1945 
1946         if (firstBaselineToTopHeight >= 0) {
1947             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1948         }
1949         if (lastBaselineToBottomHeight >= 0) {
1950             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1951         }
1952         if (lineHeight >= 0) {
1953             if (lineHeightUnit == -1) {
1954                 setLineHeightPx(lineHeight);
1955             } else {
1956                 setLineHeight(lineHeightUnit, lineHeight);
1957             }
1958         }
1959     }
1960 
1961     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)1962     private void setTextInternal(@Nullable CharSequence text) {
1963         mText = text;
1964         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
1965         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
1966     }
1967 
1968     /**
1969      * Specify whether this widget should automatically scale the text to try to perfectly fit
1970      * within the layout bounds by using the default auto-size configuration.
1971      *
1972      * @param autoSizeTextType the type of auto-size. Must be one of
1973      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1974      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1975      *
1976      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1977      *
1978      * @attr ref android.R.styleable#TextView_autoSizeTextType
1979      *
1980      * @see #getAutoSizeTextType()
1981      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1982     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1983         if (supportsAutoSizeText()) {
1984             switch (autoSizeTextType) {
1985                 case AUTO_SIZE_TEXT_TYPE_NONE:
1986                     clearAutoSizeConfiguration();
1987                     break;
1988                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1989                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1990                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1991                             TypedValue.COMPLEX_UNIT_SP,
1992                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1993                             displayMetrics);
1994                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1995                             TypedValue.COMPLEX_UNIT_SP,
1996                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1997                             displayMetrics);
1998 
1999                     validateAndSetAutoSizeTextTypeUniformConfiguration(
2000                             autoSizeMinTextSizeInPx,
2001                             autoSizeMaxTextSizeInPx,
2002                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
2003                     if (setupAutoSizeText()) {
2004                         autoSizeText();
2005                         invalidate();
2006                     }
2007                     break;
2008                 default:
2009                     throw new IllegalArgumentException(
2010                             "Unknown auto-size text type: " + autoSizeTextType);
2011             }
2012         }
2013     }
2014 
2015     /**
2016      * Specify whether this widget should automatically scale the text to try to perfectly fit
2017      * within the layout bounds. If all the configuration params are valid the type of auto-size is
2018      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
2019      *
2020      * @param autoSizeMinTextSize the minimum text size available for auto-size
2021      * @param autoSizeMaxTextSize the maximum text size available for auto-size
2022      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
2023      *                                the minimum and maximum text size in order to build the set of
2024      *                                text sizes the system uses to choose from when auto-sizing
2025      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
2026      *             possible dimension units
2027      *
2028      * @throws IllegalArgumentException if any of the configuration params are invalid.
2029      *
2030      * @attr ref android.R.styleable#TextView_autoSizeTextType
2031      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2032      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2033      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
2034      *
2035      * @see #setAutoSizeTextTypeWithDefaults(int)
2036      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2037      * @see #getAutoSizeMinTextSize()
2038      * @see #getAutoSizeMaxTextSize()
2039      * @see #getAutoSizeStepGranularity()
2040      * @see #getAutoSizeTextAvailableSizes()
2041      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)2042     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
2043             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
2044         if (supportsAutoSizeText()) {
2045             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2046             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
2047                     unit, autoSizeMinTextSize, displayMetrics);
2048             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
2049                     unit, autoSizeMaxTextSize, displayMetrics);
2050             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
2051                     unit, autoSizeStepGranularity, displayMetrics);
2052 
2053             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
2054                     autoSizeMaxTextSizeInPx,
2055                     autoSizeStepGranularityInPx);
2056 
2057             if (setupAutoSizeText()) {
2058                 autoSizeText();
2059                 invalidate();
2060             }
2061         }
2062     }
2063 
2064     /**
2065      * Specify whether this widget should automatically scale the text to try to perfectly fit
2066      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
2067      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
2068      *
2069      * @param presetSizes an {@code int} array of sizes in pixels
2070      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
2071      *             the possible dimension units
2072      *
2073      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
2074      *
2075      * @attr ref android.R.styleable#TextView_autoSizeTextType
2076      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
2077      *
2078      * @see #setAutoSizeTextTypeWithDefaults(int)
2079      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2080      * @see #getAutoSizeMinTextSize()
2081      * @see #getAutoSizeMaxTextSize()
2082      * @see #getAutoSizeTextAvailableSizes()
2083      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)2084     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
2085         if (supportsAutoSizeText()) {
2086             final int presetSizesLength = presetSizes.length;
2087             if (presetSizesLength > 0) {
2088                 int[] presetSizesInPx = new int[presetSizesLength];
2089 
2090                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
2091                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
2092                 } else {
2093                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2094                     // Convert all to sizes to pixels.
2095                     for (int i = 0; i < presetSizesLength; i++) {
2096                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
2097                             presetSizes[i], displayMetrics));
2098                     }
2099                 }
2100 
2101                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
2102                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
2103                     throw new IllegalArgumentException("None of the preset sizes is valid: "
2104                             + Arrays.toString(presetSizes));
2105                 }
2106             } else {
2107                 mHasPresetAutoSizeValues = false;
2108             }
2109 
2110             if (setupAutoSizeText()) {
2111                 autoSizeText();
2112                 invalidate();
2113             }
2114         }
2115     }
2116 
2117     /**
2118      * Returns the type of auto-size set for this widget.
2119      *
2120      * @return an {@code int} corresponding to one of the auto-size types:
2121      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
2122      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
2123      *
2124      * @attr ref android.R.styleable#TextView_autoSizeTextType
2125      *
2126      * @see #setAutoSizeTextTypeWithDefaults(int)
2127      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2128      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2129      */
2130     @InspectableProperty(enumMapping = {
2131             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
2132             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
2133     })
2134     @AutoSizeTextType
getAutoSizeTextType()2135     public int getAutoSizeTextType() {
2136         return mAutoSizeTextType;
2137     }
2138 
2139     /**
2140      * @return the current auto-size step granularity in pixels.
2141      *
2142      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
2143      *
2144      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2145      */
2146     @InspectableProperty
getAutoSizeStepGranularity()2147     public int getAutoSizeStepGranularity() {
2148         return Math.round(mAutoSizeStepGranularityInPx);
2149     }
2150 
2151     /**
2152      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
2153      *         if auto-size has not been configured this function returns {@code -1}.
2154      *
2155      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2156      *
2157      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2158      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2159      */
2160     @InspectableProperty
getAutoSizeMinTextSize()2161     public int getAutoSizeMinTextSize() {
2162         return Math.round(mAutoSizeMinTextSizeInPx);
2163     }
2164 
2165     /**
2166      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
2167      *         if auto-size has not been configured this function returns {@code -1}.
2168      *
2169      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2170      *
2171      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2172      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2173      */
2174     @InspectableProperty
getAutoSizeMaxTextSize()2175     public int getAutoSizeMaxTextSize() {
2176         return Math.round(mAutoSizeMaxTextSizeInPx);
2177     }
2178 
2179     /**
2180      * @return the current auto-size {@code int} sizes array (in pixels).
2181      *
2182      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2183      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2184      */
getAutoSizeTextAvailableSizes()2185     public int[] getAutoSizeTextAvailableSizes() {
2186         return mAutoSizeTextSizesInPx;
2187     }
2188 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)2189     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
2190         final int textSizesLength = textSizes.length();
2191         final int[] parsedSizes = new int[textSizesLength];
2192 
2193         if (textSizesLength > 0) {
2194             for (int i = 0; i < textSizesLength; i++) {
2195                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
2196             }
2197             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
2198             setupAutoSizeUniformPresetSizesConfiguration();
2199         }
2200     }
2201 
setupAutoSizeUniformPresetSizesConfiguration()2202     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
2203         final int sizesLength = mAutoSizeTextSizesInPx.length;
2204         mHasPresetAutoSizeValues = sizesLength > 0;
2205         if (mHasPresetAutoSizeValues) {
2206             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2207             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
2208             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
2209             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2210         }
2211         return mHasPresetAutoSizeValues;
2212     }
2213 
2214     /**
2215      * If all params are valid then save the auto-size configuration.
2216      *
2217      * @throws IllegalArgumentException if any of the params are invalid
2218      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2219     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
2220             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
2221         // First validate.
2222         if (autoSizeMinTextSizeInPx <= 0) {
2223             throw new IllegalArgumentException("Minimum auto-size text size ("
2224                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
2225         }
2226 
2227         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2228             throw new IllegalArgumentException("Maximum auto-size text size ("
2229                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2230                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2231         }
2232 
2233         if (autoSizeStepGranularityInPx <= 0) {
2234             throw new IllegalArgumentException("The auto-size step granularity ("
2235                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2236         }
2237 
2238         // All good, persist the configuration.
2239         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2240         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2241         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2242         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2243         mHasPresetAutoSizeValues = false;
2244     }
2245 
clearAutoSizeConfiguration()2246     private void clearAutoSizeConfiguration() {
2247         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2248         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2249         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2250         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2251         mAutoSizeTextSizesInPx = EmptyArray.INT;
2252         mNeedsAutoSizeText = false;
2253     }
2254 
2255     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)2256     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2257         final int presetValuesLength = presetValues.length;
2258         if (presetValuesLength == 0) {
2259             return presetValues;
2260         }
2261         Arrays.sort(presetValues);
2262 
2263         final IntArray uniqueValidSizes = new IntArray();
2264         for (int i = 0; i < presetValuesLength; i++) {
2265             final int currentPresetValue = presetValues[i];
2266 
2267             if (currentPresetValue > 0
2268                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2269                 uniqueValidSizes.add(currentPresetValue);
2270             }
2271         }
2272 
2273         return presetValuesLength == uniqueValidSizes.size()
2274             ? presetValues
2275             : uniqueValidSizes.toArray();
2276     }
2277 
setupAutoSizeText()2278     private boolean setupAutoSizeText() {
2279         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2280             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2281             // not have a predefined set of sizes or if the current sizes array is empty.
2282             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2283                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2284                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2285                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2286                 for (int i = 0; i < autoSizeValuesLength; i++) {
2287                     autoSizeTextSizesInPx[i] = Math.round(
2288                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2289                 }
2290                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2291             }
2292 
2293             mNeedsAutoSizeText = true;
2294         } else {
2295             mNeedsAutoSizeText = false;
2296         }
2297 
2298         return mNeedsAutoSizeText;
2299     }
2300 
parseDimensionArray(TypedArray dimens)2301     private int[] parseDimensionArray(TypedArray dimens) {
2302         if (dimens == null) {
2303             return null;
2304         }
2305         int[] result = new int[dimens.length()];
2306         for (int i = 0; i < result.length; i++) {
2307             result[i] = dimens.getDimensionPixelSize(i, 0);
2308         }
2309         return result;
2310     }
2311 
2312     /**
2313      * @hide
2314      */
2315     @TestApi
2316     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)2317     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
2318         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2319             if (resultCode == Activity.RESULT_OK && data != null) {
2320                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2321                 if (result != null) {
2322                     if (isTextEditable()) {
2323                         ClipData clip = ClipData.newPlainText("", result);
2324                         ContentInfo payload =
2325                                 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build();
2326                         performReceiveContent(payload);
2327                         if (mEditor != null) {
2328                             mEditor.refreshTextActionMode();
2329                         }
2330                     } else {
2331                         if (result.length() > 0) {
2332                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2333                                 .show();
2334                         }
2335                     }
2336                 }
2337             } else if (mSpannable != null) {
2338                 // Reset the selection.
2339                 Selection.setSelection(mSpannable, getSelectionEnd());
2340             }
2341         }
2342     }
2343 
2344     /**
2345      * Sets the Typeface taking into account the given attributes.
2346      *
2347      * @param typeface a typeface
2348      * @param familyName family name string, e.g. "serif"
2349      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2350      * @param style a typeface style
2351      * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED}
2352      *               if not specified.
2353      */
setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)2354     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2355             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2356             @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
2357                     int weight) {
2358         if (typeface == null && familyName != null) {
2359             // Lookup normal Typeface from system font map.
2360             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2361             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2362         } else if (typeface != null) {
2363             resolveStyleAndSetTypeface(typeface, style, weight);
2364         } else {  // both typeface and familyName is null.
2365             switch (typefaceIndex) {
2366                 case SANS:
2367                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2368                     break;
2369                 case SERIF:
2370                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2371                     break;
2372                 case MONOSPACE:
2373                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2374                     break;
2375                 case DEFAULT_TYPEFACE:
2376                 default:
2377                     resolveStyleAndSetTypeface(null, style, weight);
2378                     break;
2379             }
2380         }
2381     }
2382 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)2383     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2384             @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
2385                     int weight) {
2386         if (weight >= 0) {
2387             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2388             final boolean italic = (style & Typeface.ITALIC) != 0;
2389             setTypeface(Typeface.create(typeface, weight, italic));
2390         } else {
2391             setTypeface(typeface, style);
2392         }
2393     }
2394 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2395     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2396         boolean hasRelativeDrawables = (start != null) || (end != null);
2397         if (hasRelativeDrawables) {
2398             Drawables dr = mDrawables;
2399             if (dr == null) {
2400                 mDrawables = dr = new Drawables(getContext());
2401             }
2402             mDrawables.mOverride = true;
2403             final Rect compoundRect = dr.mCompoundRect;
2404             int[] state = getDrawableState();
2405             if (start != null) {
2406                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2407                 start.setState(state);
2408                 start.copyBounds(compoundRect);
2409                 start.setCallback(this);
2410 
2411                 dr.mDrawableStart = start;
2412                 dr.mDrawableSizeStart = compoundRect.width();
2413                 dr.mDrawableHeightStart = compoundRect.height();
2414             } else {
2415                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2416             }
2417             if (end != null) {
2418                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2419                 end.setState(state);
2420                 end.copyBounds(compoundRect);
2421                 end.setCallback(this);
2422 
2423                 dr.mDrawableEnd = end;
2424                 dr.mDrawableSizeEnd = compoundRect.width();
2425                 dr.mDrawableHeightEnd = compoundRect.height();
2426             } else {
2427                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2428             }
2429             resetResolvedDrawables();
2430             resolveDrawables();
2431             applyCompoundDrawableTint();
2432         }
2433     }
2434 
2435     @android.view.RemotableViewMethod
2436     @Override
setEnabled(boolean enabled)2437     public void setEnabled(boolean enabled) {
2438         if (enabled == isEnabled()) {
2439             return;
2440         }
2441 
2442         if (!enabled) {
2443             // Hide the soft input if the currently active TextView is disabled
2444             InputMethodManager imm = getInputMethodManager();
2445             if (imm != null && imm.isActive(this)) {
2446                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2447             }
2448         }
2449 
2450         super.setEnabled(enabled);
2451 
2452         if (enabled) {
2453             // Make sure IME is updated with current editor info.
2454             InputMethodManager imm = getInputMethodManager();
2455             if (imm != null) imm.restartInput(this);
2456         }
2457 
2458         // Will change text color
2459         if (mEditor != null) {
2460             mEditor.invalidateTextDisplayList();
2461             mEditor.prepareCursorControllers();
2462 
2463             // start or stop the cursor blinking as appropriate
2464             mEditor.makeBlink();
2465         }
2466     }
2467 
2468     /**
2469      * Sets the typeface and style in which the text should be displayed,
2470      * and turns on the fake bold and italic bits in the Paint if the
2471      * Typeface that you provided does not have all the bits in the
2472      * style that you specified.
2473      *
2474      * @attr ref android.R.styleable#TextView_typeface
2475      * @attr ref android.R.styleable#TextView_textStyle
2476      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2477     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2478         if (style > 0) {
2479             if (tf == null) {
2480                 tf = Typeface.defaultFromStyle(style);
2481             } else {
2482                 tf = Typeface.create(tf, style);
2483             }
2484 
2485             setTypeface(tf);
2486             // now compute what (if any) algorithmic styling is needed
2487             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2488             int need = style & ~typefaceStyle;
2489             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2490             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2491         } else {
2492             mTextPaint.setFakeBoldText(false);
2493             mTextPaint.setTextSkewX(0);
2494             setTypeface(tf);
2495         }
2496     }
2497 
2498     /**
2499      * Subclasses override this to specify that they have a KeyListener
2500      * by default even if not specifically called for in the XML options.
2501      */
getDefaultEditable()2502     protected boolean getDefaultEditable() {
2503         return false;
2504     }
2505 
2506     /**
2507      * Subclasses override this to specify a default movement method.
2508      */
getDefaultMovementMethod()2509     protected MovementMethod getDefaultMovementMethod() {
2510         return null;
2511     }
2512 
2513     /**
2514      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2515      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2516      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2517      * the return value from this method to Spannable or Editable, respectively.
2518      *
2519      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2520      * should make your own copy first.</p>
2521      *
2522      * @return The text displayed by the text view.
2523      * @attr ref android.R.styleable#TextView_text
2524      */
2525     @ViewDebug.CapturedViewProperty
2526     @InspectableProperty
getText()2527     public CharSequence getText() {
2528         if (mUseTextPaddingForUiTranslation) {
2529             ViewTranslationCallback callback = getViewTranslationCallback();
2530             if (callback != null && callback instanceof TextViewTranslationCallback) {
2531                 TextViewTranslationCallback defaultCallback =
2532                         (TextViewTranslationCallback) callback;
2533                 if (defaultCallback.isTextPaddingEnabled()
2534                         && defaultCallback.isShowingTranslation()) {
2535                     return defaultCallback.getPaddedText(mText, mTransformed);
2536                 }
2537             }
2538         }
2539         return mText;
2540     }
2541 
2542     /**
2543      * Returns the length, in characters, of the text managed by this TextView
2544      * @return The length of the text managed by the TextView in characters.
2545      */
length()2546     public int length() {
2547         return mText.length();
2548     }
2549 
2550     /**
2551      * Return the text that TextView is displaying as an Editable object. If the text is not
2552      * editable, null is returned.
2553      *
2554      * @see #getText
2555      */
getEditableText()2556     public Editable getEditableText() {
2557         return (mText instanceof Editable) ? (Editable) mText : null;
2558     }
2559 
2560     /**
2561      * @hide
2562      */
2563     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getTransformed()2564     public CharSequence getTransformed() {
2565         return mTransformed;
2566     }
2567 
2568     /**
2569      * Gets the vertical distance between lines of text, in pixels.
2570      * Note that markup within the text can cause individual lines
2571      * to be taller or shorter than this height, and the layout may
2572      * contain additional first-or last-line padding.
2573      * @return The height of one standard line in pixels.
2574      */
2575     @InspectableProperty
getLineHeight()2576     public int getLineHeight() {
2577         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2578     }
2579 
2580     /**
2581      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2582      * This value can be null if the text or width has recently changed.
2583      * @return The Layout that is currently being used to display the text.
2584      */
getLayout()2585     public final Layout getLayout() {
2586         return mLayout;
2587     }
2588 
2589     /**
2590      * @return the {@link android.text.Layout} that is currently being used to
2591      * display the hint text. This can be null.
2592      */
2593     @UnsupportedAppUsage
getHintLayout()2594     final Layout getHintLayout() {
2595         return mHintLayout;
2596     }
2597 
2598     /**
2599      * Retrieve the {@link android.content.UndoManager} that is currently associated
2600      * with this TextView.  By default there is no associated UndoManager, so null
2601      * is returned.  One can be associated with the TextView through
2602      * {@link #setUndoManager(android.content.UndoManager, String)}
2603      *
2604      * @hide
2605      */
getUndoManager()2606     public final UndoManager getUndoManager() {
2607         // TODO: Consider supporting a global undo manager.
2608         throw new UnsupportedOperationException("not implemented");
2609     }
2610 
2611 
2612     /**
2613      * @hide
2614      */
2615     @VisibleForTesting
getEditorForTesting()2616     public final Editor getEditorForTesting() {
2617         return mEditor;
2618     }
2619 
2620     /**
2621      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2622      * done, all edit operations on the TextView will result in appropriate
2623      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2624      * stack.
2625      *
2626      * @param undoManager The {@link android.content.UndoManager} to associate with
2627      * this TextView, or null to clear any existing association.
2628      * @param tag String tag identifying this particular TextView owner in the
2629      * UndoManager.  This is used to keep the correct association with the
2630      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2631      *
2632      * @hide
2633      */
setUndoManager(UndoManager undoManager, String tag)2634     public final void setUndoManager(UndoManager undoManager, String tag) {
2635         // TODO: Consider supporting a global undo manager. An implementation will need to:
2636         // * createEditorIfNeeded()
2637         // * Promote to BufferType.EDITABLE if needed.
2638         // * Update the UndoManager and UndoOwner.
2639         // Likewise it will need to be able to restore the default UndoManager.
2640         throw new UnsupportedOperationException("not implemented");
2641     }
2642 
2643     /**
2644      * Gets the current {@link KeyListener} for the TextView.
2645      * This will frequently be null for non-EditText TextViews.
2646      * @return the current key listener for this TextView.
2647      *
2648      * @attr ref android.R.styleable#TextView_numeric
2649      * @attr ref android.R.styleable#TextView_digits
2650      * @attr ref android.R.styleable#TextView_phoneNumber
2651      * @attr ref android.R.styleable#TextView_inputMethod
2652      * @attr ref android.R.styleable#TextView_capitalize
2653      * @attr ref android.R.styleable#TextView_autoText
2654      */
getKeyListener()2655     public final KeyListener getKeyListener() {
2656         return mEditor == null ? null : mEditor.mKeyListener;
2657     }
2658 
2659     /**
2660      * Sets the key listener to be used with this TextView.  This can be null
2661      * to disallow user input.  Note that this method has significant and
2662      * subtle interactions with soft keyboards and other input method:
2663      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2664      * for important details.  Calling this method will replace the current
2665      * content type of the text view with the content type returned by the
2666      * key listener.
2667      * <p>
2668      * Be warned that if you want a TextView with a key listener or movement
2669      * method not to be focusable, or if you want a TextView without a
2670      * key listener or movement method to be focusable, you must call
2671      * {@link #setFocusable} again after calling this to get the focusability
2672      * back the way you want it.
2673      *
2674      * @attr ref android.R.styleable#TextView_numeric
2675      * @attr ref android.R.styleable#TextView_digits
2676      * @attr ref android.R.styleable#TextView_phoneNumber
2677      * @attr ref android.R.styleable#TextView_inputMethod
2678      * @attr ref android.R.styleable#TextView_capitalize
2679      * @attr ref android.R.styleable#TextView_autoText
2680      */
setKeyListener(KeyListener input)2681     public void setKeyListener(KeyListener input) {
2682         mListenerChanged = true;
2683         setKeyListenerOnly(input);
2684         fixFocusableAndClickableSettings();
2685 
2686         if (input != null) {
2687             createEditorIfNeeded();
2688             setInputTypeFromEditor();
2689         } else {
2690             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2691         }
2692 
2693         InputMethodManager imm = getInputMethodManager();
2694         if (imm != null) imm.restartInput(this);
2695     }
2696 
setInputTypeFromEditor()2697     private void setInputTypeFromEditor() {
2698         try {
2699             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2700         } catch (IncompatibleClassChangeError e) {
2701             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2702         }
2703         // Change inputType, without affecting transformation.
2704         // No need to applySingleLine since mSingleLine is unchanged.
2705         setInputTypeSingleLine(mSingleLine);
2706     }
2707 
setKeyListenerOnly(KeyListener input)2708     private void setKeyListenerOnly(KeyListener input) {
2709         if (mEditor == null && input == null) return; // null is the default value
2710 
2711         createEditorIfNeeded();
2712         if (mEditor.mKeyListener != input) {
2713             mEditor.mKeyListener = input;
2714             if (input != null && !(mText instanceof Editable)) {
2715                 setText(mText);
2716             }
2717 
2718             setFilters((Editable) mText, mFilters);
2719         }
2720     }
2721 
2722     /**
2723      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2724      * which provides positioning, scrolling, and text selection functionality.
2725      * This will frequently be null for non-EditText TextViews.
2726      * @return the movement method being used for this TextView.
2727      * @see android.text.method.MovementMethod
2728      */
getMovementMethod()2729     public final MovementMethod getMovementMethod() {
2730         return mMovement;
2731     }
2732 
2733     /**
2734      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2735      * for this TextView. This can be null to disallow using the arrow keys to move the
2736      * cursor or scroll the view.
2737      * <p>
2738      * Be warned that if you want a TextView with a key listener or movement
2739      * method not to be focusable, or if you want a TextView without a
2740      * key listener or movement method to be focusable, you must call
2741      * {@link #setFocusable} again after calling this to get the focusability
2742      * back the way you want it.
2743      */
setMovementMethod(MovementMethod movement)2744     public final void setMovementMethod(MovementMethod movement) {
2745         if (mMovement != movement) {
2746             mMovement = movement;
2747 
2748             if (movement != null && mSpannable == null) {
2749                 setText(mText);
2750             }
2751 
2752             fixFocusableAndClickableSettings();
2753 
2754             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2755             // mMovement
2756             if (mEditor != null) mEditor.prepareCursorControllers();
2757         }
2758     }
2759 
fixFocusableAndClickableSettings()2760     private void fixFocusableAndClickableSettings() {
2761         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2762             setFocusable(FOCUSABLE);
2763             setClickable(true);
2764             setLongClickable(true);
2765         } else {
2766             setFocusable(FOCUSABLE_AUTO);
2767             setClickable(false);
2768             setLongClickable(false);
2769         }
2770     }
2771 
2772     /**
2773      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2774      * This is frequently null, except for single-line and password fields.
2775      * @return the current transformation method for this TextView.
2776      *
2777      * @attr ref android.R.styleable#TextView_password
2778      * @attr ref android.R.styleable#TextView_singleLine
2779      */
getTransformationMethod()2780     public final TransformationMethod getTransformationMethod() {
2781         return mTransformation;
2782     }
2783 
2784     /**
2785      * Sets the transformation that is applied to the text that this
2786      * TextView is displaying.
2787      *
2788      * @attr ref android.R.styleable#TextView_password
2789      * @attr ref android.R.styleable#TextView_singleLine
2790      */
setTransformationMethod(TransformationMethod method)2791     public final void setTransformationMethod(TransformationMethod method) {
2792         if (mEditor != null) {
2793             mEditor.setTransformationMethod(method);
2794         } else {
2795             setTransformationMethodInternal(method, /* updateText */ true);
2796         }
2797     }
2798 
2799     /**
2800      * Set the transformation that is applied to the text that this TextView is displaying,
2801      * optionally call the setText.
2802      * @param method the new transformation method to be set.
2803      * @param updateText whether the call {@link #setText} which will update the TextView to display
2804      *                   the new content. This method is helpful when updating
2805      *                   {@link TransformationMethod} inside {@link #setText}. It should only be
2806      *                   false if text will be updated immediately after this call, otherwise the
2807      *                   TextView will enter an inconsistent state.
2808      */
setTransformationMethodInternal(@ullable TransformationMethod method, boolean updateText)2809     void setTransformationMethodInternal(@Nullable TransformationMethod method,
2810             boolean updateText) {
2811         if (method == mTransformation) {
2812             // Avoid the setText() below if the transformation is
2813             // the same.
2814             return;
2815         }
2816         if (mTransformation != null) {
2817             if (mSpannable != null) {
2818                 mSpannable.removeSpan(mTransformation);
2819             }
2820         }
2821 
2822         mTransformation = method;
2823 
2824         if (method instanceof TransformationMethod2) {
2825             TransformationMethod2 method2 = (TransformationMethod2) method;
2826             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2827             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2828         } else {
2829             mAllowTransformationLengthChange = false;
2830         }
2831 
2832         if (updateText) {
2833             setText(mText);
2834         }
2835 
2836         if (hasPasswordTransformationMethod()) {
2837             notifyViewAccessibilityStateChangedIfNeeded(
2838                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2839         }
2840 
2841         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2842         // getTextDirectionHeuristic, needs reset
2843         mTextDir = getTextDirectionHeuristic();
2844     }
2845 
2846     /**
2847      * Returns the top padding of the view, plus space for the top
2848      * Drawable if any.
2849      */
getCompoundPaddingTop()2850     public int getCompoundPaddingTop() {
2851         final Drawables dr = mDrawables;
2852         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2853             return mPaddingTop;
2854         } else {
2855             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2856         }
2857     }
2858 
2859     /**
2860      * Returns the bottom padding of the view, plus space for the bottom
2861      * Drawable if any.
2862      */
getCompoundPaddingBottom()2863     public int getCompoundPaddingBottom() {
2864         final Drawables dr = mDrawables;
2865         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2866             return mPaddingBottom;
2867         } else {
2868             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2869         }
2870     }
2871 
2872     /**
2873      * Returns the left padding of the view, plus space for the left
2874      * Drawable if any.
2875      */
getCompoundPaddingLeft()2876     public int getCompoundPaddingLeft() {
2877         final Drawables dr = mDrawables;
2878         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2879             return mPaddingLeft;
2880         } else {
2881             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2882         }
2883     }
2884 
2885     /**
2886      * Returns the right padding of the view, plus space for the right
2887      * Drawable if any.
2888      */
getCompoundPaddingRight()2889     public int getCompoundPaddingRight() {
2890         final Drawables dr = mDrawables;
2891         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2892             return mPaddingRight;
2893         } else {
2894             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2895         }
2896     }
2897 
2898     /**
2899      * Returns the start padding of the view, plus space for the start
2900      * Drawable if any.
2901      */
getCompoundPaddingStart()2902     public int getCompoundPaddingStart() {
2903         resolveDrawables();
2904         switch(getLayoutDirection()) {
2905             default:
2906             case LAYOUT_DIRECTION_LTR:
2907                 return getCompoundPaddingLeft();
2908             case LAYOUT_DIRECTION_RTL:
2909                 return getCompoundPaddingRight();
2910         }
2911     }
2912 
2913     /**
2914      * Returns the end padding of the view, plus space for the end
2915      * Drawable if any.
2916      */
getCompoundPaddingEnd()2917     public int getCompoundPaddingEnd() {
2918         resolveDrawables();
2919         switch(getLayoutDirection()) {
2920             default:
2921             case LAYOUT_DIRECTION_LTR:
2922                 return getCompoundPaddingRight();
2923             case LAYOUT_DIRECTION_RTL:
2924                 return getCompoundPaddingLeft();
2925         }
2926     }
2927 
2928     /**
2929      * Returns the extended top padding of the view, including both the
2930      * top Drawable if any and any extra space to keep more than maxLines
2931      * of text from showing.  It is only valid to call this after measuring.
2932      */
getExtendedPaddingTop()2933     public int getExtendedPaddingTop() {
2934         if (mMaxMode != LINES) {
2935             return getCompoundPaddingTop();
2936         }
2937 
2938         if (mLayout == null) {
2939             assumeLayout();
2940         }
2941 
2942         if (mLayout.getLineCount() <= mMaximum) {
2943             return getCompoundPaddingTop();
2944         }
2945 
2946         int top = getCompoundPaddingTop();
2947         int bottom = getCompoundPaddingBottom();
2948         int viewht = getHeight() - top - bottom;
2949         int layoutht = mLayout.getLineTop(mMaximum);
2950 
2951         if (layoutht >= viewht) {
2952             return top;
2953         }
2954 
2955         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2956         if (gravity == Gravity.TOP) {
2957             return top;
2958         } else if (gravity == Gravity.BOTTOM) {
2959             return top + viewht - layoutht;
2960         } else { // (gravity == Gravity.CENTER_VERTICAL)
2961             return top + (viewht - layoutht) / 2;
2962         }
2963     }
2964 
2965     /**
2966      * Returns the extended bottom padding of the view, including both the
2967      * bottom Drawable if any and any extra space to keep more than maxLines
2968      * of text from showing.  It is only valid to call this after measuring.
2969      */
getExtendedPaddingBottom()2970     public int getExtendedPaddingBottom() {
2971         if (mMaxMode != LINES) {
2972             return getCompoundPaddingBottom();
2973         }
2974 
2975         if (mLayout == null) {
2976             assumeLayout();
2977         }
2978 
2979         if (mLayout.getLineCount() <= mMaximum) {
2980             return getCompoundPaddingBottom();
2981         }
2982 
2983         int top = getCompoundPaddingTop();
2984         int bottom = getCompoundPaddingBottom();
2985         int viewht = getHeight() - top - bottom;
2986         int layoutht = mLayout.getLineTop(mMaximum);
2987 
2988         if (layoutht >= viewht) {
2989             return bottom;
2990         }
2991 
2992         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2993         if (gravity == Gravity.TOP) {
2994             return bottom + viewht - layoutht;
2995         } else if (gravity == Gravity.BOTTOM) {
2996             return bottom;
2997         } else { // (gravity == Gravity.CENTER_VERTICAL)
2998             return bottom + (viewht - layoutht) / 2;
2999         }
3000     }
3001 
3002     /**
3003      * Returns the total left padding of the view, including the left
3004      * Drawable if any.
3005      */
getTotalPaddingLeft()3006     public int getTotalPaddingLeft() {
3007         return getCompoundPaddingLeft();
3008     }
3009 
3010     /**
3011      * Returns the total right padding of the view, including the right
3012      * Drawable if any.
3013      */
getTotalPaddingRight()3014     public int getTotalPaddingRight() {
3015         return getCompoundPaddingRight();
3016     }
3017 
3018     /**
3019      * Returns the total start padding of the view, including the start
3020      * Drawable if any.
3021      */
getTotalPaddingStart()3022     public int getTotalPaddingStart() {
3023         return getCompoundPaddingStart();
3024     }
3025 
3026     /**
3027      * Returns the total end padding of the view, including the end
3028      * Drawable if any.
3029      */
getTotalPaddingEnd()3030     public int getTotalPaddingEnd() {
3031         return getCompoundPaddingEnd();
3032     }
3033 
3034     /**
3035      * Returns the total top padding of the view, including the top
3036      * Drawable if any, the extra space to keep more than maxLines
3037      * from showing, and the vertical offset for gravity, if any.
3038      */
getTotalPaddingTop()3039     public int getTotalPaddingTop() {
3040         return getExtendedPaddingTop() + getVerticalOffset(true);
3041     }
3042 
3043     /**
3044      * Returns the total bottom padding of the view, including the bottom
3045      * Drawable if any, the extra space to keep more than maxLines
3046      * from showing, and the vertical offset for gravity, if any.
3047      */
getTotalPaddingBottom()3048     public int getTotalPaddingBottom() {
3049         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
3050     }
3051 
3052     /**
3053      * Sets the Drawables (if any) to appear to the left of, above, to the
3054      * right of, and below the text. Use {@code null} if you do not want a
3055      * Drawable there. The Drawables must already have had
3056      * {@link Drawable#setBounds} called.
3057      * <p>
3058      * Calling this method will overwrite any Drawables previously set using
3059      * {@link #setCompoundDrawablesRelative} or related methods.
3060      *
3061      * @attr ref android.R.styleable#TextView_drawableLeft
3062      * @attr ref android.R.styleable#TextView_drawableTop
3063      * @attr ref android.R.styleable#TextView_drawableRight
3064      * @attr ref android.R.styleable#TextView_drawableBottom
3065      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3066     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
3067             @Nullable Drawable right, @Nullable Drawable bottom) {
3068         Drawables dr = mDrawables;
3069 
3070         // We're switching to absolute, discard relative.
3071         if (dr != null) {
3072             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3073             dr.mDrawableStart = null;
3074             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
3075             dr.mDrawableEnd = null;
3076             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3077             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3078         }
3079 
3080         final boolean drawables = left != null || top != null || right != null || bottom != null;
3081         if (!drawables) {
3082             // Clearing drawables...  can we free the data structure?
3083             if (dr != null) {
3084                 if (!dr.hasMetadata()) {
3085                     mDrawables = null;
3086                 } else {
3087                     // We need to retain the last set padding, so just clear
3088                     // out all of the fields in the existing structure.
3089                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
3090                         if (dr.mShowing[i] != null) {
3091                             dr.mShowing[i].setCallback(null);
3092                         }
3093                         dr.mShowing[i] = null;
3094                     }
3095                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3096                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3097                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3098                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3099                 }
3100             }
3101         } else {
3102             if (dr == null) {
3103                 mDrawables = dr = new Drawables(getContext());
3104             }
3105 
3106             mDrawables.mOverride = false;
3107 
3108             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
3109                 dr.mShowing[Drawables.LEFT].setCallback(null);
3110             }
3111             dr.mShowing[Drawables.LEFT] = left;
3112 
3113             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3114                 dr.mShowing[Drawables.TOP].setCallback(null);
3115             }
3116             dr.mShowing[Drawables.TOP] = top;
3117 
3118             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
3119                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3120             }
3121             dr.mShowing[Drawables.RIGHT] = right;
3122 
3123             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3124                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3125             }
3126             dr.mShowing[Drawables.BOTTOM] = bottom;
3127 
3128             final Rect compoundRect = dr.mCompoundRect;
3129             int[] state;
3130 
3131             state = getDrawableState();
3132 
3133             if (left != null) {
3134                 left.setState(state);
3135                 left.copyBounds(compoundRect);
3136                 left.setCallback(this);
3137                 dr.mDrawableSizeLeft = compoundRect.width();
3138                 dr.mDrawableHeightLeft = compoundRect.height();
3139             } else {
3140                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3141             }
3142 
3143             if (right != null) {
3144                 right.setState(state);
3145                 right.copyBounds(compoundRect);
3146                 right.setCallback(this);
3147                 dr.mDrawableSizeRight = compoundRect.width();
3148                 dr.mDrawableHeightRight = compoundRect.height();
3149             } else {
3150                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3151             }
3152 
3153             if (top != null) {
3154                 top.setState(state);
3155                 top.copyBounds(compoundRect);
3156                 top.setCallback(this);
3157                 dr.mDrawableSizeTop = compoundRect.height();
3158                 dr.mDrawableWidthTop = compoundRect.width();
3159             } else {
3160                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3161             }
3162 
3163             if (bottom != null) {
3164                 bottom.setState(state);
3165                 bottom.copyBounds(compoundRect);
3166                 bottom.setCallback(this);
3167                 dr.mDrawableSizeBottom = compoundRect.height();
3168                 dr.mDrawableWidthBottom = compoundRect.width();
3169             } else {
3170                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3171             }
3172         }
3173 
3174         // Save initial left/right drawables
3175         if (dr != null) {
3176             dr.mDrawableLeftInitial = left;
3177             dr.mDrawableRightInitial = right;
3178         }
3179 
3180         resetResolvedDrawables();
3181         resolveDrawables();
3182         applyCompoundDrawableTint();
3183         invalidate();
3184         requestLayout();
3185     }
3186 
3187     /**
3188      * Sets the Drawables (if any) to appear to the left of, above, to the
3189      * right of, and below the text. Use 0 if you do not want a Drawable there.
3190      * The Drawables' bounds will be set to their intrinsic bounds.
3191      * <p>
3192      * Calling this method will overwrite any Drawables previously set using
3193      * {@link #setCompoundDrawablesRelative} or related methods.
3194      *
3195      * @param left Resource identifier of the left Drawable.
3196      * @param top Resource identifier of the top Drawable.
3197      * @param right Resource identifier of the right Drawable.
3198      * @param bottom Resource identifier of the bottom Drawable.
3199      *
3200      * @attr ref android.R.styleable#TextView_drawableLeft
3201      * @attr ref android.R.styleable#TextView_drawableTop
3202      * @attr ref android.R.styleable#TextView_drawableRight
3203      * @attr ref android.R.styleable#TextView_drawableBottom
3204      */
3205     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)3206     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
3207             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
3208         final Context context = getContext();
3209         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
3210                 top != 0 ? context.getDrawable(top) : null,
3211                 right != 0 ? context.getDrawable(right) : null,
3212                 bottom != 0 ? context.getDrawable(bottom) : null);
3213     }
3214 
3215     /**
3216      * Sets the Drawables (if any) to appear to the left of, above, to the
3217      * right of, and below the text. Use {@code null} if you do not want a
3218      * Drawable there. The Drawables' bounds will be set to their intrinsic
3219      * bounds.
3220      * <p>
3221      * Calling this method will overwrite any Drawables previously set using
3222      * {@link #setCompoundDrawablesRelative} or related methods.
3223      *
3224      * @attr ref android.R.styleable#TextView_drawableLeft
3225      * @attr ref android.R.styleable#TextView_drawableTop
3226      * @attr ref android.R.styleable#TextView_drawableRight
3227      * @attr ref android.R.styleable#TextView_drawableBottom
3228      */
3229     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3230     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
3231             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
3232 
3233         if (left != null) {
3234             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
3235         }
3236         if (right != null) {
3237             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
3238         }
3239         if (top != null) {
3240             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3241         }
3242         if (bottom != null) {
3243             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3244         }
3245         setCompoundDrawables(left, top, right, bottom);
3246     }
3247 
3248     /**
3249      * Sets the Drawables (if any) to appear to the start of, above, to the end
3250      * of, and below the text. Use {@code null} if you do not want a Drawable
3251      * there. The Drawables must already have had {@link Drawable#setBounds}
3252      * called.
3253      * <p>
3254      * Calling this method will overwrite any Drawables previously set using
3255      * {@link #setCompoundDrawables} or related methods.
3256      *
3257      * @attr ref android.R.styleable#TextView_drawableStart
3258      * @attr ref android.R.styleable#TextView_drawableTop
3259      * @attr ref android.R.styleable#TextView_drawableEnd
3260      * @attr ref android.R.styleable#TextView_drawableBottom
3261      */
3262     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3263     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
3264             @Nullable Drawable end, @Nullable Drawable bottom) {
3265         Drawables dr = mDrawables;
3266 
3267         // We're switching to relative, discard absolute.
3268         if (dr != null) {
3269             if (dr.mShowing[Drawables.LEFT] != null) {
3270                 dr.mShowing[Drawables.LEFT].setCallback(null);
3271             }
3272             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3273             if (dr.mShowing[Drawables.RIGHT] != null) {
3274                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3275             }
3276             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3277             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3278             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3279         }
3280 
3281         final boolean drawables = start != null || top != null
3282                 || end != null || bottom != null;
3283 
3284         if (!drawables) {
3285             // Clearing drawables...  can we free the data structure?
3286             if (dr != null) {
3287                 if (!dr.hasMetadata()) {
3288                     mDrawables = null;
3289                 } else {
3290                     // We need to retain the last set padding, so just clear
3291                     // out all of the fields in the existing structure.
3292                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3293                     dr.mDrawableStart = null;
3294                     if (dr.mShowing[Drawables.TOP] != null) {
3295                         dr.mShowing[Drawables.TOP].setCallback(null);
3296                     }
3297                     dr.mShowing[Drawables.TOP] = null;
3298                     if (dr.mDrawableEnd != null) {
3299                         dr.mDrawableEnd.setCallback(null);
3300                     }
3301                     dr.mDrawableEnd = null;
3302                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3303                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3304                     }
3305                     dr.mShowing[Drawables.BOTTOM] = null;
3306                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3307                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3308                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3309                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3310                 }
3311             }
3312         } else {
3313             if (dr == null) {
3314                 mDrawables = dr = new Drawables(getContext());
3315             }
3316 
3317             mDrawables.mOverride = true;
3318 
3319             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3320                 dr.mDrawableStart.setCallback(null);
3321             }
3322             dr.mDrawableStart = start;
3323 
3324             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3325                 dr.mShowing[Drawables.TOP].setCallback(null);
3326             }
3327             dr.mShowing[Drawables.TOP] = top;
3328 
3329             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3330                 dr.mDrawableEnd.setCallback(null);
3331             }
3332             dr.mDrawableEnd = end;
3333 
3334             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3335                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3336             }
3337             dr.mShowing[Drawables.BOTTOM] = bottom;
3338 
3339             final Rect compoundRect = dr.mCompoundRect;
3340             int[] state;
3341 
3342             state = getDrawableState();
3343 
3344             if (start != null) {
3345                 start.setState(state);
3346                 start.copyBounds(compoundRect);
3347                 start.setCallback(this);
3348                 dr.mDrawableSizeStart = compoundRect.width();
3349                 dr.mDrawableHeightStart = compoundRect.height();
3350             } else {
3351                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3352             }
3353 
3354             if (end != null) {
3355                 end.setState(state);
3356                 end.copyBounds(compoundRect);
3357                 end.setCallback(this);
3358                 dr.mDrawableSizeEnd = compoundRect.width();
3359                 dr.mDrawableHeightEnd = compoundRect.height();
3360             } else {
3361                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3362             }
3363 
3364             if (top != null) {
3365                 top.setState(state);
3366                 top.copyBounds(compoundRect);
3367                 top.setCallback(this);
3368                 dr.mDrawableSizeTop = compoundRect.height();
3369                 dr.mDrawableWidthTop = compoundRect.width();
3370             } else {
3371                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3372             }
3373 
3374             if (bottom != null) {
3375                 bottom.setState(state);
3376                 bottom.copyBounds(compoundRect);
3377                 bottom.setCallback(this);
3378                 dr.mDrawableSizeBottom = compoundRect.height();
3379                 dr.mDrawableWidthBottom = compoundRect.width();
3380             } else {
3381                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3382             }
3383         }
3384 
3385         resetResolvedDrawables();
3386         resolveDrawables();
3387         invalidate();
3388         requestLayout();
3389     }
3390 
3391     /**
3392      * Sets the Drawables (if any) to appear to the start of, above, to the end
3393      * of, and below the text. Use 0 if you do not want a Drawable there. The
3394      * Drawables' bounds will be set to their intrinsic bounds.
3395      * <p>
3396      * Calling this method will overwrite any Drawables previously set using
3397      * {@link #setCompoundDrawables} or related methods.
3398      *
3399      * @param start Resource identifier of the start Drawable.
3400      * @param top Resource identifier of the top Drawable.
3401      * @param end Resource identifier of the end Drawable.
3402      * @param bottom Resource identifier of the bottom Drawable.
3403      *
3404      * @attr ref android.R.styleable#TextView_drawableStart
3405      * @attr ref android.R.styleable#TextView_drawableTop
3406      * @attr ref android.R.styleable#TextView_drawableEnd
3407      * @attr ref android.R.styleable#TextView_drawableBottom
3408      */
3409     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3410     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3411             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3412         final Context context = getContext();
3413         setCompoundDrawablesRelativeWithIntrinsicBounds(
3414                 start != 0 ? context.getDrawable(start) : null,
3415                 top != 0 ? context.getDrawable(top) : null,
3416                 end != 0 ? context.getDrawable(end) : null,
3417                 bottom != 0 ? context.getDrawable(bottom) : null);
3418     }
3419 
3420     /**
3421      * Sets the Drawables (if any) to appear to the start of, above, to the end
3422      * of, and below the text. Use {@code null} if you do not want a Drawable
3423      * there. The Drawables' bounds will be set to their intrinsic bounds.
3424      * <p>
3425      * Calling this method will overwrite any Drawables previously set using
3426      * {@link #setCompoundDrawables} or related methods.
3427      *
3428      * @attr ref android.R.styleable#TextView_drawableStart
3429      * @attr ref android.R.styleable#TextView_drawableTop
3430      * @attr ref android.R.styleable#TextView_drawableEnd
3431      * @attr ref android.R.styleable#TextView_drawableBottom
3432      */
3433     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3434     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3435             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3436 
3437         if (start != null) {
3438             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3439         }
3440         if (end != null) {
3441             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3442         }
3443         if (top != null) {
3444             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3445         }
3446         if (bottom != null) {
3447             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3448         }
3449         setCompoundDrawablesRelative(start, top, end, bottom);
3450     }
3451 
3452     /**
3453      * Returns drawables for the left, top, right, and bottom borders.
3454      *
3455      * @attr ref android.R.styleable#TextView_drawableLeft
3456      * @attr ref android.R.styleable#TextView_drawableTop
3457      * @attr ref android.R.styleable#TextView_drawableRight
3458      * @attr ref android.R.styleable#TextView_drawableBottom
3459      */
3460     @NonNull
getCompoundDrawables()3461     public Drawable[] getCompoundDrawables() {
3462         final Drawables dr = mDrawables;
3463         if (dr != null) {
3464             return dr.mShowing.clone();
3465         } else {
3466             return new Drawable[] { null, null, null, null };
3467         }
3468     }
3469 
3470     /**
3471      * Returns drawables for the start, top, end, and bottom borders.
3472      *
3473      * @attr ref android.R.styleable#TextView_drawableStart
3474      * @attr ref android.R.styleable#TextView_drawableTop
3475      * @attr ref android.R.styleable#TextView_drawableEnd
3476      * @attr ref android.R.styleable#TextView_drawableBottom
3477      */
3478     @NonNull
getCompoundDrawablesRelative()3479     public Drawable[] getCompoundDrawablesRelative() {
3480         final Drawables dr = mDrawables;
3481         if (dr != null) {
3482             return new Drawable[] {
3483                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3484                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3485             };
3486         } else {
3487             return new Drawable[] { null, null, null, null };
3488         }
3489     }
3490 
3491     /**
3492      * Sets the size of the padding between the compound drawables and
3493      * the text.
3494      *
3495      * @attr ref android.R.styleable#TextView_drawablePadding
3496      */
3497     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3498     public void setCompoundDrawablePadding(int pad) {
3499         Drawables dr = mDrawables;
3500         if (pad == 0) {
3501             if (dr != null) {
3502                 dr.mDrawablePadding = pad;
3503             }
3504         } else {
3505             if (dr == null) {
3506                 mDrawables = dr = new Drawables(getContext());
3507             }
3508             dr.mDrawablePadding = pad;
3509         }
3510 
3511         invalidate();
3512         requestLayout();
3513     }
3514 
3515     /**
3516      * Returns the padding between the compound drawables and the text.
3517      *
3518      * @attr ref android.R.styleable#TextView_drawablePadding
3519      */
3520     @InspectableProperty(name = "drawablePadding")
getCompoundDrawablePadding()3521     public int getCompoundDrawablePadding() {
3522         final Drawables dr = mDrawables;
3523         return dr != null ? dr.mDrawablePadding : 0;
3524     }
3525 
3526     /**
3527      * Applies a tint to the compound drawables. Does not modify the
3528      * current tint mode, which is {@link BlendMode#SRC_IN} by default.
3529      * <p>
3530      * Subsequent calls to
3531      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3532      * and related methods will automatically mutate the drawables and apply
3533      * the specified tint and tint mode using
3534      * {@link Drawable#setTintList(ColorStateList)}.
3535      *
3536      * @param tint the tint to apply, may be {@code null} to clear tint
3537      *
3538      * @attr ref android.R.styleable#TextView_drawableTint
3539      * @see #getCompoundDrawableTintList()
3540      * @see Drawable#setTintList(ColorStateList)
3541      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3542     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3543         if (mDrawables == null) {
3544             mDrawables = new Drawables(getContext());
3545         }
3546         mDrawables.mTintList = tint;
3547         mDrawables.mHasTint = true;
3548 
3549         applyCompoundDrawableTint();
3550     }
3551 
3552     /**
3553      * @return the tint applied to the compound drawables
3554      * @attr ref android.R.styleable#TextView_drawableTint
3555      * @see #setCompoundDrawableTintList(ColorStateList)
3556      */
3557     @InspectableProperty(name = "drawableTint")
getCompoundDrawableTintList()3558     public ColorStateList getCompoundDrawableTintList() {
3559         return mDrawables != null ? mDrawables.mTintList : null;
3560     }
3561 
3562     /**
3563      * Specifies the blending mode used to apply the tint specified by
3564      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3565      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3566      *
3567      * @param tintMode the blending mode used to apply the tint, may be
3568      *                 {@code null} to clear tint
3569      * @attr ref android.R.styleable#TextView_drawableTintMode
3570      * @see #setCompoundDrawableTintList(ColorStateList)
3571      * @see Drawable#setTintMode(PorterDuff.Mode)
3572      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3573     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3574         setCompoundDrawableTintBlendMode(tintMode != null
3575                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3576     }
3577 
3578     /**
3579      * Specifies the blending mode used to apply the tint specified by
3580      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3581      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3582      *
3583      * @param blendMode the blending mode used to apply the tint, may be
3584      *                 {@code null} to clear tint
3585      * @attr ref android.R.styleable#TextView_drawableTintMode
3586      * @see #setCompoundDrawableTintList(ColorStateList)
3587      * @see Drawable#setTintBlendMode(BlendMode)
3588      */
setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3589     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3590         if (mDrawables == null) {
3591             mDrawables = new Drawables(getContext());
3592         }
3593         mDrawables.mBlendMode = blendMode;
3594         mDrawables.mHasTintMode = true;
3595 
3596         applyCompoundDrawableTint();
3597     }
3598 
3599     /**
3600      * Returns the blending mode used to apply the tint to the compound
3601      * drawables, if specified.
3602      *
3603      * @return the blending mode used to apply the tint to the compound
3604      *         drawables
3605      * @attr ref android.R.styleable#TextView_drawableTintMode
3606      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3607      *
3608      */
3609     @InspectableProperty(name = "drawableTintMode")
getCompoundDrawableTintMode()3610     public PorterDuff.Mode getCompoundDrawableTintMode() {
3611         BlendMode mode = getCompoundDrawableTintBlendMode();
3612         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3613     }
3614 
3615     /**
3616      * Returns the blending mode used to apply the tint to the compound
3617      * drawables, if specified.
3618      *
3619      * @return the blending mode used to apply the tint to the compound
3620      *         drawables
3621      * @attr ref android.R.styleable#TextView_drawableTintMode
3622      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3623      */
3624     @InspectableProperty(name = "drawableBlendMode",
3625             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
getCompoundDrawableTintBlendMode()3626     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3627         return mDrawables != null ? mDrawables.mBlendMode : null;
3628     }
3629 
applyCompoundDrawableTint()3630     private void applyCompoundDrawableTint() {
3631         if (mDrawables == null) {
3632             return;
3633         }
3634 
3635         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3636             final ColorStateList tintList = mDrawables.mTintList;
3637             final BlendMode blendMode = mDrawables.mBlendMode;
3638             final boolean hasTint = mDrawables.mHasTint;
3639             final boolean hasTintMode = mDrawables.mHasTintMode;
3640             final int[] state = getDrawableState();
3641 
3642             for (Drawable dr : mDrawables.mShowing) {
3643                 if (dr == null) {
3644                     continue;
3645                 }
3646 
3647                 if (dr == mDrawables.mDrawableError) {
3648                     // From a developer's perspective, the error drawable isn't
3649                     // a compound drawable. Don't apply the generic compound
3650                     // drawable tint to it.
3651                     continue;
3652                 }
3653 
3654                 dr.mutate();
3655 
3656                 if (hasTint) {
3657                     dr.setTintList(tintList);
3658                 }
3659 
3660                 if (hasTintMode) {
3661                     dr.setTintBlendMode(blendMode);
3662                 }
3663 
3664                 // The drawable (or one of its children) may not have been
3665                 // stateful before applying the tint, so let's try again.
3666                 if (dr.isStateful()) {
3667                     dr.setState(state);
3668                 }
3669             }
3670         }
3671     }
3672 
3673     /**
3674      * @inheritDoc
3675      *
3676      * @see #setFirstBaselineToTopHeight(int)
3677      * @see #setLastBaselineToBottomHeight(int)
3678      */
3679     @Override
setPadding(int left, int top, int right, int bottom)3680     public void setPadding(int left, int top, int right, int bottom) {
3681         if (left != mPaddingLeft
3682                 || right != mPaddingRight
3683                 || top != mPaddingTop
3684                 ||  bottom != mPaddingBottom) {
3685             nullLayouts();
3686         }
3687 
3688         // the super call will requestLayout()
3689         super.setPadding(left, top, right, bottom);
3690         invalidate();
3691     }
3692 
3693     /**
3694      * @inheritDoc
3695      *
3696      * @see #setFirstBaselineToTopHeight(int)
3697      * @see #setLastBaselineToBottomHeight(int)
3698      */
3699     @Override
setPaddingRelative(int start, int top, int end, int bottom)3700     public void setPaddingRelative(int start, int top, int end, int bottom) {
3701         if (start != getPaddingStart()
3702                 || end != getPaddingEnd()
3703                 || top != mPaddingTop
3704                 || bottom != mPaddingBottom) {
3705             nullLayouts();
3706         }
3707 
3708         // the super call will requestLayout()
3709         super.setPaddingRelative(start, top, end, bottom);
3710         invalidate();
3711     }
3712 
3713     /**
3714      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3715      * the distance between the top of the TextView and first line's baseline.
3716      * <p>
3717      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3718      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3719      *
3720      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3721      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3722      * Moreover since this function sets the top padding, if the height of the TextView is less than
3723      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3724      * down and bottom will be clipped.
3725      *
3726      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3727      *      in pixels
3728      *
3729      * @see #getFirstBaselineToTopHeight()
3730      * @see #setLastBaselineToBottomHeight(int)
3731      * @see #setPadding(int, int, int, int)
3732      * @see #setPaddingRelative(int, int, int, int)
3733      *
3734      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3735      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3736     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3737         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3738 
3739         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3740         final int fontMetricsTop;
3741         if (getIncludeFontPadding()) {
3742             fontMetricsTop = fontMetrics.top;
3743         } else {
3744             fontMetricsTop = fontMetrics.ascent;
3745         }
3746 
3747         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3748         // in settings). At the moment, we don't.
3749 
3750         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3751             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3752             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3753         }
3754     }
3755 
3756     /**
3757      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3758      * the distance between the bottom of the TextView and the last line's baseline.
3759      * <p>
3760      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3761      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3762      *
3763      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3764      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3765      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3766      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3767      * clipped.
3768      *
3769      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3770      *      in pixels
3771      *
3772      * @see #getLastBaselineToBottomHeight()
3773      * @see #setFirstBaselineToTopHeight(int)
3774      * @see #setPadding(int, int, int, int)
3775      * @see #setPaddingRelative(int, int, int, int)
3776      *
3777      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3778      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3779     public void setLastBaselineToBottomHeight(
3780             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3781         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3782 
3783         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3784         final int fontMetricsBottom;
3785         if (getIncludeFontPadding()) {
3786             fontMetricsBottom = fontMetrics.bottom;
3787         } else {
3788             fontMetricsBottom = fontMetrics.descent;
3789         }
3790 
3791         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3792         // in settings). At the moment, we don't.
3793 
3794         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3795             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3796             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3797         }
3798     }
3799 
3800     /**
3801      * Returns the distance between the first text baseline and the top of this TextView.
3802      *
3803      * @see #setFirstBaselineToTopHeight(int)
3804      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3805      */
3806     @InspectableProperty
getFirstBaselineToTopHeight()3807     public int getFirstBaselineToTopHeight() {
3808         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3809     }
3810 
3811     /**
3812      * Returns the distance between the last text baseline and the bottom of this TextView.
3813      *
3814      * @see #setLastBaselineToBottomHeight(int)
3815      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3816      */
3817     @InspectableProperty
getLastBaselineToBottomHeight()3818     public int getLastBaselineToBottomHeight() {
3819         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3820     }
3821 
3822     /**
3823      * Gets the autolink mask of the text.
3824      *
3825      * See {@link Linkify#ALL} and peers for possible values.
3826      *
3827      * @attr ref android.R.styleable#TextView_autoLink
3828      */
3829     @InspectableProperty(name = "autoLink", flagMapping = {
3830             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3831             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3832             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3833             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3834     })
getAutoLinkMask()3835     public final int getAutoLinkMask() {
3836         return mAutoLinkMask;
3837     }
3838 
3839     /**
3840      * Sets the Drawable corresponding to the selection handle used for
3841      * positioning the cursor within text. The Drawable defaults to the value
3842      * of the textSelectHandle attribute.
3843      * Note that any change applied to the handle Drawable will not be visible
3844      * until the handle is hidden and then drawn again.
3845      *
3846      * @see #setTextSelectHandle(int)
3847      * @attr ref android.R.styleable#TextView_textSelectHandle
3848      */
3849     @android.view.RemotableViewMethod
setTextSelectHandle(@onNull Drawable textSelectHandle)3850     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3851         Preconditions.checkNotNull(textSelectHandle,
3852                 "The text select handle should not be null.");
3853         mTextSelectHandle = textSelectHandle;
3854         mTextSelectHandleRes = 0;
3855         if (mEditor != null) {
3856             mEditor.loadHandleDrawables(true /* overwrite */);
3857         }
3858     }
3859 
3860     /**
3861      * Sets the Drawable corresponding to the selection handle used for
3862      * positioning the cursor within text. The Drawable defaults to the value
3863      * of the textSelectHandle attribute.
3864      * Note that any change applied to the handle Drawable will not be visible
3865      * until the handle is hidden and then drawn again.
3866      *
3867      * @see #setTextSelectHandle(Drawable)
3868      * @attr ref android.R.styleable#TextView_textSelectHandle
3869      */
3870     @android.view.RemotableViewMethod
setTextSelectHandle(@rawableRes int textSelectHandle)3871     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3872         Preconditions.checkArgument(textSelectHandle != 0,
3873                 "The text select handle should be a valid drawable resource id.");
3874         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3875     }
3876 
3877     /**
3878      * Returns the Drawable corresponding to the selection handle used
3879      * for positioning the cursor within text.
3880      * Note that any change applied to the handle Drawable will not be visible
3881      * until the handle is hidden and then drawn again.
3882      *
3883      * @return the text select handle drawable
3884      *
3885      * @see #setTextSelectHandle(Drawable)
3886      * @see #setTextSelectHandle(int)
3887      * @attr ref android.R.styleable#TextView_textSelectHandle
3888      */
getTextSelectHandle()3889     @Nullable public Drawable getTextSelectHandle() {
3890         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3891             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3892         }
3893         return mTextSelectHandle;
3894     }
3895 
3896     /**
3897      * Sets the Drawable corresponding to the left handle used
3898      * for selecting text. The Drawable defaults to the value of the
3899      * textSelectHandleLeft attribute.
3900      * Note that any change applied to the handle Drawable will not be visible
3901      * until the handle is hidden and then drawn again.
3902      *
3903      * @see #setTextSelectHandleLeft(int)
3904      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3905      */
3906     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3907     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3908         Preconditions.checkNotNull(textSelectHandleLeft,
3909                 "The left text select handle should not be null.");
3910         mTextSelectHandleLeft = textSelectHandleLeft;
3911         mTextSelectHandleLeftRes = 0;
3912         if (mEditor != null) {
3913             mEditor.loadHandleDrawables(true /* overwrite */);
3914         }
3915     }
3916 
3917     /**
3918      * Sets the Drawable corresponding to the left handle used
3919      * for selecting text. The Drawable defaults to the value of the
3920      * textSelectHandleLeft attribute.
3921      * Note that any change applied to the handle Drawable will not be visible
3922      * until the handle is hidden and then drawn again.
3923      *
3924      * @see #setTextSelectHandleLeft(Drawable)
3925      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3926      */
3927     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3928     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
3929         Preconditions.checkArgument(textSelectHandleLeft != 0,
3930                 "The text select left handle should be a valid drawable resource id.");
3931         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
3932     }
3933 
3934     /**
3935      * Returns the Drawable corresponding to the left handle used
3936      * for selecting text.
3937      * Note that any change applied to the handle Drawable will not be visible
3938      * until the handle is hidden and then drawn again.
3939      *
3940      * @return the left text selection handle drawable
3941      *
3942      * @see #setTextSelectHandleLeft(Drawable)
3943      * @see #setTextSelectHandleLeft(int)
3944      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3945      */
getTextSelectHandleLeft()3946     @Nullable public Drawable getTextSelectHandleLeft() {
3947         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
3948             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
3949         }
3950         return mTextSelectHandleLeft;
3951     }
3952 
3953     /**
3954      * Sets the Drawable corresponding to the right handle used
3955      * for selecting text. The Drawable defaults to the value of the
3956      * textSelectHandleRight attribute.
3957      * Note that any change applied to the handle Drawable will not be visible
3958      * until the handle is hidden and then drawn again.
3959      *
3960      * @see #setTextSelectHandleRight(int)
3961      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3962      */
3963     @android.view.RemotableViewMethod
setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3964     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
3965         Preconditions.checkNotNull(textSelectHandleRight,
3966                 "The right text select handle should not be null.");
3967         mTextSelectHandleRight = textSelectHandleRight;
3968         mTextSelectHandleRightRes = 0;
3969         if (mEditor != null) {
3970             mEditor.loadHandleDrawables(true /* overwrite */);
3971         }
3972     }
3973 
3974     /**
3975      * Sets the Drawable corresponding to the right handle used
3976      * for selecting text. The Drawable defaults to the value of the
3977      * textSelectHandleRight attribute.
3978      * Note that any change applied to the handle Drawable will not be visible
3979      * until the handle is hidden and then drawn again.
3980      *
3981      * @see #setTextSelectHandleRight(Drawable)
3982      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3983      */
3984     @android.view.RemotableViewMethod
setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3985     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
3986         Preconditions.checkArgument(textSelectHandleRight != 0,
3987                 "The text select right handle should be a valid drawable resource id.");
3988         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
3989     }
3990 
3991     /**
3992      * Returns the Drawable corresponding to the right handle used
3993      * for selecting text.
3994      * Note that any change applied to the handle Drawable will not be visible
3995      * until the handle is hidden and then drawn again.
3996      *
3997      * @return the right text selection handle drawable
3998      *
3999      * @see #setTextSelectHandleRight(Drawable)
4000      * @see #setTextSelectHandleRight(int)
4001      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4002      */
getTextSelectHandleRight()4003     @Nullable public Drawable getTextSelectHandleRight() {
4004         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
4005             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
4006         }
4007         return mTextSelectHandleRight;
4008     }
4009 
4010     /**
4011      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
4012      * value of the textCursorDrawable attribute.
4013      * Note that any change applied to the cursor Drawable will not be visible
4014      * until the cursor is hidden and then drawn again.
4015      *
4016      * @see #setTextCursorDrawable(int)
4017      * @attr ref android.R.styleable#TextView_textCursorDrawable
4018      */
setTextCursorDrawable(@ullable Drawable textCursorDrawable)4019     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
4020         mCursorDrawable = textCursorDrawable;
4021         mCursorDrawableRes = 0;
4022         if (mEditor != null) {
4023             mEditor.loadCursorDrawable();
4024         }
4025     }
4026 
4027     /**
4028      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
4029      * value of the textCursorDrawable attribute.
4030      * Note that any change applied to the cursor Drawable will not be visible
4031      * until the cursor is hidden and then drawn again.
4032      *
4033      * @see #setTextCursorDrawable(Drawable)
4034      * @attr ref android.R.styleable#TextView_textCursorDrawable
4035      */
setTextCursorDrawable(@rawableRes int textCursorDrawable)4036     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
4037         setTextCursorDrawable(
4038                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
4039     }
4040 
4041     /**
4042      * Returns the Drawable corresponding to the text cursor.
4043      * Note that any change applied to the cursor Drawable will not be visible
4044      * until the cursor is hidden and then drawn again.
4045      *
4046      * @return the text cursor drawable
4047      *
4048      * @see #setTextCursorDrawable(Drawable)
4049      * @see #setTextCursorDrawable(int)
4050      * @attr ref android.R.styleable#TextView_textCursorDrawable
4051      */
getTextCursorDrawable()4052     @Nullable public Drawable getTextCursorDrawable() {
4053         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
4054             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
4055         }
4056         return mCursorDrawable;
4057     }
4058 
4059     /**
4060      * Sets the text appearance from the specified style resource.
4061      * <p>
4062      * Use a framework-defined {@code TextAppearance} style like
4063      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
4064      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
4065      * set of attributes that can be used in a custom style.
4066      *
4067      * @param resId the resource identifier of the style to apply
4068      * @attr ref android.R.styleable#TextView_textAppearance
4069      */
4070     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)4071     public void setTextAppearance(@StyleRes int resId) {
4072         setTextAppearance(mContext, resId);
4073     }
4074 
4075     /**
4076      * Sets the text color, size, style, hint color, and highlight color
4077      * from the specified TextAppearance resource.
4078      *
4079      * @deprecated Use {@link #setTextAppearance(int)} instead.
4080      */
4081     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)4082     public void setTextAppearance(Context context, @StyleRes int resId) {
4083         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
4084         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
4085         readTextAppearance(context, ta, attributes, false /* styleArray */);
4086         ta.recycle();
4087         applyTextAppearance(attributes);
4088     }
4089 
4090     /**
4091      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
4092      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
4093      */
4094     private static class TextAppearanceAttributes {
4095         int mTextColorHighlight = 0;
4096         int mSearchResultHighlightColor = 0;
4097         int mFocusedSearchResultHighlightColor = 0;
4098         ColorStateList mTextColor = null;
4099         ColorStateList mTextColorHint = null;
4100         ColorStateList mTextColorLink = null;
4101         int mTextSize = -1;
4102         int mTextSizeUnit = -1;
4103         LocaleList mTextLocales = null;
4104         String mFontFamily = null;
4105         Typeface mFontTypeface = null;
4106         boolean mFontFamilyExplicit = false;
4107         int mTypefaceIndex = -1;
4108         int mTextStyle = 0;
4109         int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
4110         boolean mAllCaps = false;
4111         int mShadowColor = 0;
4112         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
4113         boolean mHasElegant = false;
4114         boolean mElegant = false;
4115         boolean mHasFallbackLineSpacing = false;
4116         boolean mFallbackLineSpacing = false;
4117         boolean mHasLetterSpacing = false;
4118         float mLetterSpacing = 0;
4119         String mFontFeatureSettings = null;
4120         String mFontVariationSettings = null;
4121         boolean mHasLineBreakStyle = false;
4122         boolean mHasLineBreakWordStyle = false;
4123         int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
4124         int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
4125 
4126         @Override
toString()4127         public String toString() {
4128             return "TextAppearanceAttributes {\n"
4129                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
4130                     + "    mSearchResultHighlightColor: " + mSearchResultHighlightColor + "\n"
4131                     + "    mFocusedSearchResultHighlightColor: "
4132                     + mFocusedSearchResultHighlightColor + "\n"
4133                     + "    mTextColor:" + mTextColor + "\n"
4134                     + "    mTextColorHint:" + mTextColorHint + "\n"
4135                     + "    mTextColorLink:" + mTextColorLink + "\n"
4136                     + "    mTextSize:" + mTextSize + "\n"
4137                     + "    mTextSizeUnit:" + mTextSizeUnit + "\n"
4138                     + "    mTextLocales:" + mTextLocales + "\n"
4139                     + "    mFontFamily:" + mFontFamily + "\n"
4140                     + "    mFontTypeface:" + mFontTypeface + "\n"
4141                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
4142                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
4143                     + "    mTextStyle:" + mTextStyle + "\n"
4144                     + "    mFontWeight:" + mFontWeight + "\n"
4145                     + "    mAllCaps:" + mAllCaps + "\n"
4146                     + "    mShadowColor:" + mShadowColor + "\n"
4147                     + "    mShadowDx:" + mShadowDx + "\n"
4148                     + "    mShadowDy:" + mShadowDy + "\n"
4149                     + "    mShadowRadius:" + mShadowRadius + "\n"
4150                     + "    mHasElegant:" + mHasElegant + "\n"
4151                     + "    mElegant:" + mElegant + "\n"
4152                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
4153                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
4154                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
4155                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
4156                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
4157                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
4158                     + "    mHasLineBreakStyle:" + mHasLineBreakStyle + "\n"
4159                     + "    mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n"
4160                     + "    mLineBreakStyle:" + mLineBreakStyle + "\n"
4161                     + "    mLineBreakWordStyle:" + mLineBreakWordStyle + "\n"
4162                     + "}";
4163         }
4164     }
4165 
4166     // Maps styleable attributes that exist both in TextView style and TextAppearance.
4167     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
4168     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)4169         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
4170                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor)4171         sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor,
4172                 com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor);
sAppearanceValues.put( com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor)4173         sAppearanceValues.put(
4174                 com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor,
4175                 com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)4176         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
4177                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)4178         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
4179                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)4180         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
4181                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)4182         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
4183                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)4184         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
4185                 com.android.internal.R.styleable.TextAppearance_textLocale);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)4186         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
4187                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)4188         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
4189                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)4190         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
4191                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4192         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
4193                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4194         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
4195                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4196         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
4197                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4198         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
4199                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4200         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
4201                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4202         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
4203                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4204         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
4205                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4206         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
4207                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4208         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
4209                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4210         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
4211                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4212         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
4213                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, com.android.internal.R.styleable.TextAppearance_lineBreakStyle)4214         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle,
4215                 com.android.internal.R.styleable.TextAppearance_lineBreakStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle)4216         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle,
4217                 com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle);
4218     }
4219 
4220     /**
4221      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
4222      * set. If the TypedArray contains a value that was already set in the given attributes, that
4223      * will be overridden.
4224      *
4225      * @param context The Context to be used
4226      * @param appearance The TypedArray to read properties from
4227      * @param attributes the TextAppearanceAttributes to fill in
4228      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
4229      *                   what attribute indexes will be used to read the properties.
4230      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)4231     private void readTextAppearance(Context context, TypedArray appearance,
4232             TextAppearanceAttributes attributes, boolean styleArray) {
4233         final int n = appearance.getIndexCount();
4234         for (int i = 0; i < n; i++) {
4235             final int attr = appearance.getIndex(i);
4236             int index = attr;
4237             // Translate style array index ids to TextAppearance ids.
4238             if (styleArray) {
4239                 index = sAppearanceValues.get(attr, -1);
4240                 if (index == -1) {
4241                     // This value is not part of a Text Appearance and should be ignored.
4242                     continue;
4243                 }
4244             }
4245             switch (index) {
4246                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
4247                     attributes.mTextColorHighlight =
4248                             appearance.getColor(attr, attributes.mTextColorHighlight);
4249                     break;
4250                 case com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor:
4251                     attributes.mSearchResultHighlightColor =
4252                             appearance.getColor(attr, attributes.mSearchResultHighlightColor);
4253                     break;
4254                 case com.android.internal.R.styleable
4255                         .TextAppearance_focusedSearchResultHighlightColor:
4256                     attributes.mFocusedSearchResultHighlightColor =
4257                             appearance.getColor(attr,
4258                                     attributes.mFocusedSearchResultHighlightColor);
4259                     break;
4260                 case com.android.internal.R.styleable.TextAppearance_textColor:
4261                     attributes.mTextColor = appearance.getColorStateList(attr);
4262                     break;
4263                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
4264                     attributes.mTextColorHint = appearance.getColorStateList(attr);
4265                     break;
4266                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
4267                     attributes.mTextColorLink = appearance.getColorStateList(attr);
4268                     break;
4269                 case com.android.internal.R.styleable.TextAppearance_textSize:
4270                     attributes.mTextSize =
4271                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
4272                     attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit();
4273                     break;
4274                 case com.android.internal.R.styleable.TextAppearance_textLocale:
4275                     final String localeString = appearance.getString(attr);
4276                     if (localeString != null) {
4277                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
4278                         if (!localeList.isEmpty()) {
4279                             attributes.mTextLocales = localeList;
4280                         }
4281                     }
4282                     break;
4283                 case com.android.internal.R.styleable.TextAppearance_typeface:
4284                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
4285                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4286                         attributes.mFontFamily = null;
4287                     }
4288                     break;
4289                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
4290                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
4291                         try {
4292                             attributes.mFontTypeface = appearance.getFont(attr);
4293                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
4294                             // Expected if it is not a font resource.
4295                         }
4296                     }
4297                     if (attributes.mFontTypeface == null) {
4298                         attributes.mFontFamily = appearance.getString(attr);
4299                     }
4300                     attributes.mFontFamilyExplicit = true;
4301                     break;
4302                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4303                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4304                     break;
4305                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4306                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4307                     break;
4308                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4309                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4310                     break;
4311                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4312                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4313                     break;
4314                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4315                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4316                     break;
4317                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4318                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4319                     break;
4320                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4321                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4322                     break;
4323                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4324                     attributes.mHasElegant = true;
4325                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4326                     break;
4327                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4328                     attributes.mHasFallbackLineSpacing = true;
4329                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4330                             attributes.mFallbackLineSpacing);
4331                     break;
4332                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4333                     attributes.mHasLetterSpacing = true;
4334                     attributes.mLetterSpacing =
4335                             appearance.getFloat(attr, attributes.mLetterSpacing);
4336                     break;
4337                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4338                     attributes.mFontFeatureSettings = appearance.getString(attr);
4339                     break;
4340                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4341                     attributes.mFontVariationSettings = appearance.getString(attr);
4342                     break;
4343                 case com.android.internal.R.styleable.TextAppearance_lineBreakStyle:
4344                     attributes.mHasLineBreakStyle = true;
4345                     attributes.mLineBreakStyle =
4346                             appearance.getInt(attr, attributes.mLineBreakStyle);
4347                     break;
4348                 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
4349                     attributes.mHasLineBreakWordStyle = true;
4350                     attributes.mLineBreakWordStyle =
4351                             appearance.getInt(attr, attributes.mLineBreakWordStyle);
4352                     break;
4353                 default:
4354             }
4355         }
4356     }
4357 
applyTextAppearance(TextAppearanceAttributes attributes)4358     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4359         if (attributes.mTextColor != null) {
4360             setTextColor(attributes.mTextColor);
4361         }
4362 
4363         if (attributes.mTextColorHint != null) {
4364             setHintTextColor(attributes.mTextColorHint);
4365         }
4366 
4367         if (attributes.mTextColorLink != null) {
4368             setLinkTextColor(attributes.mTextColorLink);
4369         }
4370 
4371         if (attributes.mTextColorHighlight != 0) {
4372             setHighlightColor(attributes.mTextColorHighlight);
4373         }
4374 
4375         if (attributes.mSearchResultHighlightColor != 0) {
4376             setSearchResultHighlightColor(attributes.mSearchResultHighlightColor);
4377         }
4378 
4379         if (attributes.mFocusedSearchResultHighlightColor != 0) {
4380             setFocusedSearchResultHighlightColor(attributes.mFocusedSearchResultHighlightColor);
4381         }
4382 
4383         if (attributes.mTextSize != -1) {
4384             mTextSizeUnit = attributes.mTextSizeUnit;
4385             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4386         }
4387 
4388         if (attributes.mTextLocales != null) {
4389             setTextLocales(attributes.mTextLocales);
4390         }
4391 
4392         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4393             attributes.mFontFamily = null;
4394         }
4395         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4396                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4397 
4398         if (attributes.mShadowColor != 0) {
4399             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4400                     attributes.mShadowColor);
4401         }
4402 
4403         if (attributes.mAllCaps) {
4404             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4405         }
4406 
4407         if (attributes.mHasElegant) {
4408             setElegantTextHeight(attributes.mElegant);
4409         }
4410 
4411         if (attributes.mHasFallbackLineSpacing) {
4412             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4413         }
4414 
4415         if (attributes.mHasLetterSpacing) {
4416             setLetterSpacing(attributes.mLetterSpacing);
4417         }
4418 
4419         if (attributes.mFontFeatureSettings != null) {
4420             setFontFeatureSettings(attributes.mFontFeatureSettings);
4421         }
4422 
4423         if (attributes.mFontVariationSettings != null) {
4424             setFontVariationSettings(attributes.mFontVariationSettings);
4425         }
4426 
4427         if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) {
4428             updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle,
4429                     attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle,
4430                     attributes.mLineBreakWordStyle);
4431         }
4432     }
4433 
4434     /**
4435      * Updates the LineBreakConfig from the TextAppearance.
4436      *
4437      * This method updates the given line configuration from the TextAppearance. This method will
4438      * request new layout if line break config has been changed.
4439      *
4440      * @param isLineBreakStyleSpecified true if the line break style is specified.
4441      * @param isLineBreakWordStyleSpecified true if the line break word style is specified.
4442      * @param lineBreakStyle the value of the line break style in the TextAppearance.
4443      * @param lineBreakWordStyle the value of the line break word style in the TextAppearance.
4444      */
updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, boolean isLineBreakWordStyleSpecified, @LineBreakConfig.LineBreakStyle int lineBreakStyle, @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)4445     private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified,
4446             boolean isLineBreakWordStyleSpecified,
4447             @LineBreakConfig.LineBreakStyle int lineBreakStyle,
4448             @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
4449         boolean updated = false;
4450         if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) {
4451             mLineBreakStyle = lineBreakStyle;
4452             updated = true;
4453         }
4454         if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) {
4455             mLineBreakWordStyle = lineBreakWordStyle;
4456             updated = true;
4457         }
4458         if (updated && mLayout != null) {
4459             nullLayouts();
4460             requestLayout();
4461             invalidate();
4462         }
4463     }
4464     /**
4465      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4466      * the first member of {@link #getTextLocales()}.
4467      * @return the default primary {@link Locale} of the text in this TextView.
4468      */
4469     @NonNull
getTextLocale()4470     public Locale getTextLocale() {
4471         return mTextPaint.getTextLocale();
4472     }
4473 
4474     /**
4475      * Get the default {@link LocaleList} of the text in this TextView.
4476      * @return the default {@link LocaleList} of the text in this TextView.
4477      */
4478     @NonNull @Size(min = 1)
getTextLocales()4479     public LocaleList getTextLocales() {
4480         return mTextPaint.getTextLocales();
4481     }
4482 
changeListenerLocaleTo(@ullable Locale locale)4483     private void changeListenerLocaleTo(@Nullable Locale locale) {
4484         if (mListenerChanged) {
4485             // If a listener has been explicitly set, don't change it. We may break something.
4486             return;
4487         }
4488         // The following null check is not absolutely necessary since all calling points of
4489         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4490         // here in case others would want to call this method in the future.
4491         if (mEditor != null) {
4492             KeyListener listener = mEditor.mKeyListener;
4493             if (listener instanceof DigitsKeyListener) {
4494                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4495             } else if (listener instanceof DateKeyListener) {
4496                 listener = DateKeyListener.getInstance(locale);
4497             } else if (listener instanceof TimeKeyListener) {
4498                 listener = TimeKeyListener.getInstance(locale);
4499             } else if (listener instanceof DateTimeKeyListener) {
4500                 listener = DateTimeKeyListener.getInstance(locale);
4501             } else {
4502                 return;
4503             }
4504             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4505             setKeyListenerOnly(listener);
4506             setInputTypeFromEditor();
4507             if (wasPasswordType) {
4508                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4509                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4510                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4511                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4512                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4513                 }
4514             }
4515         }
4516     }
4517 
4518     /**
4519      * Set the default {@link Locale} of the text in this TextView to a one-member
4520      * {@link LocaleList} containing just the given Locale.
4521      *
4522      * @param locale the {@link Locale} for drawing text, must not be null.
4523      *
4524      * @see #setTextLocales
4525      */
setTextLocale(@onNull Locale locale)4526     public void setTextLocale(@NonNull Locale locale) {
4527         mLocalesChanged = true;
4528         mTextPaint.setTextLocale(locale);
4529         if (mLayout != null) {
4530             nullLayouts();
4531             requestLayout();
4532             invalidate();
4533         }
4534     }
4535 
4536     /**
4537      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4538      *
4539      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4540      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4541      * other aspects of text display, including line breaking.
4542      *
4543      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4544      *
4545      * @see Paint#setTextLocales
4546      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)4547     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4548         mLocalesChanged = true;
4549         mTextPaint.setTextLocales(locales);
4550         if (mLayout != null) {
4551             nullLayouts();
4552             requestLayout();
4553             invalidate();
4554         }
4555     }
4556 
4557     @Override
onConfigurationChanged(Configuration newConfig)4558     protected void onConfigurationChanged(Configuration newConfig) {
4559         super.onConfigurationChanged(newConfig);
4560         if (!mLocalesChanged) {
4561             mTextPaint.setTextLocales(LocaleList.getDefault());
4562             if (mLayout != null) {
4563                 nullLayouts();
4564                 requestLayout();
4565                 invalidate();
4566             }
4567         }
4568         if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) {
4569             mFontWeightAdjustment = newConfig.fontWeightAdjustment;
4570             setTypeface(getTypeface());
4571         }
4572     }
4573 
4574     /**
4575      * @return the size (in pixels) of the default text size in this TextView.
4576      */
4577     @InspectableProperty
4578     @ViewDebug.ExportedProperty(category = "text")
getTextSize()4579     public float getTextSize() {
4580         return mTextPaint.getTextSize();
4581     }
4582 
4583     /**
4584      * @return the size (in scaled pixels) of the default text size in this TextView.
4585      * @hide
4586      */
4587     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()4588     public float getScaledTextSize() {
4589         return mTextPaint.getTextSize() / mTextPaint.density;
4590     }
4591 
4592     /** @hide */
4593     @ViewDebug.ExportedProperty(category = "text", mapping = {
4594             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4595             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4596             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4597             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4598     })
getTypefaceStyle()4599     public int getTypefaceStyle() {
4600         Typeface typeface = mTextPaint.getTypeface();
4601         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4602     }
4603 
4604     /**
4605      * Set the default text size to the given value, interpreted as "scaled
4606      * pixel" units.  This size is adjusted based on the current density and
4607      * user font size preference.
4608      *
4609      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4610      *
4611      * @param size The scaled pixel size.
4612      *
4613      * @attr ref android.R.styleable#TextView_textSize
4614      */
4615     @android.view.RemotableViewMethod
setTextSize(float size)4616     public void setTextSize(float size) {
4617         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4618     }
4619 
4620     /**
4621      * Set the default text size to a given unit and value. See {@link
4622      * TypedValue} for the possible dimension units.
4623      *
4624      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4625      *
4626      * @param unit The desired dimension unit.
4627      * @param size The desired size in the given units.
4628      *
4629      * @attr ref android.R.styleable#TextView_textSize
4630      */
setTextSize(int unit, float size)4631     public void setTextSize(int unit, float size) {
4632         if (!isAutoSizeEnabled()) {
4633             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4634         }
4635     }
4636 
4637     @NonNull
getDisplayMetricsOrSystem()4638     private DisplayMetrics getDisplayMetricsOrSystem() {
4639         Context c = getContext();
4640         Resources r;
4641 
4642         if (c == null) {
4643             r = Resources.getSystem();
4644         } else {
4645             r = c.getResources();
4646         }
4647 
4648         return r.getDisplayMetrics();
4649     }
4650 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4651     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4652         mTextSizeUnit = unit;
4653         setRawTextSize(TypedValue.applyDimension(unit, size, getDisplayMetricsOrSystem()),
4654                 shouldRequestLayout);
4655     }
4656 
4657     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setRawTextSize(float size, boolean shouldRequestLayout)4658     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4659         if (size != mTextPaint.getTextSize()) {
4660             mTextPaint.setTextSize(size);
4661 
4662             maybeRecalculateLineHeight();
4663             if (shouldRequestLayout && mLayout != null) {
4664                 // Do not auto-size right after setting the text size.
4665                 mNeedsAutoSizeText = false;
4666                 nullLayouts();
4667                 requestLayout();
4668                 invalidate();
4669             }
4670         }
4671     }
4672 
4673     /**
4674      * Gets the text size unit defined by the developer. It may be specified in resources or be
4675      * passed as the unit argument of {@link #setTextSize(int, float)} at runtime.
4676      *
4677      * @return the dimension type of the text size unit originally defined.
4678      * @see TypedValue#TYPE_DIMENSION
4679      */
getTextSizeUnit()4680     public int getTextSizeUnit() {
4681         return mTextSizeUnit;
4682     }
4683 
4684     /**
4685      * Gets the extent by which text should be stretched horizontally.
4686      * This will usually be 1.0.
4687      * @return The horizontal scale factor.
4688      */
4689     @InspectableProperty
getTextScaleX()4690     public float getTextScaleX() {
4691         return mTextPaint.getTextScaleX();
4692     }
4693 
4694     /**
4695      * Sets the horizontal scale factor for text. The default value
4696      * is 1.0. Values greater than 1.0 stretch the text wider.
4697      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4698      * @param size The horizontal scale factor.
4699      * @attr ref android.R.styleable#TextView_textScaleX
4700      */
4701     @android.view.RemotableViewMethod
setTextScaleX(float size)4702     public void setTextScaleX(float size) {
4703         if (size != mTextPaint.getTextScaleX()) {
4704             mUserSetTextScaleX = true;
4705             mTextPaint.setTextScaleX(size);
4706 
4707             if (mLayout != null) {
4708                 nullLayouts();
4709                 requestLayout();
4710                 invalidate();
4711             }
4712         }
4713     }
4714 
4715     /**
4716      * Sets the typeface and style in which the text should be displayed.
4717      * Note that not all Typeface families actually have bold and italic
4718      * variants, so you may need to use
4719      * {@link #setTypeface(Typeface, int)} to get the appearance
4720      * that you actually want.
4721      *
4722      * @see #getTypeface()
4723      *
4724      * @attr ref android.R.styleable#TextView_fontFamily
4725      * @attr ref android.R.styleable#TextView_typeface
4726      * @attr ref android.R.styleable#TextView_textStyle
4727      */
setTypeface(@ullable Typeface tf)4728     public void setTypeface(@Nullable Typeface tf) {
4729         mOriginalTypeface = tf;
4730         if (mFontWeightAdjustment != 0
4731                 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
4732             if (tf == null) {
4733                 tf = Typeface.DEFAULT;
4734             } else {
4735                 int newWeight = Math.min(
4736                         Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
4737                         FontStyle.FONT_WEIGHT_MAX);
4738                 int typefaceStyle = tf != null ? tf.getStyle() : 0;
4739                 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0;
4740                 tf = Typeface.create(tf, newWeight, italic);
4741             }
4742         }
4743         if (mTextPaint.getTypeface() != tf) {
4744             mTextPaint.setTypeface(tf);
4745 
4746             if (mLayout != null) {
4747                 nullLayouts();
4748                 requestLayout();
4749                 invalidate();
4750             }
4751         }
4752     }
4753 
4754     /**
4755      * Gets the current {@link Typeface} that is used to style the text.
4756      * @return The current Typeface.
4757      *
4758      * @see #setTypeface(Typeface)
4759      *
4760      * @attr ref android.R.styleable#TextView_fontFamily
4761      * @attr ref android.R.styleable#TextView_typeface
4762      * @attr ref android.R.styleable#TextView_textStyle
4763      */
4764     @InspectableProperty
getTypeface()4765     public Typeface getTypeface() {
4766         return mOriginalTypeface;
4767     }
4768 
4769     /**
4770      * Set the TextView's elegant height metrics flag. This setting selects font
4771      * variants that have not been compacted to fit Latin-based vertical
4772      * metrics, and also increases top and bottom bounds to provide more space.
4773      *
4774      * @param elegant set the paint's elegant metrics flag.
4775      *
4776      * @see #isElegantTextHeight()
4777      * @see Paint#isElegantTextHeight()
4778      *
4779      * @attr ref android.R.styleable#TextView_elegantTextHeight
4780      */
setElegantTextHeight(boolean elegant)4781     public void setElegantTextHeight(boolean elegant) {
4782         if (elegant != mTextPaint.isElegantTextHeight()) {
4783             mTextPaint.setElegantTextHeight(elegant);
4784             if (mLayout != null) {
4785                 nullLayouts();
4786                 requestLayout();
4787                 invalidate();
4788             }
4789         }
4790     }
4791 
4792     /**
4793      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4794      * displaying the text (which is needed to avoid text from consecutive lines running into
4795      * each other). If set, fallback fonts that end up getting used can increase the ascent
4796      * and descent of the lines that they are used on.
4797      * <p/>
4798      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4799      * is typically much taller or deeper than Latin text.
4800      *
4801      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4802      *
4803      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4804      *
4805      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4806      */
setFallbackLineSpacing(boolean enabled)4807     public void setFallbackLineSpacing(boolean enabled) {
4808         int fallbackStrategy;
4809         if (enabled) {
4810             if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
4811                 fallbackStrategy = FALLBACK_LINE_SPACING_ALL;
4812             } else {
4813                 fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
4814             }
4815         } else {
4816             fallbackStrategy = FALLBACK_LINE_SPACING_NONE;
4817         }
4818         if (mUseFallbackLineSpacing != fallbackStrategy) {
4819             mUseFallbackLineSpacing = fallbackStrategy;
4820             if (mLayout != null) {
4821                 nullLayouts();
4822                 requestLayout();
4823                 invalidate();
4824             }
4825         }
4826     }
4827 
4828     /**
4829      * @return whether fallback line spacing is enabled, {@code true} by default
4830      *
4831      * @see #setFallbackLineSpacing(boolean)
4832      *
4833      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4834      */
4835     @InspectableProperty
isFallbackLineSpacing()4836     public boolean isFallbackLineSpacing() {
4837         return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE;
4838     }
4839 
isFallbackLineSpacingForBoringLayout()4840     private boolean isFallbackLineSpacingForBoringLayout() {
4841         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL;
4842     }
4843 
4844     // Package privte for accessing from Editor.java
isFallbackLineSpacingForStaticLayout()4845     /* package */ boolean isFallbackLineSpacingForStaticLayout() {
4846         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL
4847                 || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
4848     }
4849 
4850     /**
4851      * Get the value of the TextView's elegant height metrics flag. This setting selects font
4852      * variants that have not been compacted to fit Latin-based vertical
4853      * metrics, and also increases top and bottom bounds to provide more space.
4854      * @return {@code true} if the elegant height metrics flag is set.
4855      *
4856      * @see #setElegantTextHeight(boolean)
4857      * @see Paint#setElegantTextHeight(boolean)
4858      */
4859     @InspectableProperty
isElegantTextHeight()4860     public boolean isElegantTextHeight() {
4861         return mTextPaint.isElegantTextHeight();
4862     }
4863 
4864     /**
4865      * Gets the text letter-space value, which determines the spacing between characters.
4866      * The value returned is in ems. Normally, this value is 0.0.
4867      * @return The text letter-space value in ems.
4868      *
4869      * @see #setLetterSpacing(float)
4870      * @see Paint#setLetterSpacing
4871      */
4872     @InspectableProperty
getLetterSpacing()4873     public float getLetterSpacing() {
4874         return mTextPaint.getLetterSpacing();
4875     }
4876 
4877     /**
4878      * Sets text letter-spacing in em units.  Typical values
4879      * for slight expansion will be around 0.05.  Negative values tighten text.
4880      *
4881      * @see #getLetterSpacing()
4882      * @see Paint#getLetterSpacing
4883      *
4884      * @param letterSpacing A text letter-space value in ems.
4885      * @attr ref android.R.styleable#TextView_letterSpacing
4886      */
4887     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)4888     public void setLetterSpacing(float letterSpacing) {
4889         if (letterSpacing != mTextPaint.getLetterSpacing()) {
4890             mTextPaint.setLetterSpacing(letterSpacing);
4891 
4892             if (mLayout != null) {
4893                 nullLayouts();
4894                 requestLayout();
4895                 invalidate();
4896             }
4897         }
4898     }
4899 
4900     /**
4901      * Returns the font feature settings. The format is the same as the CSS
4902      * font-feature-settings attribute:
4903      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4904      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4905      *
4906      * @return the currently set font feature settings.  Default is null.
4907      *
4908      * @see #setFontFeatureSettings(String)
4909      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
4910      */
4911     @InspectableProperty
4912     @Nullable
getFontFeatureSettings()4913     public String getFontFeatureSettings() {
4914         return mTextPaint.getFontFeatureSettings();
4915     }
4916 
4917     /**
4918      * Returns the font variation settings.
4919      *
4920      * @return the currently set font variation settings.  Returns null if no variation is
4921      * specified.
4922      *
4923      * @see #setFontVariationSettings(String)
4924      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
4925      */
4926     @Nullable
getFontVariationSettings()4927     public String getFontVariationSettings() {
4928         return mTextPaint.getFontVariationSettings();
4929     }
4930 
4931     /**
4932      * Sets the break strategy for breaking paragraphs into lines. The default value for
4933      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
4934      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
4935      * text "dancing" when being edited.
4936      * <p>
4937      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4938      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4939      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4940      * improves the structure of text layout however has performance impact and requires more time
4941      * to do the text layout.</p>
4942      * <p>
4943      * Compared with {@link #setLineBreakStyle(int)}, line break style with different strictness is
4944      * evaluated in the ICU to identify the potential breakpoints. In
4945      * {@link #setBreakStrategy(int)}, line break strategy handles the post processing of ICU's line
4946      * break result. It aims to evaluate ICU's breakpoints and break the lines based on the
4947      * constraint.
4948      * </p>
4949      *
4950      * @attr ref android.R.styleable#TextView_breakStrategy
4951      * @see #getBreakStrategy()
4952      * @see #setHyphenationFrequency(int)
4953      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4954     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
4955         mBreakStrategy = breakStrategy;
4956         if (mLayout != null) {
4957             nullLayouts();
4958             requestLayout();
4959             invalidate();
4960         }
4961     }
4962 
4963     /**
4964      * Gets the current strategy for breaking paragraphs into lines.
4965      * @return the current strategy for breaking paragraphs into lines.
4966      *
4967      * @attr ref android.R.styleable#TextView_breakStrategy
4968      * @see #setBreakStrategy(int)
4969      */
4970     @InspectableProperty(enumMapping = {
4971             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
4972             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
4973             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
4974     })
4975     @Layout.BreakStrategy
getBreakStrategy()4976     public int getBreakStrategy() {
4977         return mBreakStrategy;
4978     }
4979 
4980     /**
4981      * Sets the frequency of automatic hyphenation to use when determining word breaks.
4982      * The default value for both TextView and {@link EditText} is
4983      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
4984      * is set from the theme.
4985      * <p/>
4986      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4987      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4988      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4989      * improves the structure of text layout however has performance impact and requires more time
4990      * to do the text layout.
4991      * <p/>
4992      * Note: Before Android Q, in the theme hyphenation frequency is set to
4993      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
4994      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
4995      *
4996      * @param hyphenationFrequency the hyphenation frequency to use, one of
4997      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
4998      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
4999      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
5000      * @attr ref android.R.styleable#TextView_hyphenationFrequency
5001      * @see #getHyphenationFrequency()
5002      * @see #getBreakStrategy()
5003      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)5004     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
5005         mHyphenationFrequency = hyphenationFrequency;
5006         if (mLayout != null) {
5007             nullLayouts();
5008             requestLayout();
5009             invalidate();
5010         }
5011     }
5012 
5013     /**
5014      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
5015      * @return the current frequency of automatic hyphenation to be used when determining word
5016      * breaks.
5017      *
5018      * @attr ref android.R.styleable#TextView_hyphenationFrequency
5019      * @see #setHyphenationFrequency(int)
5020      */
5021     @InspectableProperty(enumMapping = {
5022             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
5023             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
5024             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
5025     })
5026     @Layout.HyphenationFrequency
getHyphenationFrequency()5027     public int getHyphenationFrequency() {
5028         return mHyphenationFrequency;
5029     }
5030 
5031     /**
5032      * Sets the line-break style for text wrapping.
5033      *
5034      * <p>Line-break style specifies the line-break strategies that can be used
5035      * for text wrapping. The line-break style affects rule-based line breaking
5036      * by specifying the strictness of line-breaking rules.
5037      *
5038      * <p>The following are types of line-break styles:
5039      * <ul>
5040      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}
5041      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}
5042      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}
5043      * </ul>
5044      *
5045      * <p>The default line-break style is
5046      * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
5047      * line-breaking rules are used.
5048      *
5049      * <p>See the
5050      * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
5051      * line-break property</a> for more information.
5052      *
5053      * @param lineBreakStyle The line-break style for the text.
5054      */
setLineBreakStyle(@ineBreakConfig.LineBreakStyle int lineBreakStyle)5055     public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
5056         if (mLineBreakStyle != lineBreakStyle) {
5057             mLineBreakStyle = lineBreakStyle;
5058             if (mLayout != null) {
5059                 nullLayouts();
5060                 requestLayout();
5061                 invalidate();
5062             }
5063         }
5064     }
5065 
5066     /**
5067      * Sets the line-break word style for text wrapping.
5068      *
5069      * <p>The line-break word style affects dictionary-based line breaking by
5070      * providing phrase-based line-breaking opportunities. Use
5071      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
5072      * phrase-based line breaking.
5073      *
5074      * <p>The default line-break word style is
5075      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
5076      * no line-breaking word style is used.
5077      *
5078      * <p>See the
5079      * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
5080      * word-break property</a> for more information.
5081      *
5082      * @param lineBreakWordStyle The line-break word style for the text.
5083      */
setLineBreakWordStyle(@ineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)5084     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
5085         if (mLineBreakWordStyle != lineBreakWordStyle) {
5086             mLineBreakWordStyle = lineBreakWordStyle;
5087             if (mLayout != null) {
5088                 nullLayouts();
5089                 requestLayout();
5090                 invalidate();
5091             }
5092         }
5093     }
5094 
5095     /**
5096      * Gets the current line-break style for text wrapping.
5097      *
5098      * @return The line-break style to be used for text wrapping.
5099      */
getLineBreakStyle()5100     public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
5101         return mLineBreakStyle;
5102     }
5103 
5104     /**
5105      * Gets the current line-break word style for text wrapping.
5106      *
5107      * @return The line-break word style to be used for text wrapping.
5108      */
getLineBreakWordStyle()5109     public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
5110         return mLineBreakWordStyle;
5111     }
5112 
5113     /**
5114      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
5115      *
5116      * @return a current {@link PrecomputedText.Params}
5117      * @see PrecomputedText
5118      */
getTextMetricsParams()5119     public @NonNull PrecomputedText.Params getTextMetricsParams() {
5120         return new PrecomputedText.Params(new TextPaint(mTextPaint),
5121                 LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
5122                 getTextDirectionHeuristic(),
5123                 mBreakStrategy, mHyphenationFrequency);
5124     }
5125 
5126     /**
5127      * Apply the text layout parameter.
5128      *
5129      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
5130      * @see PrecomputedText
5131      */
setTextMetricsParams(@onNull PrecomputedText.Params params)5132     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
5133         mTextPaint.set(params.getTextPaint());
5134         mUserSetTextScaleX = true;
5135         mTextDir = params.getTextDirection();
5136         mBreakStrategy = params.getBreakStrategy();
5137         mHyphenationFrequency = params.getHyphenationFrequency();
5138         LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
5139         mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
5140         mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
5141         if (mLayout != null) {
5142             nullLayouts();
5143             requestLayout();
5144             invalidate();
5145         }
5146     }
5147 
5148     /**
5149      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
5150      * last line is too short for justification, the last line will be displayed with the
5151      * alignment set by {@link android.view.View#setTextAlignment}.
5152      *
5153      * @see #getJustificationMode()
5154      */
5155     @Layout.JustificationMode
5156     @android.view.RemotableViewMethod
setJustificationMode(@ayout.JustificationMode int justificationMode)5157     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
5158         mJustificationMode = justificationMode;
5159         if (mLayout != null) {
5160             nullLayouts();
5161             requestLayout();
5162             invalidate();
5163         }
5164     }
5165 
5166     /**
5167      * @return true if currently paragraph justification mode.
5168      *
5169      * @see #setJustificationMode(int)
5170      */
5171     @InspectableProperty(enumMapping = {
5172             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
5173             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
5174     })
getJustificationMode()5175     public @Layout.JustificationMode int getJustificationMode() {
5176         return mJustificationMode;
5177     }
5178 
5179     /**
5180      * Sets font feature settings. The format is the same as the CSS
5181      * font-feature-settings attribute:
5182      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
5183      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
5184      *
5185      * @param fontFeatureSettings font feature settings represented as CSS compatible string
5186      *
5187      * @see #getFontFeatureSettings()
5188      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
5189      *
5190      * @attr ref android.R.styleable#TextView_fontFeatureSettings
5191      */
5192     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)5193     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
5194         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
5195             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
5196 
5197             if (mLayout != null) {
5198                 nullLayouts();
5199                 requestLayout();
5200                 invalidate();
5201             }
5202         }
5203     }
5204 
5205 
5206     /**
5207      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
5208      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
5209      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
5210      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
5211      * are invalid. If a specified axis name is not defined in the font, the settings will be
5212      * ignored.
5213      *
5214      * <p>
5215      * Examples,
5216      * <ul>
5217      * <li>Set font width to 150.
5218      * <pre>
5219      * <code>
5220      *   TextView textView = (TextView) findViewById(R.id.textView);
5221      *   textView.setFontVariationSettings("'wdth' 150");
5222      * </code>
5223      * </pre>
5224      * </li>
5225      *
5226      * <li>Set the font slant to 20 degrees and ask for italic style.
5227      * <pre>
5228      * <code>
5229      *   TextView textView = (TextView) findViewById(R.id.textView);
5230      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
5231      * </code>
5232      * </pre>
5233      * </p>
5234      * </li>
5235      * </ul>
5236      *
5237      * @param fontVariationSettings font variation settings. You can pass null or empty string as
5238      *                              no variation settings.
5239      * @return true if the given settings is effective to at least one font file underlying this
5240      *         TextView. This function also returns true for empty settings string. Otherwise
5241      *         returns false.
5242      *
5243      * @throws IllegalArgumentException If given string is not a valid font variation settings
5244      *                                  format.
5245      *
5246      * @see #getFontVariationSettings()
5247      * @see FontVariationAxis
5248      *
5249      * @attr ref android.R.styleable#TextView_fontVariationSettings
5250      */
setFontVariationSettings(@ullable String fontVariationSettings)5251     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
5252         final String existingSettings = mTextPaint.getFontVariationSettings();
5253         if (fontVariationSettings == existingSettings
5254                 || (fontVariationSettings != null
5255                         && fontVariationSettings.equals(existingSettings))) {
5256             return true;
5257         }
5258         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
5259 
5260         if (effective && mLayout != null) {
5261             nullLayouts();
5262             requestLayout();
5263             invalidate();
5264         }
5265         return effective;
5266     }
5267 
5268     /**
5269      * Sets the text color for all the states (normal, selected,
5270      * focused) to be this color.
5271      *
5272      * @param color A color value in the form 0xAARRGGBB.
5273      * Do not pass a resource ID. To get a color value from a resource ID, call
5274      * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}.
5275      *
5276      * @see #setTextColor(ColorStateList)
5277      * @see #getTextColors()
5278      *
5279      * @attr ref android.R.styleable#TextView_textColor
5280      */
5281     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)5282     public void setTextColor(@ColorInt int color) {
5283         mTextColor = ColorStateList.valueOf(color);
5284         updateTextColors();
5285     }
5286 
5287     /**
5288      * Sets the text color.
5289      *
5290      * @see #setTextColor(int)
5291      * @see #getTextColors()
5292      * @see #setHintTextColor(ColorStateList)
5293      * @see #setLinkTextColor(ColorStateList)
5294      *
5295      * @attr ref android.R.styleable#TextView_textColor
5296      */
5297     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)5298     public void setTextColor(ColorStateList colors) {
5299         if (colors == null) {
5300             throw new NullPointerException();
5301         }
5302 
5303         mTextColor = colors;
5304         updateTextColors();
5305     }
5306 
5307     /**
5308      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
5309      *
5310      * @see #setTextColor(ColorStateList)
5311      * @see #setTextColor(int)
5312      *
5313      * @attr ref android.R.styleable#TextView_textColor
5314      */
5315     @InspectableProperty(name = "textColor")
getTextColors()5316     public final ColorStateList getTextColors() {
5317         return mTextColor;
5318     }
5319 
5320     /**
5321      * Return the current color selected for normal text.
5322      *
5323      * @return Returns the current text color.
5324      */
5325     @ColorInt
getCurrentTextColor()5326     public final int getCurrentTextColor() {
5327         return mCurTextColor;
5328     }
5329 
5330     /**
5331      * Sets the color used to display the selection highlight.
5332      *
5333      * @attr ref android.R.styleable#TextView_textColorHighlight
5334      */
5335     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)5336     public void setHighlightColor(@ColorInt int color) {
5337         if (mHighlightColor != color) {
5338             mHighlightColor = color;
5339             invalidate();
5340         }
5341     }
5342 
5343     /**
5344      * @return the color used to display the selection highlight
5345      *
5346      * @see #setHighlightColor(int)
5347      *
5348      * @attr ref android.R.styleable#TextView_textColorHighlight
5349      */
5350     @InspectableProperty(name = "textColorHighlight")
5351     @ColorInt
getHighlightColor()5352     public int getHighlightColor() {
5353         return mHighlightColor;
5354     }
5355 
5356     /**
5357      * Sets whether the soft input method will be made visible when this
5358      * TextView gets focused. The default is true.
5359      */
5360     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)5361     public final void setShowSoftInputOnFocus(boolean show) {
5362         createEditorIfNeeded();
5363         mEditor.mShowSoftInputOnFocus = show;
5364     }
5365 
5366     /**
5367      * Returns whether the soft input method will be made visible when this
5368      * TextView gets focused. The default is true.
5369      */
getShowSoftInputOnFocus()5370     public final boolean getShowSoftInputOnFocus() {
5371         // When there is no Editor, return default true value
5372         return mEditor == null || mEditor.mShowSoftInputOnFocus;
5373     }
5374 
5375     /**
5376      * Gives the text a shadow of the specified blur radius and color, the specified
5377      * distance from its drawn position.
5378      * <p>
5379      * The text shadow produced does not interact with the properties on view
5380      * that are responsible for real time shadows,
5381      * {@link View#getElevation() elevation} and
5382      * {@link View#getTranslationZ() translationZ}.
5383      *
5384      * @see Paint#setShadowLayer(float, float, float, int)
5385      *
5386      * @attr ref android.R.styleable#TextView_shadowColor
5387      * @attr ref android.R.styleable#TextView_shadowDx
5388      * @attr ref android.R.styleable#TextView_shadowDy
5389      * @attr ref android.R.styleable#TextView_shadowRadius
5390      */
setShadowLayer(float radius, float dx, float dy, int color)5391     public void setShadowLayer(float radius, float dx, float dy, int color) {
5392         mTextPaint.setShadowLayer(radius, dx, dy, color);
5393 
5394         mShadowRadius = radius;
5395         mShadowDx = dx;
5396         mShadowDy = dy;
5397         mShadowColor = color;
5398 
5399         // Will change text clip region
5400         if (mEditor != null) {
5401             mEditor.invalidateTextDisplayList();
5402             mEditor.invalidateHandlesAndActionMode();
5403         }
5404         invalidate();
5405     }
5406 
5407     /**
5408      * Gets the radius of the shadow layer.
5409      *
5410      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
5411      *
5412      * @see #setShadowLayer(float, float, float, int)
5413      *
5414      * @attr ref android.R.styleable#TextView_shadowRadius
5415      */
5416     @InspectableProperty
getShadowRadius()5417     public float getShadowRadius() {
5418         return mShadowRadius;
5419     }
5420 
5421     /**
5422      * @return the horizontal offset of the shadow layer
5423      *
5424      * @see #setShadowLayer(float, float, float, int)
5425      *
5426      * @attr ref android.R.styleable#TextView_shadowDx
5427      */
5428     @InspectableProperty
getShadowDx()5429     public float getShadowDx() {
5430         return mShadowDx;
5431     }
5432 
5433     /**
5434      * Gets the vertical offset of the shadow layer.
5435      * @return The vertical offset of the shadow layer.
5436      *
5437      * @see #setShadowLayer(float, float, float, int)
5438      *
5439      * @attr ref android.R.styleable#TextView_shadowDy
5440      */
5441     @InspectableProperty
getShadowDy()5442     public float getShadowDy() {
5443         return mShadowDy;
5444     }
5445 
5446     /**
5447      * Gets the color of the shadow layer.
5448      * @return the color of the shadow layer
5449      *
5450      * @see #setShadowLayer(float, float, float, int)
5451      *
5452      * @attr ref android.R.styleable#TextView_shadowColor
5453      */
5454     @InspectableProperty
5455     @ColorInt
getShadowColor()5456     public int getShadowColor() {
5457         return mShadowColor;
5458     }
5459 
5460     /**
5461      * Gets the {@link TextPaint} used for the text.
5462      * Use this only to consult the Paint's properties and not to change them.
5463      * @return The base paint used for the text.
5464      */
getPaint()5465     public TextPaint getPaint() {
5466         return mTextPaint;
5467     }
5468 
5469     /**
5470      * Sets the autolink mask of the text.  See {@link
5471      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
5472      * possible values.
5473      *
5474      * <p class="note"><b>Note:</b>
5475      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
5476      * is deprecated and should be avoided; see its documentation.
5477      *
5478      * @attr ref android.R.styleable#TextView_autoLink
5479      */
5480     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)5481     public final void setAutoLinkMask(int mask) {
5482         mAutoLinkMask = mask;
5483     }
5484 
5485     /**
5486      * Sets whether the movement method will automatically be set to
5487      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5488      * set to nonzero and links are detected in {@link #setText}.
5489      * The default is true.
5490      *
5491      * @attr ref android.R.styleable#TextView_linksClickable
5492      */
5493     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)5494     public final void setLinksClickable(boolean whether) {
5495         mLinksClickable = whether;
5496     }
5497 
5498     /**
5499      * Returns whether the movement method will automatically be set to
5500      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5501      * set to nonzero and links are detected in {@link #setText}.
5502      * The default is true.
5503      *
5504      * @attr ref android.R.styleable#TextView_linksClickable
5505      */
5506     @InspectableProperty
getLinksClickable()5507     public final boolean getLinksClickable() {
5508         return mLinksClickable;
5509     }
5510 
5511     /**
5512      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5513      * (by {@link Linkify} or otherwise) if any.  You can call
5514      * {@link URLSpan#getURL} on them to find where they link to
5515      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5516      * to find the region of the text they are attached to.
5517      */
getUrls()5518     public URLSpan[] getUrls() {
5519         if (mText instanceof Spanned) {
5520             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5521         } else {
5522             return new URLSpan[0];
5523         }
5524     }
5525 
5526     /**
5527      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5528      * TextView.
5529      *
5530      * @see #setHintTextColor(ColorStateList)
5531      * @see #getHintTextColors()
5532      * @see #setTextColor(int)
5533      *
5534      * @attr ref android.R.styleable#TextView_textColorHint
5535      */
5536     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)5537     public final void setHintTextColor(@ColorInt int color) {
5538         mHintTextColor = ColorStateList.valueOf(color);
5539         updateTextColors();
5540     }
5541 
5542     /**
5543      * Sets the color of the hint text.
5544      *
5545      * @see #getHintTextColors()
5546      * @see #setHintTextColor(int)
5547      * @see #setTextColor(ColorStateList)
5548      * @see #setLinkTextColor(ColorStateList)
5549      *
5550      * @attr ref android.R.styleable#TextView_textColorHint
5551      */
setHintTextColor(ColorStateList colors)5552     public final void setHintTextColor(ColorStateList colors) {
5553         mHintTextColor = colors;
5554         updateTextColors();
5555     }
5556 
5557     /**
5558      * @return the color of the hint text, for the different states of this TextView.
5559      *
5560      * @see #setHintTextColor(ColorStateList)
5561      * @see #setHintTextColor(int)
5562      * @see #setTextColor(ColorStateList)
5563      * @see #setLinkTextColor(ColorStateList)
5564      *
5565      * @attr ref android.R.styleable#TextView_textColorHint
5566      */
5567     @InspectableProperty(name = "textColorHint")
getHintTextColors()5568     public final ColorStateList getHintTextColors() {
5569         return mHintTextColor;
5570     }
5571 
5572     /**
5573      * <p>Return the current color selected to paint the hint text.</p>
5574      *
5575      * @return Returns the current hint text color.
5576      */
5577     @ColorInt
getCurrentHintTextColor()5578     public final int getCurrentHintTextColor() {
5579         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5580     }
5581 
5582     /**
5583      * Sets the color of links in the text.
5584      *
5585      * @see #setLinkTextColor(ColorStateList)
5586      * @see #getLinkTextColors()
5587      *
5588      * @attr ref android.R.styleable#TextView_textColorLink
5589      */
5590     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)5591     public final void setLinkTextColor(@ColorInt int color) {
5592         mLinkTextColor = ColorStateList.valueOf(color);
5593         updateTextColors();
5594     }
5595 
5596     /**
5597      * Sets the color of links in the text.
5598      *
5599      * @see #setLinkTextColor(int)
5600      * @see #getLinkTextColors()
5601      * @see #setTextColor(ColorStateList)
5602      * @see #setHintTextColor(ColorStateList)
5603      *
5604      * @attr ref android.R.styleable#TextView_textColorLink
5605      */
setLinkTextColor(ColorStateList colors)5606     public final void setLinkTextColor(ColorStateList colors) {
5607         mLinkTextColor = colors;
5608         updateTextColors();
5609     }
5610 
5611     /**
5612      * @return the list of colors used to paint the links in the text, for the different states of
5613      * this TextView
5614      *
5615      * @see #setLinkTextColor(ColorStateList)
5616      * @see #setLinkTextColor(int)
5617      *
5618      * @attr ref android.R.styleable#TextView_textColorLink
5619      */
5620     @InspectableProperty(name = "textColorLink")
getLinkTextColors()5621     public final ColorStateList getLinkTextColors() {
5622         return mLinkTextColor;
5623     }
5624 
5625     /**
5626      * Sets the horizontal alignment of the text and the
5627      * vertical gravity that will be used when there is extra space
5628      * in the TextView beyond what is required for the text itself.
5629      *
5630      * @see android.view.Gravity
5631      * @attr ref android.R.styleable#TextView_gravity
5632      */
5633     @android.view.RemotableViewMethod
setGravity(int gravity)5634     public void setGravity(int gravity) {
5635         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5636             gravity |= Gravity.START;
5637         }
5638         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5639             gravity |= Gravity.TOP;
5640         }
5641 
5642         boolean newLayout = false;
5643 
5644         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5645                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5646             newLayout = true;
5647         }
5648 
5649         if (gravity != mGravity) {
5650             invalidate();
5651         }
5652 
5653         mGravity = gravity;
5654 
5655         if (mLayout != null && newLayout) {
5656             // XXX this is heavy-handed because no actual content changes.
5657             int want = mLayout.getWidth();
5658             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5659 
5660             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5661                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5662         }
5663     }
5664 
5665     /**
5666      * Returns the horizontal and vertical alignment of this TextView.
5667      *
5668      * @see android.view.Gravity
5669      * @attr ref android.R.styleable#TextView_gravity
5670      */
5671     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()5672     public int getGravity() {
5673         return mGravity;
5674     }
5675 
5676     /**
5677      * Gets the flags on the Paint being used to display the text.
5678      * @return The flags on the Paint being used to display the text.
5679      * @see Paint#getFlags
5680      */
getPaintFlags()5681     public int getPaintFlags() {
5682         return mTextPaint.getFlags();
5683     }
5684 
5685     /**
5686      * Sets flags on the Paint being used to display the text and
5687      * reflows the text if they are different from the old flags.
5688      * @see Paint#setFlags
5689      */
5690     @android.view.RemotableViewMethod
setPaintFlags(int flags)5691     public void setPaintFlags(int flags) {
5692         if (mTextPaint.getFlags() != flags) {
5693             mTextPaint.setFlags(flags);
5694 
5695             if (mLayout != null) {
5696                 nullLayouts();
5697                 requestLayout();
5698                 invalidate();
5699             }
5700         }
5701     }
5702 
5703     /**
5704      * Sets whether the text should be allowed to be wider than the
5705      * View is.  If false, it will be wrapped to the width of the View.
5706      *
5707      * @attr ref android.R.styleable#TextView_scrollHorizontally
5708      */
setHorizontallyScrolling(boolean whether)5709     public void setHorizontallyScrolling(boolean whether) {
5710         if (mHorizontallyScrolling != whether) {
5711             mHorizontallyScrolling = whether;
5712 
5713             if (mLayout != null) {
5714                 nullLayouts();
5715                 requestLayout();
5716                 invalidate();
5717             }
5718         }
5719     }
5720 
5721     /**
5722      * Returns whether the text is allowed to be wider than the View.
5723      * If false, the text will be wrapped to the width of the View.
5724      *
5725      * @attr ref android.R.styleable#TextView_scrollHorizontally
5726      * @see #setHorizontallyScrolling(boolean)
5727      */
5728     @InspectableProperty(name = "scrollHorizontally")
isHorizontallyScrollable()5729     public final boolean isHorizontallyScrollable() {
5730         return mHorizontallyScrolling;
5731     }
5732 
5733     /**
5734      * Returns whether the text is allowed to be wider than the View.
5735      * If false, the text will be wrapped to the width of the View.
5736      *
5737      * @attr ref android.R.styleable#TextView_scrollHorizontally
5738      * @hide
5739      */
5740     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
getHorizontallyScrolling()5741     public boolean getHorizontallyScrolling() {
5742         return mHorizontallyScrolling;
5743     }
5744 
5745     /**
5746      * Sets the height of the TextView to be at least {@code minLines} tall.
5747      * <p>
5748      * This value is used for height calculation if LayoutParams does not force TextView to have an
5749      * exact height. Setting this value overrides other previous minimum height configurations such
5750      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
5751      * this value to 1.
5752      *
5753      * @param minLines the minimum height of TextView in terms of number of lines
5754      *
5755      * @see #getMinLines()
5756      * @see #setLines(int)
5757      *
5758      * @attr ref android.R.styleable#TextView_minLines
5759      */
5760     @android.view.RemotableViewMethod
setMinLines(int minLines)5761     public void setMinLines(int minLines) {
5762         mMinimum = minLines;
5763         mMinMode = LINES;
5764 
5765         requestLayout();
5766         invalidate();
5767     }
5768 
5769     /**
5770      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
5771      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
5772      *
5773      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
5774      *         height is not defined in lines
5775      *
5776      * @see #setMinLines(int)
5777      * @see #setLines(int)
5778      *
5779      * @attr ref android.R.styleable#TextView_minLines
5780      */
5781     @InspectableProperty
getMinLines()5782     public int getMinLines() {
5783         return mMinMode == LINES ? mMinimum : -1;
5784     }
5785 
5786     /**
5787      * Sets the height of the TextView to be at least {@code minPixels} tall.
5788      * <p>
5789      * This value is used for height calculation if LayoutParams does not force TextView to have an
5790      * exact height. Setting this value overrides previous minimum height configurations such as
5791      * {@link #setMinLines(int)} or {@link #setLines(int)}.
5792      * <p>
5793      * The value given here is different than {@link #setMinimumHeight(int)}. Between
5794      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
5795      * used to decide the final height.
5796      *
5797      * @param minPixels the minimum height of TextView in terms of pixels
5798      *
5799      * @see #getMinHeight()
5800      * @see #setHeight(int)
5801      *
5802      * @attr ref android.R.styleable#TextView_minHeight
5803      */
5804     @android.view.RemotableViewMethod
setMinHeight(int minPixels)5805     public void setMinHeight(int minPixels) {
5806         mMinimum = minPixels;
5807         mMinMode = PIXELS;
5808 
5809         requestLayout();
5810         invalidate();
5811     }
5812 
5813     /**
5814      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
5815      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
5816      *
5817      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
5818      *         defined in pixels
5819      *
5820      * @see #setMinHeight(int)
5821      * @see #setHeight(int)
5822      *
5823      * @attr ref android.R.styleable#TextView_minHeight
5824      */
getMinHeight()5825     public int getMinHeight() {
5826         return mMinMode == PIXELS ? mMinimum : -1;
5827     }
5828 
5829     /**
5830      * Sets the height of the TextView to be at most {@code maxLines} tall.
5831      * <p>
5832      * This value is used for height calculation if LayoutParams does not force TextView to have an
5833      * exact height. Setting this value overrides previous maximum height configurations such as
5834      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
5835      *
5836      * @param maxLines the maximum height of TextView in terms of number of lines
5837      *
5838      * @see #getMaxLines()
5839      * @see #setLines(int)
5840      *
5841      * @attr ref android.R.styleable#TextView_maxLines
5842      */
5843     @android.view.RemotableViewMethod
setMaxLines(int maxLines)5844     public void setMaxLines(int maxLines) {
5845         mMaximum = maxLines;
5846         mMaxMode = LINES;
5847 
5848         requestLayout();
5849         invalidate();
5850     }
5851 
5852     /**
5853      * Returns the maximum height of TextView in terms of number of lines or -1 if the
5854      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
5855      *
5856      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
5857      *         is not defined in lines.
5858      *
5859      * @see #setMaxLines(int)
5860      * @see #setLines(int)
5861      *
5862      * @attr ref android.R.styleable#TextView_maxLines
5863      */
5864     @InspectableProperty
getMaxLines()5865     public int getMaxLines() {
5866         return mMaxMode == LINES ? mMaximum : -1;
5867     }
5868 
5869     /**
5870      * Sets the height of the TextView to be at most {@code maxPixels} tall.
5871      * <p>
5872      * This value is used for height calculation if LayoutParams does not force TextView to have an
5873      * exact height. Setting this value overrides previous maximum height configurations such as
5874      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
5875      *
5876      * @param maxPixels the maximum height of TextView in terms of pixels
5877      *
5878      * @see #getMaxHeight()
5879      * @see #setHeight(int)
5880      *
5881      * @attr ref android.R.styleable#TextView_maxHeight
5882      */
5883     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)5884     public void setMaxHeight(int maxPixels) {
5885         mMaximum = maxPixels;
5886         mMaxMode = PIXELS;
5887 
5888         requestLayout();
5889         invalidate();
5890     }
5891 
5892     /**
5893      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
5894      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
5895      *
5896      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
5897      *         is not defined in pixels
5898      *
5899      * @see #setMaxHeight(int)
5900      * @see #setHeight(int)
5901      *
5902      * @attr ref android.R.styleable#TextView_maxHeight
5903      */
5904     @InspectableProperty
getMaxHeight()5905     public int getMaxHeight() {
5906         return mMaxMode == PIXELS ? mMaximum : -1;
5907     }
5908 
5909     /**
5910      * Sets the height of the TextView to be exactly {@code lines} tall.
5911      * <p>
5912      * This value is used for height calculation if LayoutParams does not force TextView to have an
5913      * exact height. Setting this value overrides previous minimum/maximum height configurations
5914      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
5915      * set this value to 1.
5916      *
5917      * @param lines the exact height of the TextView in terms of lines
5918      *
5919      * @see #setHeight(int)
5920      *
5921      * @attr ref android.R.styleable#TextView_lines
5922      */
5923     @android.view.RemotableViewMethod
setLines(int lines)5924     public void setLines(int lines) {
5925         mMaximum = mMinimum = lines;
5926         mMaxMode = mMinMode = LINES;
5927 
5928         requestLayout();
5929         invalidate();
5930     }
5931 
5932     /**
5933      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
5934      * <p>
5935      * This value is used for height calculation if LayoutParams does not force TextView to have an
5936      * exact height. Setting this value overrides previous minimum/maximum height configurations
5937      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
5938      *
5939      * @param pixels the exact height of the TextView in terms of pixels
5940      *
5941      * @see #setLines(int)
5942      *
5943      * @attr ref android.R.styleable#TextView_height
5944      */
5945     @android.view.RemotableViewMethod
setHeight(int pixels)5946     public void setHeight(int pixels) {
5947         mMaximum = mMinimum = pixels;
5948         mMaxMode = mMinMode = PIXELS;
5949 
5950         requestLayout();
5951         invalidate();
5952     }
5953 
5954     /**
5955      * Sets the width of the TextView to be at least {@code minEms} wide.
5956      * <p>
5957      * This value is used for width calculation if LayoutParams does not force TextView to have an
5958      * exact width. Setting this value overrides previous minimum width configurations such as
5959      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5960      *
5961      * @param minEms the minimum width of TextView in terms of ems
5962      *
5963      * @see #getMinEms()
5964      * @see #setEms(int)
5965      *
5966      * @attr ref android.R.styleable#TextView_minEms
5967      */
5968     @android.view.RemotableViewMethod
setMinEms(int minEms)5969     public void setMinEms(int minEms) {
5970         mMinWidth = minEms;
5971         mMinWidthMode = EMS;
5972 
5973         requestLayout();
5974         invalidate();
5975     }
5976 
5977     /**
5978      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
5979      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5980      *
5981      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
5982      *         defined in ems
5983      *
5984      * @see #setMinEms(int)
5985      * @see #setEms(int)
5986      *
5987      * @attr ref android.R.styleable#TextView_minEms
5988      */
5989     @InspectableProperty
getMinEms()5990     public int getMinEms() {
5991         return mMinWidthMode == EMS ? mMinWidth : -1;
5992     }
5993 
5994     /**
5995      * Sets the width of the TextView to be at least {@code minPixels} wide.
5996      * <p>
5997      * This value is used for width calculation if LayoutParams does not force TextView to have an
5998      * exact width. Setting this value overrides previous minimum width configurations such as
5999      * {@link #setMinEms(int)} or {@link #setEms(int)}.
6000      * <p>
6001      * The value given here is different than {@link #setMinimumWidth(int)}. Between
6002      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
6003      * to decide the final width.
6004      *
6005      * @param minPixels the minimum width of TextView in terms of pixels
6006      *
6007      * @see #getMinWidth()
6008      * @see #setWidth(int)
6009      *
6010      * @attr ref android.R.styleable#TextView_minWidth
6011      */
6012     @android.view.RemotableViewMethod
setMinWidth(int minPixels)6013     public void setMinWidth(int minPixels) {
6014         mMinWidth = minPixels;
6015         mMinWidthMode = PIXELS;
6016 
6017         requestLayout();
6018         invalidate();
6019     }
6020 
6021     /**
6022      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
6023      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
6024      *
6025      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
6026      *         defined in pixels
6027      *
6028      * @see #setMinWidth(int)
6029      * @see #setWidth(int)
6030      *
6031      * @attr ref android.R.styleable#TextView_minWidth
6032      */
6033     @InspectableProperty
getMinWidth()6034     public int getMinWidth() {
6035         return mMinWidthMode == PIXELS ? mMinWidth : -1;
6036     }
6037 
6038     /**
6039      * Sets the width of the TextView to be at most {@code maxEms} wide.
6040      * <p>
6041      * This value is used for width calculation if LayoutParams does not force TextView to have an
6042      * exact width. Setting this value overrides previous maximum width configurations such as
6043      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
6044      *
6045      * @param maxEms the maximum width of TextView in terms of ems
6046      *
6047      * @see #getMaxEms()
6048      * @see #setEms(int)
6049      *
6050      * @attr ref android.R.styleable#TextView_maxEms
6051      */
6052     @android.view.RemotableViewMethod
setMaxEms(int maxEms)6053     public void setMaxEms(int maxEms) {
6054         mMaxWidth = maxEms;
6055         mMaxWidthMode = EMS;
6056 
6057         requestLayout();
6058         invalidate();
6059     }
6060 
6061     /**
6062      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
6063      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
6064      *
6065      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
6066      *         defined in ems
6067      *
6068      * @see #setMaxEms(int)
6069      * @see #setEms(int)
6070      *
6071      * @attr ref android.R.styleable#TextView_maxEms
6072      */
6073     @InspectableProperty
getMaxEms()6074     public int getMaxEms() {
6075         return mMaxWidthMode == EMS ? mMaxWidth : -1;
6076     }
6077 
6078     /**
6079      * Sets the width of the TextView to be at most {@code maxPixels} wide.
6080      * <p>
6081      * This value is used for width calculation if LayoutParams does not force TextView to have an
6082      * exact width. Setting this value overrides previous maximum width configurations such as
6083      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
6084      *
6085      * @param maxPixels the maximum width of TextView in terms of pixels
6086      *
6087      * @see #getMaxWidth()
6088      * @see #setWidth(int)
6089      *
6090      * @attr ref android.R.styleable#TextView_maxWidth
6091      */
6092     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)6093     public void setMaxWidth(int maxPixels) {
6094         mMaxWidth = maxPixels;
6095         mMaxWidthMode = PIXELS;
6096 
6097         requestLayout();
6098         invalidate();
6099     }
6100 
6101     /**
6102      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
6103      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
6104      *
6105      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
6106      *         defined in pixels
6107      *
6108      * @see #setMaxWidth(int)
6109      * @see #setWidth(int)
6110      *
6111      * @attr ref android.R.styleable#TextView_maxWidth
6112      */
6113     @InspectableProperty
getMaxWidth()6114     public int getMaxWidth() {
6115         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
6116     }
6117 
6118     /**
6119      * Sets the width of the TextView to be exactly {@code ems} wide.
6120      *
6121      * This value is used for width calculation if LayoutParams does not force TextView to have an
6122      * exact width. Setting this value overrides previous minimum/maximum configurations such as
6123      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
6124      *
6125      * @param ems the exact width of the TextView in terms of ems
6126      *
6127      * @see #setWidth(int)
6128      *
6129      * @attr ref android.R.styleable#TextView_ems
6130      */
6131     @android.view.RemotableViewMethod
setEms(int ems)6132     public void setEms(int ems) {
6133         mMaxWidth = mMinWidth = ems;
6134         mMaxWidthMode = mMinWidthMode = EMS;
6135 
6136         requestLayout();
6137         invalidate();
6138     }
6139 
6140     /**
6141      * Sets the width of the TextView to be exactly {@code pixels} wide.
6142      * <p>
6143      * This value is used for width calculation if LayoutParams does not force TextView to have an
6144      * exact width. Setting this value overrides previous minimum/maximum width configurations
6145      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
6146      *
6147      * @param pixels the exact width of the TextView in terms of pixels
6148      *
6149      * @see #setEms(int)
6150      *
6151      * @attr ref android.R.styleable#TextView_width
6152      */
6153     @android.view.RemotableViewMethod
setWidth(int pixels)6154     public void setWidth(int pixels) {
6155         mMaxWidth = mMinWidth = pixels;
6156         mMaxWidthMode = mMinWidthMode = PIXELS;
6157 
6158         requestLayout();
6159         invalidate();
6160     }
6161 
6162     /**
6163      * Sets line spacing for this TextView.  Each line other than the last line will have its height
6164      * multiplied by {@code mult} and have {@code add} added to it.
6165      *
6166      * @param add The value in pixels that should be added to each line other than the last line.
6167      *            This will be applied after the multiplier
6168      * @param mult The value by which each line height other than the last line will be multiplied
6169      *             by
6170      *
6171      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6172      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6173      */
setLineSpacing(float add, float mult)6174     public void setLineSpacing(float add, float mult) {
6175         if (mSpacingAdd != add || mSpacingMult != mult) {
6176             mSpacingAdd = add;
6177             mSpacingMult = mult;
6178 
6179             if (mLayout != null) {
6180                 nullLayouts();
6181                 requestLayout();
6182                 invalidate();
6183             }
6184         }
6185     }
6186 
6187     /**
6188      * Gets the line spacing multiplier
6189      *
6190      * @return the value by which each line's height is multiplied to get its actual height.
6191      *
6192      * @see #setLineSpacing(float, float)
6193      * @see #getLineSpacingExtra()
6194      *
6195      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6196      */
6197     @InspectableProperty
getLineSpacingMultiplier()6198     public float getLineSpacingMultiplier() {
6199         return mSpacingMult;
6200     }
6201 
6202     /**
6203      * Gets the line spacing extra space
6204      *
6205      * @return the extra space that is added to the height of each lines of this TextView.
6206      *
6207      * @see #setLineSpacing(float, float)
6208      * @see #getLineSpacingMultiplier()
6209      *
6210      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6211      */
6212     @InspectableProperty
getLineSpacingExtra()6213     public float getLineSpacingExtra() {
6214         return mSpacingAdd;
6215     }
6216 
6217     /**
6218      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
6219      * between subsequent baselines in the TextView.
6220      *
6221      * @param lineHeight the line height in pixels
6222      *
6223      * @see #setLineSpacing(float, float)
6224      * @see #getLineSpacingExtra()
6225      *
6226      * @attr ref android.R.styleable#TextView_lineHeight
6227      */
6228     @android.view.RemotableViewMethod
setLineHeight(@x @ntRangefrom = 0) int lineHeight)6229     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
6230         setLineHeightPx(lineHeight);
6231     }
6232 
setLineHeightPx(@x @loatRangefrom = 0) float lineHeight)6233     private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) {
6234         Preconditions.checkArgumentNonNegative(lineHeight,
6235                 "Expecting non-negative lineHeight while the input is " + lineHeight);
6236 
6237         final int fontHeight = getPaint().getFontMetricsInt(null);
6238         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
6239         // TODO(b/274974975): should this also check if lineSpacing needs to change?
6240         if (lineHeight != fontHeight) {
6241             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
6242             setLineSpacing(lineHeight - fontHeight, 1f);
6243 
6244             mLineHeightComplexDimen =
6245                         TypedValue.createComplexDimension(lineHeight, TypedValue.COMPLEX_UNIT_PX);
6246         }
6247     }
6248 
6249     /**
6250      * Sets an explicit line height to a given unit and value for this TextView. This is equivalent
6251      * to the vertical distance between subsequent baselines in the TextView. See {@link
6252      * TypedValue} for the possible dimension units.
6253      *
6254      * @param unit The desired dimension unit. SP units are strongly recommended so that line height
6255      *             stays proportional to the text size when fonts are scaled up for accessibility.
6256      * @param lineHeight The desired line height in the given units.
6257      *
6258      * @see #setLineSpacing(float, float)
6259      * @see #getLineSpacingExtra()
6260      *
6261      * @attr ref android.R.styleable#TextView_lineHeight
6262      */
6263     @android.view.RemotableViewMethod
setLineHeight( @ypedValue.ComplexDimensionUnit int unit, @FloatRange(from = 0) float lineHeight )6264     public void setLineHeight(
6265             @TypedValue.ComplexDimensionUnit int unit,
6266             @FloatRange(from = 0) float lineHeight
6267     ) {
6268         var metrics = getDisplayMetricsOrSystem();
6269         // We can avoid the recalculation if we know non-linear font scaling isn't being used
6270         // (an optimization for the majority case).
6271         // We also don't try to do the recalculation unless both textSize and lineHeight are in SP.
6272         if (!FontScaleConverterFactory.isNonLinearFontScalingActive(
6273                     getResources().getConfiguration().fontScale)
6274                 || unit != TypedValue.COMPLEX_UNIT_SP
6275                 || mTextSizeUnit != TypedValue.COMPLEX_UNIT_SP
6276         ) {
6277             setLineHeightPx(TypedValue.applyDimension(unit, lineHeight, metrics));
6278 
6279             // Do this last so it overwrites what setLineHeightPx() sets it to.
6280             mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
6281             return;
6282         }
6283 
6284         // Recalculate a proportional line height when non-linear font scaling is in effect.
6285         // Otherwise, a desired 2x line height at font scale 1.0 will not be 2x at font scale 2.0,
6286         // due to non-linear font scaling compressing higher SP sizes. See b/273326061 for details.
6287         // We know they are using SP units for both the text size and the line height
6288         // at this point, so determine the ratio between them. This is the *intended* line spacing
6289         // multiplier if font scale == 1.0. We can then determine what the pixel value for the line
6290         // height would be if we preserved proportions.
6291         var textSizePx = getTextSize();
6292         var textSizeSp = TypedValue.convertPixelsToDimension(
6293                 TypedValue.COMPLEX_UNIT_SP,
6294                 textSizePx,
6295                 metrics
6296         );
6297         var ratio = lineHeight / textSizeSp;
6298         setLineHeightPx(textSizePx * ratio);
6299 
6300         // Do this last so it overwrites what setLineHeightPx() sets it to.
6301         mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
6302     }
6303 
maybeRecalculateLineHeight()6304     private void maybeRecalculateLineHeight() {
6305         if (mLineHeightComplexDimen == 0) {
6306             return;
6307         }
6308         int unit = TypedValue.getUnitFromComplexDimension(mLineHeightComplexDimen);
6309         if (unit != TypedValue.COMPLEX_UNIT_SP) {
6310             // The lineHeight was never supplied in SP, so we didn't do any fancy recalculations
6311             // in setLineHeight(). We don't need to recalculate.
6312             return;
6313         }
6314 
6315         setLineHeight(unit, TypedValue.complexToFloat(mLineHeightComplexDimen));
6316     }
6317 
6318     /**
6319      * Set Highlights
6320      *
6321      * @param highlights A highlight object. Call with null for reset.
6322      *
6323      * @see #getHighlights()
6324      * @see Highlights
6325      */
setHighlights(@ullable Highlights highlights)6326     public void setHighlights(@Nullable Highlights highlights) {
6327         mHighlights = highlights;
6328         mHighlightPathsBogus = true;
6329         invalidate();
6330     }
6331 
6332     /**
6333      * Returns highlights
6334      *
6335      * @return a highlight to be drawn. null if no highlight was set.
6336      *
6337      * @see #setHighlights(Highlights)
6338      * @see Highlights
6339      *
6340      */
6341     @Nullable
getHighlights()6342     public Highlights getHighlights() {
6343         return mHighlights;
6344     }
6345 
6346     /**
6347      * Sets the search result ranges with flatten range representation.
6348      *
6349      * Ranges are represented of flattened inclusive start and exclusive end integers array. The
6350      * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array.
6351      * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the
6352      * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array
6353      * [1, 2, 3, 4].
6354      *
6355      * TextView will render the search result with the highlights with specified color in the theme.
6356      * If there is a focused search result, it is rendered with focused color. By calling this
6357      * method, the focused search index will be cleared.
6358      *
6359      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6360      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6361      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6362      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6363      *
6364      * @see #getSearchResultHighlights()
6365      * @see #setFocusedSearchResultIndex(int)
6366      * @see #getFocusedSearchResultIndex()
6367      * @see #setSearchResultHighlightColor(int)
6368      * @see #getSearchResultHighlightColor()
6369      * @see #setFocusedSearchResultHighlightColor(int)
6370      * @see #getFocusedSearchResultHighlightColor()
6371      *
6372      * @param ranges the flatten ranges of the search result. null for clear.
6373      */
setSearchResultHighlights(@ullable int... ranges)6374     public void setSearchResultHighlights(@Nullable int... ranges) {
6375         if (ranges == null) {
6376             mSearchResultHighlights = null;
6377             mHighlightPathsBogus = true;
6378             return;
6379         }
6380         if (ranges.length % 2 == 1) {
6381             throw new IllegalArgumentException(
6382                     "Flatten ranges must have even numbered elements");
6383         }
6384         for (int j = 0; j < ranges.length / 2; ++j) {
6385             int start = ranges[j * 2];
6386             int end = ranges[j * 2 + 1];
6387             if (start > end) {
6388                 throw new IllegalArgumentException(
6389                         "Reverse range found in the flatten range: " + start + ", " + end + ""
6390                                 + " at " + j + "-th range");
6391             }
6392         }
6393         mHighlightPathsBogus = true;
6394         mSearchResultHighlights = ranges;
6395         mFocusedSearchResultIndex = FOCUSED_SEARCH_RESULT_INDEX_NONE;
6396         invalidate();
6397     }
6398 
6399     /**
6400      * Gets the current search result ranges.
6401      *
6402      * @see #setSearchResultHighlights(int[])
6403      * @see #setFocusedSearchResultIndex(int)
6404      * @see #getFocusedSearchResultIndex()
6405      * @see #setSearchResultHighlightColor(int)
6406      * @see #getSearchResultHighlightColor()
6407      * @see #setFocusedSearchResultHighlightColor(int)
6408      * @see #getFocusedSearchResultHighlightColor()
6409      *
6410      * @return a flatten search result ranges. null if not available.
6411      */
6412     @Nullable
getSearchResultHighlights()6413     public int[] getSearchResultHighlights() {
6414         return mSearchResultHighlights;
6415     }
6416 
6417     /**
6418      * A special index used for {@link #setFocusedSearchResultIndex(int)} and
6419      * {@link #getFocusedSearchResultIndex()} inidicating there is no focused search result.
6420      */
6421     public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1;
6422 
6423     /**
6424      * Sets the focused search result index.
6425      *
6426      * The focused search result is drawn in a focused color.
6427      * Calling {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} for clearing focused search result.
6428      *
6429      * This method must be called after setting search result ranges by
6430      * {@link #setSearchResultHighlights(int[])}.
6431      *
6432      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6433      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6434      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6435      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6436      *
6437      * @see #setSearchResultHighlights(int[])
6438      * @see #getSearchResultHighlights()
6439      * @see #setFocusedSearchResultIndex(int)
6440      * @see #getFocusedSearchResultIndex()
6441      * @see #setSearchResultHighlightColor(int)
6442      * @see #getSearchResultHighlightColor()
6443      * @see #setFocusedSearchResultHighlightColor(int)
6444      * @see #getFocusedSearchResultHighlightColor()
6445      *
6446      * @param index a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
6447      */
setFocusedSearchResultIndex(int index)6448     public void setFocusedSearchResultIndex(int index) {
6449         if (mSearchResultHighlights == null) {
6450             throw new IllegalArgumentException("Search result range must be set beforehand.");
6451         }
6452         if (index < -1 || index >= mSearchResultHighlights.length / 2) {
6453             throw new IllegalArgumentException("Focused index(" + index + ") must be larger than "
6454                     + "-1 and less than range count(" + (mSearchResultHighlights.length / 2) + ")");
6455         }
6456         mFocusedSearchResultIndex = index;
6457         mHighlightPathsBogus = true;
6458         invalidate();
6459     }
6460 
6461     /**
6462      * Gets the focused search result index.
6463      *
6464      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6465      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6466      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6467      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6468      *
6469      * @see #setSearchResultHighlights(int[])
6470      * @see #getSearchResultHighlights()
6471      * @see #setFocusedSearchResultIndex(int)
6472      * @see #getFocusedSearchResultIndex()
6473      * @see #setSearchResultHighlightColor(int)
6474      * @see #getSearchResultHighlightColor()
6475      * @see #setFocusedSearchResultHighlightColor(int)
6476      * @see #getFocusedSearchResultHighlightColor()
6477 
6478      * @return a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
6479      */
getFocusedSearchResultIndex()6480     public int getFocusedSearchResultIndex() {
6481         return mFocusedSearchResultIndex;
6482     }
6483 
6484     /**
6485      * Sets the search result highlight color.
6486      *
6487      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6488      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6489      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6490      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6491      *
6492      * @see #setSearchResultHighlights(int[])
6493      * @see #getSearchResultHighlights()
6494      * @see #setFocusedSearchResultIndex(int)
6495      * @see #getFocusedSearchResultIndex()
6496      * @see #setSearchResultHighlightColor(int)
6497      * @see #getSearchResultHighlightColor()
6498      * @see #setFocusedSearchResultHighlightColor(int)
6499      * @see #getFocusedSearchResultHighlightColor()
6500 
6501      * @param color a search result highlight color.
6502      */
setSearchResultHighlightColor(@olorInt int color)6503     public void setSearchResultHighlightColor(@ColorInt int color) {
6504         mSearchResultHighlightColor = color;
6505     }
6506 
6507     /**
6508      * Gets the search result highlight color.
6509      *
6510      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6511      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6512      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6513      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6514      *
6515      * @see #setSearchResultHighlights(int[])
6516      * @see #getSearchResultHighlights()
6517      * @see #setFocusedSearchResultIndex(int)
6518      * @see #getFocusedSearchResultIndex()
6519      * @see #setSearchResultHighlightColor(int)
6520      * @see #getSearchResultHighlightColor()
6521      * @see #setFocusedSearchResultHighlightColor(int)
6522      * @see #getFocusedSearchResultHighlightColor()
6523 
6524      * @return a search result highlight color.
6525      */
6526     @ColorInt
getSearchResultHighlightColor()6527     public int getSearchResultHighlightColor() {
6528         return mSearchResultHighlightColor;
6529     }
6530 
6531     /**
6532      * Sets focused search result highlight color.
6533      *
6534      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6535      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6536      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6537      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6538      *
6539      * @see #setSearchResultHighlights(int[])
6540      * @see #getSearchResultHighlights()
6541      * @see #setFocusedSearchResultIndex(int)
6542      * @see #getFocusedSearchResultIndex()
6543      * @see #setSearchResultHighlightColor(int)
6544      * @see #getSearchResultHighlightColor()
6545      * @see #setFocusedSearchResultHighlightColor(int)
6546      * @see #getFocusedSearchResultHighlightColor()
6547 
6548      * @param color a focused search result highlight color.
6549      */
setFocusedSearchResultHighlightColor(@olorInt int color)6550     public void setFocusedSearchResultHighlightColor(@ColorInt int color) {
6551         mFocusedSearchResultHighlightColor = color;
6552     }
6553 
6554     /**
6555      * Gets focused search result highlight color.
6556      *
6557      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6558      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6559      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6560      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6561      *
6562      * @see #setSearchResultHighlights(int[])
6563      * @see #getSearchResultHighlights()
6564      * @see #setFocusedSearchResultIndex(int)
6565      * @see #getFocusedSearchResultIndex()
6566      * @see #setSearchResultHighlightColor(int)
6567      * @see #getSearchResultHighlightColor()
6568      * @see #setFocusedSearchResultHighlightColor(int)
6569      * @see #getFocusedSearchResultHighlightColor()
6570 
6571      * @return a focused search result highlight color.
6572      */
6573     @ColorInt
getFocusedSearchResultHighlightColor()6574     public int getFocusedSearchResultHighlightColor() {
6575         return mFocusedSearchResultHighlightColor;
6576     }
6577 
6578     /**
6579      * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
6580      * will be selected by the ongoing select handwriting gesture. While the gesture preview
6581      * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
6582      * the gesture preview highlight will be cleared.
6583      */
setSelectGesturePreviewHighlight(int start, int end)6584     private void setSelectGesturePreviewHighlight(int start, int end) {
6585         // Selection preview highlight color is the same as selection highlight color.
6586         setGesturePreviewHighlight(start, end, mHighlightColor);
6587     }
6588 
6589     /**
6590      * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
6591      * will be deleted by the ongoing delete handwriting gesture. While the gesture preview
6592      * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
6593      * the gesture preview highlight will be cleared.
6594      */
setDeleteGesturePreviewHighlight(int start, int end)6595     private void setDeleteGesturePreviewHighlight(int start, int end) {
6596         // Deletion preview highlight color is 20% opacity of the default text color.
6597         int color = mTextColor.getDefaultColor();
6598         color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
6599         setGesturePreviewHighlight(start, end, color);
6600     }
6601 
setGesturePreviewHighlight(int start, int end, int color)6602     private void setGesturePreviewHighlight(int start, int end, int color) {
6603         mGesturePreviewHighlightStart = start;
6604         mGesturePreviewHighlightEnd = end;
6605         if (mGesturePreviewHighlightPaint == null) {
6606             mGesturePreviewHighlightPaint = new Paint();
6607             mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL);
6608         }
6609         mGesturePreviewHighlightPaint.setColor(color);
6610 
6611         if (mEditor != null) {
6612             mEditor.hideCursorAndSpanControllers();
6613             mEditor.stopTextActionModeWithPreservingSelection();
6614         }
6615 
6616         mHighlightPathsBogus = true;
6617         invalidate();
6618     }
6619 
clearGesturePreviewHighlight()6620     private void clearGesturePreviewHighlight() {
6621         mGesturePreviewHighlightStart = -1;
6622         mGesturePreviewHighlightEnd = -1;
6623         mHighlightPathsBogus = true;
6624         invalidate();
6625     }
6626 
hasGesturePreviewHighlight()6627     boolean hasGesturePreviewHighlight() {
6628         return mGesturePreviewHighlightStart >= 0;
6629     }
6630 
6631     /**
6632      * Convenience method to append the specified text to the TextView's
6633      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6634      * if it was not already editable.
6635      *
6636      * @param text text to be appended to the already displayed text
6637      */
append(CharSequence text)6638     public final void append(CharSequence text) {
6639         append(text, 0, text.length());
6640     }
6641 
6642     /**
6643      * Convenience method to append the specified text slice to the TextView's
6644      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6645      * if it was not already editable.
6646      *
6647      * @param text text to be appended to the already displayed text
6648      * @param start the index of the first character in the {@code text}
6649      * @param end the index of the character following the last character in the {@code text}
6650      *
6651      * @see Appendable#append(CharSequence, int, int)
6652      */
append(CharSequence text, int start, int end)6653     public void append(CharSequence text, int start, int end) {
6654         if (!(mText instanceof Editable)) {
6655             setText(mText, BufferType.EDITABLE);
6656         }
6657 
6658         ((Editable) mText).append(text, start, end);
6659 
6660         if (mAutoLinkMask != 0) {
6661             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
6662             // Do not change the movement method for text that support text selection as it
6663             // would prevent an arbitrary cursor displacement.
6664             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
6665                 setMovementMethod(LinkMovementMethod.getInstance());
6666             }
6667         }
6668     }
6669 
updateTextColors()6670     private void updateTextColors() {
6671         boolean inval = false;
6672         final int[] drawableState = getDrawableState();
6673         int color = mTextColor.getColorForState(drawableState, 0);
6674         if (color != mCurTextColor) {
6675             mCurTextColor = color;
6676             inval = true;
6677         }
6678         if (mLinkTextColor != null) {
6679             color = mLinkTextColor.getColorForState(drawableState, 0);
6680             if (color != mTextPaint.linkColor) {
6681                 mTextPaint.linkColor = color;
6682                 inval = true;
6683             }
6684         }
6685         if (mHintTextColor != null) {
6686             color = mHintTextColor.getColorForState(drawableState, 0);
6687             if (color != mCurHintTextColor) {
6688                 mCurHintTextColor = color;
6689                 if (mText.length() == 0) {
6690                     inval = true;
6691                 }
6692             }
6693         }
6694         if (inval) {
6695             // Text needs to be redrawn with the new color
6696             if (mEditor != null) mEditor.invalidateTextDisplayList();
6697             invalidate();
6698         }
6699     }
6700 
6701     @Override
drawableStateChanged()6702     protected void drawableStateChanged() {
6703         super.drawableStateChanged();
6704 
6705         if (mTextColor != null && mTextColor.isStateful()
6706                 || (mHintTextColor != null && mHintTextColor.isStateful())
6707                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
6708             updateTextColors();
6709         }
6710 
6711         if (mDrawables != null) {
6712             final int[] state = getDrawableState();
6713             for (Drawable dr : mDrawables.mShowing) {
6714                 if (dr != null && dr.isStateful() && dr.setState(state)) {
6715                     invalidateDrawable(dr);
6716                 }
6717             }
6718         }
6719     }
6720 
6721     @Override
drawableHotspotChanged(float x, float y)6722     public void drawableHotspotChanged(float x, float y) {
6723         super.drawableHotspotChanged(x, y);
6724 
6725         if (mDrawables != null) {
6726             for (Drawable dr : mDrawables.mShowing) {
6727                 if (dr != null) {
6728                     dr.setHotspot(x, y);
6729                 }
6730             }
6731         }
6732     }
6733 
6734     @Override
onSaveInstanceState()6735     public Parcelable onSaveInstanceState() {
6736         Parcelable superState = super.onSaveInstanceState();
6737 
6738         // Save state if we are forced to
6739         final boolean freezesText = getFreezesText();
6740         boolean hasSelection = false;
6741         int start = -1;
6742         int end = -1;
6743 
6744         if (mText != null) {
6745             start = getSelectionStart();
6746             end = getSelectionEnd();
6747             if (start >= 0 || end >= 0) {
6748                 // Or save state if there is a selection
6749                 hasSelection = true;
6750             }
6751         }
6752 
6753         if (freezesText || hasSelection) {
6754             SavedState ss = new SavedState(superState);
6755 
6756             if (freezesText) {
6757                 if (mText instanceof Spanned) {
6758                     final Spannable sp = new SpannableStringBuilder(mText);
6759 
6760                     if (mEditor != null) {
6761                         removeMisspelledSpans(sp);
6762                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
6763                     }
6764 
6765                     ss.text = sp;
6766                 } else {
6767                     ss.text = mText.toString();
6768                 }
6769             }
6770 
6771             if (hasSelection) {
6772                 // XXX Should also save the current scroll position!
6773                 ss.selStart = start;
6774                 ss.selEnd = end;
6775             }
6776 
6777             if (isFocused() && start >= 0 && end >= 0) {
6778                 ss.frozenWithFocus = true;
6779             }
6780 
6781             ss.error = getError();
6782 
6783             if (mEditor != null) {
6784                 ss.editorState = mEditor.saveInstanceState();
6785             }
6786             return ss;
6787         }
6788 
6789         return superState;
6790     }
6791 
removeMisspelledSpans(Spannable spannable)6792     void removeMisspelledSpans(Spannable spannable) {
6793         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
6794                 SuggestionSpan.class);
6795         for (int i = 0; i < suggestionSpans.length; i++) {
6796             int flags = suggestionSpans[i].getFlags();
6797             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
6798                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
6799                 spannable.removeSpan(suggestionSpans[i]);
6800             }
6801         }
6802     }
6803 
6804     @Override
onRestoreInstanceState(Parcelable state)6805     public void onRestoreInstanceState(Parcelable state) {
6806         if (!(state instanceof SavedState)) {
6807             super.onRestoreInstanceState(state);
6808             return;
6809         }
6810 
6811         SavedState ss = (SavedState) state;
6812         super.onRestoreInstanceState(ss.getSuperState());
6813 
6814         // XXX restore buffer type too, as well as lots of other stuff
6815         if (ss.text != null) {
6816             setText(ss.text);
6817         }
6818 
6819         if (ss.selStart >= 0 && ss.selEnd >= 0) {
6820             if (mSpannable != null) {
6821                 int len = mText.length();
6822 
6823                 if (ss.selStart > len || ss.selEnd > len) {
6824                     String restored = "";
6825 
6826                     if (ss.text != null) {
6827                         restored = "(restored) ";
6828                     }
6829 
6830                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
6831                             + " out of range for " + restored + "text " + mText);
6832                 } else {
6833                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
6834 
6835                     if (ss.frozenWithFocus) {
6836                         createEditorIfNeeded();
6837                         mEditor.mFrozenWithFocus = true;
6838                     }
6839                 }
6840             }
6841         }
6842 
6843         if (ss.error != null) {
6844             final CharSequence error = ss.error;
6845             // Display the error later, after the first layout pass
6846             post(new Runnable() {
6847                 public void run() {
6848                     if (mEditor == null || !mEditor.mErrorWasChanged) {
6849                         setError(error);
6850                     }
6851                 }
6852             });
6853         }
6854 
6855         if (ss.editorState != null) {
6856             createEditorIfNeeded();
6857             mEditor.restoreInstanceState(ss.editorState);
6858         }
6859     }
6860 
6861     /**
6862      * Control whether this text view saves its entire text contents when
6863      * freezing to an icicle, in addition to dynamic state such as cursor
6864      * position.  By default this is false, not saving the text.  Set to true
6865      * if the text in the text view is not being saved somewhere else in
6866      * persistent storage (such as in a content provider) so that if the
6867      * view is later thawed the user will not lose their data. For
6868      * {@link android.widget.EditText} it is always enabled, regardless of
6869      * the value of the attribute.
6870      *
6871      * @param freezesText Controls whether a frozen icicle should include the
6872      * entire text data: true to include it, false to not.
6873      *
6874      * @attr ref android.R.styleable#TextView_freezesText
6875      */
6876     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)6877     public void setFreezesText(boolean freezesText) {
6878         mFreezesText = freezesText;
6879     }
6880 
6881     /**
6882      * Return whether this text view is including its entire text contents
6883      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
6884      *
6885      * @return Returns true if text is included, false if it isn't.
6886      *
6887      * @see #setFreezesText
6888      */
6889     @InspectableProperty
getFreezesText()6890     public boolean getFreezesText() {
6891         return mFreezesText;
6892     }
6893 
6894     ///////////////////////////////////////////////////////////////////////////
6895 
6896     /**
6897      * Sets the Factory used to create new {@link Editable Editables}.
6898      *
6899      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
6900      *
6901      * @see android.text.Editable.Factory
6902      * @see android.widget.TextView.BufferType#EDITABLE
6903      */
setEditableFactory(Editable.Factory factory)6904     public final void setEditableFactory(Editable.Factory factory) {
6905         mEditableFactory = factory;
6906         setText(mText);
6907     }
6908 
6909     /**
6910      * Sets the Factory used to create new {@link Spannable Spannables}.
6911      *
6912      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
6913      *
6914      * @see android.text.Spannable.Factory
6915      * @see android.widget.TextView.BufferType#SPANNABLE
6916      */
setSpannableFactory(Spannable.Factory factory)6917     public final void setSpannableFactory(Spannable.Factory factory) {
6918         mSpannableFactory = factory;
6919         setText(mText);
6920     }
6921 
6922     /**
6923      * Sets the text to be displayed. TextView <em>does not</em> accept
6924      * HTML-like formatting, which you can do with text strings in XML resource files.
6925      * To style your strings, attach android.text.style.* objects to a
6926      * {@link android.text.SpannableString}, or see the
6927      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
6928      * Available Resource Types</a> documentation for an example of setting
6929      * formatted text in the XML resource file.
6930      * <p/>
6931      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6932      * intermediate {@link Spannable Spannables}. Likewise it will use
6933      * {@link android.text.Editable.Factory} to create final or intermediate
6934      * {@link Editable Editables}.
6935      *
6936      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
6937      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
6938      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
6939      *
6940      * @param text text to be displayed
6941      *
6942      * @attr ref android.R.styleable#TextView_text
6943      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
6944      *                                  parameters used to create the PrecomputedText mismatches
6945      *                                  with this TextView.
6946      */
6947     @android.view.RemotableViewMethod(asyncImpl = "setTextAsync")
setText(CharSequence text)6948     public final void setText(CharSequence text) {
6949         setText(text, mBufferType);
6950     }
6951 
6952     /**
6953      * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}.
6954      * This should be called on a background thread, and returns a Runnable which is then must be
6955      * called on the main thread to complete the operation and set text.
6956      * @param text text to be displayed
6957      * @return Runnable that sets text; must be called on the main thread by the caller of this
6958      * method to complete the operation
6959      * @hide
6960      */
6961     @NonNull
setTextAsync(@ullable CharSequence text)6962     public Runnable setTextAsync(@Nullable CharSequence text) {
6963         return () -> setText(text);
6964     }
6965 
6966     /**
6967      * Sets the text to be displayed but retains the cursor position. Same as
6968      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
6969      * new text.
6970      * <p/>
6971      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6972      * intermediate {@link Spannable Spannables}. Likewise it will use
6973      * {@link android.text.Editable.Factory} to create final or intermediate
6974      * {@link Editable Editables}.
6975      *
6976      * @param text text to be displayed
6977      *
6978      * @see #setText(CharSequence)
6979      */
6980     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)6981     public final void setTextKeepState(CharSequence text) {
6982         setTextKeepState(text, mBufferType);
6983     }
6984 
6985     /**
6986      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
6987      * <p/>
6988      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6989      * intermediate {@link Spannable Spannables}. Likewise it will use
6990      * {@link android.text.Editable.Factory} to create final or intermediate
6991      * {@link Editable Editables}.
6992      *
6993      * Subclasses overriding this method should ensure that the following post condition holds,
6994      * in order to guarantee the safety of the view's measurement and layout operations:
6995      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
6996      * will be different from {@code null}.
6997      *
6998      * @param text text to be displayed
6999      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7000      *              stored as a static text, styleable/spannable text, or editable text
7001      *
7002      * @see #setText(CharSequence)
7003      * @see android.widget.TextView.BufferType
7004      * @see #setSpannableFactory(Spannable.Factory)
7005      * @see #setEditableFactory(Editable.Factory)
7006      *
7007      * @attr ref android.R.styleable#TextView_text
7008      * @attr ref android.R.styleable#TextView_bufferType
7009      */
setText(CharSequence text, BufferType type)7010     public void setText(CharSequence text, BufferType type) {
7011         setText(text, type, true, 0);
7012 
7013         // drop any potential mCharWrappper leaks
7014         mCharWrapper = null;
7015     }
7016 
7017     @UnsupportedAppUsage
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)7018     private void setText(CharSequence text, BufferType type,
7019                          boolean notifyBefore, int oldlen) {
7020         if (mEditor != null) {
7021             mEditor.beforeSetText();
7022         }
7023         mTextSetFromXmlOrResourceId = false;
7024         if (text == null) {
7025             text = "";
7026         }
7027 
7028         // If suggestions are not enabled, remove the suggestion spans from the text
7029         if (!isSuggestionsEnabled()) {
7030             text = removeSuggestionSpans(text);
7031         }
7032 
7033         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
7034 
7035         if (text instanceof Spanned
7036                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
7037             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
7038                 setHorizontalFadingEdgeEnabled(true);
7039                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
7040             } else {
7041                 setHorizontalFadingEdgeEnabled(false);
7042                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7043             }
7044             setEllipsize(TextUtils.TruncateAt.MARQUEE);
7045         }
7046 
7047         int n = mFilters.length;
7048         for (int i = 0; i < n; i++) {
7049             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
7050             if (out != null) {
7051                 text = out;
7052             }
7053         }
7054 
7055         if (notifyBefore) {
7056             if (mText != null) {
7057                 oldlen = mText.length();
7058                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
7059             } else {
7060                 sendBeforeTextChanged("", 0, 0, text.length());
7061             }
7062         }
7063 
7064         boolean needEditableForNotification = false;
7065 
7066         if (mListeners != null && mListeners.size() != 0) {
7067             needEditableForNotification = true;
7068         }
7069 
7070         PrecomputedText precomputed =
7071                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
7072         if (type == BufferType.EDITABLE || getKeyListener() != null
7073                 || needEditableForNotification) {
7074             createEditorIfNeeded();
7075             mEditor.forgetUndoRedo();
7076             mEditor.scheduleRestartInputForSetText();
7077             Editable t = mEditableFactory.newEditable(text);
7078             text = t;
7079             setFilters(t, mFilters);
7080         } else if (precomputed != null) {
7081             if (mTextDir == null) {
7082                 mTextDir = getTextDirectionHeuristic();
7083             }
7084             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
7085                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
7086                             mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
7087                                     mLineBreakStyle, mLineBreakWordStyle));
7088             switch (checkResult) {
7089                 case PrecomputedText.Params.UNUSABLE:
7090                     throw new IllegalArgumentException(
7091                         "PrecomputedText's Parameters don't match the parameters of this TextView."
7092                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
7093                         + "to override the settings of this TextView: "
7094                         + "PrecomputedText: " + precomputed.getParams()
7095                         + "TextView: " + getTextMetricsParams());
7096                 case PrecomputedText.Params.NEED_RECOMPUTE:
7097                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
7098                     break;
7099                 case PrecomputedText.Params.USABLE:
7100                     // pass through
7101             }
7102         } else if (type == BufferType.SPANNABLE || mMovement != null) {
7103             text = mSpannableFactory.newSpannable(text);
7104         } else if (!(text instanceof CharWrapper)) {
7105             text = TextUtils.stringOrSpannedString(text);
7106         }
7107 
7108         @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE;
7109         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
7110             a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText);
7111         }
7112 
7113         if (mAutoLinkMask != 0) {
7114             Spannable s2;
7115 
7116             if (type == BufferType.EDITABLE || text instanceof Spannable) {
7117                 s2 = (Spannable) text;
7118             } else {
7119                 s2 = mSpannableFactory.newSpannable(text);
7120             }
7121 
7122             if (Linkify.addLinks(s2, mAutoLinkMask)) {
7123                 text = s2;
7124                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
7125 
7126                 /*
7127                  * We must go ahead and set the text before changing the
7128                  * movement method, because setMovementMethod() may call
7129                  * setText() again to try to upgrade the buffer type.
7130                  */
7131                 setTextInternal(text);
7132                 if (a11yTextChangeType == AccessibilityUtils.NONE) {
7133                     a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN;
7134                 }
7135 
7136                 // Do not change the movement method for text that support text selection as it
7137                 // would prevent an arbitrary cursor displacement.
7138                 if (mLinksClickable && !textCanBeSelected()) {
7139                     setMovementMethod(LinkMovementMethod.getInstance());
7140                 }
7141             }
7142         }
7143 
7144         mBufferType = type;
7145         setTextInternal(text);
7146 
7147         if (mTransformation == null) {
7148             mTransformed = text;
7149         } else {
7150             mTransformed = mTransformation.getTransformation(text, this);
7151         }
7152         if (mTransformed == null) {
7153             // Should not happen if the transformation method follows the non-null postcondition.
7154             mTransformed = "";
7155         }
7156 
7157         final int textLength = text.length();
7158         final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
7159 
7160         if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) {
7161             Spannable sp = (Spannable) text;
7162 
7163             // Remove any ChangeWatchers that might have come from other TextViews.
7164             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
7165             final int count = watchers.length;
7166             for (int i = 0; i < count; i++) {
7167                 sp.removeSpan(watchers[i]);
7168             }
7169 
7170             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
7171 
7172             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
7173                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
7174 
7175             if (mEditor != null) mEditor.addSpanWatchers(sp);
7176 
7177             if (mTransformation != null) {
7178                 final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
7179                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
7180                         | (priority << Spanned.SPAN_PRIORITY_SHIFT));
7181             }
7182 
7183             if (mMovement != null) {
7184                 mMovement.initialize(this, (Spannable) text);
7185 
7186                 /*
7187                  * Initializing the movement method will have set the
7188                  * selection, so reset mSelectionMoved to keep that from
7189                  * interfering with the normal on-focus selection-setting.
7190                  */
7191                 if (mEditor != null) mEditor.mSelectionMoved = false;
7192             }
7193         }
7194 
7195         if (mLayout != null) {
7196             checkForRelayout();
7197         }
7198 
7199         sendOnTextChanged(text, 0, oldlen, textLength);
7200         onTextChanged(text, 0, oldlen, textLength);
7201 
7202         mHideHint = false;
7203 
7204         if (a11yTextChangeType == AccessibilityUtils.TEXT) {
7205             notifyViewAccessibilityStateChangedIfNeeded(
7206                     AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
7207         } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) {
7208             notifyViewAccessibilityStateChangedIfNeeded(
7209                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7210         }
7211 
7212         if (needEditableForNotification) {
7213             sendAfterTextChanged((Editable) text);
7214         } else {
7215             notifyListeningManagersAfterTextChanged();
7216         }
7217 
7218         if (mEditor != null) {
7219             // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
7220             mEditor.prepareCursorControllers();
7221 
7222             mEditor.maybeFireScheduledRestartInputForSetText();
7223         }
7224     }
7225 
7226     /**
7227      * Sets the TextView to display the specified slice of the specified
7228      * char array. You must promise that you will not change the contents
7229      * of the array except for right before another call to setText(),
7230      * since the TextView has no way to know that the text
7231      * has changed and that it needs to invalidate and re-layout.
7232      *
7233      * @throws NullPointerException if text is null
7234      * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length
7235      *
7236      * @param text char array to be displayed
7237      * @param start start index in the char array
7238      * @param len length of char count after {@code start}
7239      */
setText(@onNull char[] text, int start, int len)7240     public final void setText(@NonNull char[] text, int start, int len) {
7241         int oldlen = 0;
7242 
7243         if (start < 0 || len < 0 || start + len > text.length) {
7244             throw new IndexOutOfBoundsException(start + ", " + len);
7245         }
7246 
7247         /*
7248          * We must do the before-notification here ourselves because if
7249          * the old text is a CharWrapper we destroy it before calling
7250          * into the normal path.
7251          */
7252         if (mText != null) {
7253             oldlen = mText.length();
7254             sendBeforeTextChanged(mText, 0, oldlen, len);
7255         } else {
7256             sendBeforeTextChanged("", 0, 0, len);
7257         }
7258 
7259         if (mCharWrapper == null) {
7260             mCharWrapper = new CharWrapper(text, start, len);
7261         } else {
7262             mCharWrapper.set(text, start, len);
7263         }
7264 
7265         setText(mCharWrapper, mBufferType, false, oldlen);
7266     }
7267 
7268     /**
7269      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
7270      * the cursor position. Same as
7271      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
7272      * position (if any) is retained in the new text.
7273      * <p/>
7274      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7275      * intermediate {@link Spannable Spannables}. Likewise it will use
7276      * {@link android.text.Editable.Factory} to create final or intermediate
7277      * {@link Editable Editables}.
7278      *
7279      * @param text text to be displayed
7280      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7281      *              stored as a static text, styleable/spannable text, or editable text
7282      *
7283      * @see #setText(CharSequence, android.widget.TextView.BufferType)
7284      */
setTextKeepState(CharSequence text, BufferType type)7285     public final void setTextKeepState(CharSequence text, BufferType type) {
7286         int start = getSelectionStart();
7287         int end = getSelectionEnd();
7288         int len = text.length();
7289 
7290         setText(text, type);
7291 
7292         if (start >= 0 || end >= 0) {
7293             if (mSpannable != null) {
7294                 Selection.setSelection(mSpannable,
7295                                        Math.max(0, Math.min(start, len)),
7296                                        Math.max(0, Math.min(end, len)));
7297             }
7298         }
7299     }
7300 
7301     /**
7302      * Sets the text to be displayed using a string resource identifier.
7303      *
7304      * @param resid the resource identifier of the string resource to be displayed
7305      *
7306      * @see #setText(CharSequence)
7307      *
7308      * @attr ref android.R.styleable#TextView_text
7309      */
7310     @android.view.RemotableViewMethod
setText(@tringRes int resid)7311     public final void setText(@StringRes int resid) {
7312         setText(getContext().getResources().getText(resid));
7313         mTextSetFromXmlOrResourceId = true;
7314         mTextId = resid;
7315     }
7316 
7317     /**
7318      * Sets the text to be displayed using a string resource identifier and the
7319      * {@link android.widget.TextView.BufferType}.
7320      * <p/>
7321      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7322      * intermediate {@link Spannable Spannables}. Likewise it will use
7323      * {@link android.text.Editable.Factory} to create final or intermediate
7324      * {@link Editable Editables}.
7325      *
7326      * @param resid the resource identifier of the string resource to be displayed
7327      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7328      *              stored as a static text, styleable/spannable text, or editable text
7329      *
7330      * @see #setText(int)
7331      * @see #setText(CharSequence)
7332      * @see android.widget.TextView.BufferType
7333      * @see #setSpannableFactory(Spannable.Factory)
7334      * @see #setEditableFactory(Editable.Factory)
7335      *
7336      * @attr ref android.R.styleable#TextView_text
7337      * @attr ref android.R.styleable#TextView_bufferType
7338      */
setText(@tringRes int resid, BufferType type)7339     public final void setText(@StringRes int resid, BufferType type) {
7340         setText(getContext().getResources().getText(resid), type);
7341         mTextSetFromXmlOrResourceId = true;
7342         mTextId = resid;
7343     }
7344 
7345     /**
7346      * Sets the text to be displayed when the text of the TextView is empty.
7347      * Null means to use the normal empty text. The hint does not currently
7348      * participate in determining the size of the view.
7349      *
7350      * @attr ref android.R.styleable#TextView_hint
7351      */
7352     @android.view.RemotableViewMethod
setHint(CharSequence hint)7353     public final void setHint(CharSequence hint) {
7354         setHintInternal(hint);
7355 
7356         if (mEditor != null && isInputMethodTarget()) {
7357             mEditor.reportExtractedText();
7358         }
7359     }
7360 
setHintInternal(CharSequence hint)7361     private void setHintInternal(CharSequence hint) {
7362         mHideHint = false;
7363         mHint = TextUtils.stringOrSpannedString(hint);
7364 
7365         if (mLayout != null) {
7366             checkForRelayout();
7367         }
7368 
7369         if (mText.length() == 0) {
7370             invalidate();
7371         }
7372 
7373         // Invalidate display list if hint is currently used
7374         if (mEditor != null && mText.length() == 0 && mHint != null) {
7375             mEditor.invalidateTextDisplayList();
7376         }
7377     }
7378 
7379     /**
7380      * Sets the text to be displayed when the text of the TextView is empty,
7381      * from a resource.
7382      *
7383      * @attr ref android.R.styleable#TextView_hint
7384      */
7385     @android.view.RemotableViewMethod
setHint(@tringRes int resid)7386     public final void setHint(@StringRes int resid) {
7387         mHintId = resid;
7388         setHint(getContext().getResources().getText(resid));
7389     }
7390 
7391     /**
7392      * Returns the hint that is displayed when the text of the TextView
7393      * is empty.
7394      *
7395      * @attr ref android.R.styleable#TextView_hint
7396      */
7397     @InspectableProperty
7398     @ViewDebug.CapturedViewProperty
getHint()7399     public CharSequence getHint() {
7400         return mHint;
7401     }
7402 
7403     /**
7404      * Temporarily hides the hint text until the text is modified, or the hint text is modified, or
7405      * the view gains or loses focus.
7406      *
7407      * @hide
7408      */
hideHint()7409     public void hideHint() {
7410         if (isShowingHint()) {
7411             mHideHint = true;
7412             invalidate();
7413         }
7414     }
7415 
7416     /**
7417      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
7418      * line characters instead of letting it wrap onto multiple lines.
7419      *
7420      * @attr ref android.R.styleable#TextView_singleLine
7421      */
7422     @InspectableProperty
isSingleLine()7423     public boolean isSingleLine() {
7424         return mSingleLine;
7425     }
7426 
isMultilineInputType(int type)7427     private static boolean isMultilineInputType(int type) {
7428         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
7429                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
7430     }
7431 
7432     /**
7433      * Removes the suggestion spans.
7434      */
removeSuggestionSpans(CharSequence text)7435     CharSequence removeSuggestionSpans(CharSequence text) {
7436         if (text instanceof Spanned) {
7437             Spannable spannable;
7438             if (text instanceof Spannable) {
7439                 spannable = (Spannable) text;
7440             } else {
7441                 spannable = mSpannableFactory.newSpannable(text);
7442             }
7443 
7444             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
7445             if (spans.length == 0) {
7446                 return text;
7447             } else {
7448                 text = spannable;
7449             }
7450 
7451             for (int i = 0; i < spans.length; i++) {
7452                 spannable.removeSpan(spans[i]);
7453             }
7454         }
7455         return text;
7456     }
7457 
7458     /**
7459      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
7460      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
7461      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
7462      * then a soft keyboard will not be displayed for this text view.
7463      *
7464      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
7465      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
7466      * type.
7467      *
7468      * @see #getInputType()
7469      * @see #setRawInputType(int)
7470      * @see android.text.InputType
7471      * @attr ref android.R.styleable#TextView_inputType
7472      */
setInputType(int type)7473     public void setInputType(int type) {
7474         final boolean wasPassword = isPasswordInputType(getInputType());
7475         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
7476         setInputType(type, false);
7477         final boolean isPassword = isPasswordInputType(type);
7478         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
7479         boolean forceUpdate = false;
7480         if (isPassword) {
7481             setTransformationMethod(PasswordTransformationMethod.getInstance());
7482             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
7483                     Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
7484         } else if (isVisiblePassword) {
7485             if (mTransformation == PasswordTransformationMethod.getInstance()) {
7486                 forceUpdate = true;
7487             }
7488             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
7489                     Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
7490         } else if (wasPassword || wasVisiblePassword) {
7491             // not in password mode, clean up typeface and transformation
7492             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
7493                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
7494                     FontStyle.FONT_WEIGHT_UNSPECIFIED);
7495             if (mTransformation == PasswordTransformationMethod.getInstance()) {
7496                 forceUpdate = true;
7497             }
7498         }
7499 
7500         boolean singleLine = !isMultilineInputType(type);
7501 
7502         // We need to update the single line mode if it has changed or we
7503         // were previously in password mode.
7504         if (mSingleLine != singleLine || forceUpdate) {
7505             // Change single line mode, but only change the transformation if
7506             // we are not in password mode.
7507             applySingleLine(singleLine, !isPassword, true, true);
7508         }
7509 
7510         if (!isSuggestionsEnabled()) {
7511             setTextInternal(removeSuggestionSpans(mText));
7512         }
7513 
7514         InputMethodManager imm = getInputMethodManager();
7515         if (imm != null) imm.restartInput(this);
7516     }
7517 
7518     /**
7519      * It would be better to rely on the input type for everything. A password inputType should have
7520      * a password transformation. We should hence use isPasswordInputType instead of this method.
7521      *
7522      * We should:
7523      * - Call setInputType in setKeyListener instead of changing the input type directly (which
7524      * would install the correct transformation).
7525      * - Refuse the installation of a non-password transformation in setTransformation if the input
7526      * type is password.
7527      *
7528      * However, this is like this for legacy reasons and we cannot break existing apps. This method
7529      * is useful since it matches what the user can see (obfuscated text or not).
7530      *
7531      * @return true if the current transformation method is of the password type.
7532      */
hasPasswordTransformationMethod()7533     boolean hasPasswordTransformationMethod() {
7534         return mTransformation instanceof PasswordTransformationMethod;
7535     }
7536 
7537     /**
7538      * Returns true if the current inputType is any type of password.
7539      *
7540      * @hide
7541      */
isAnyPasswordInputType()7542     public boolean isAnyPasswordInputType() {
7543         final int inputType = getInputType();
7544         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
7545     }
7546 
isPasswordInputType(int inputType)7547     static boolean isPasswordInputType(int inputType) {
7548         final int variation =
7549                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
7550         return variation
7551                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
7552                 || variation
7553                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
7554                 || variation
7555                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
7556     }
7557 
isVisiblePasswordInputType(int inputType)7558     private static boolean isVisiblePasswordInputType(int inputType) {
7559         final int variation =
7560                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
7561         return variation
7562                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
7563     }
7564 
7565     /**
7566      * Directly change the content type integer of the text view, without
7567      * modifying any other state.
7568      * @see #setInputType(int)
7569      * @see android.text.InputType
7570      * @attr ref android.R.styleable#TextView_inputType
7571      */
setRawInputType(int type)7572     public void setRawInputType(int type) {
7573         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
7574         createEditorIfNeeded();
7575         mEditor.mInputType = type;
7576     }
7577 
7578     @Override
getAutofillHints()7579     public String[] getAutofillHints() {
7580         String[] hints = super.getAutofillHints();
7581         if (isAnyPasswordInputType()) {
7582             if (!ArrayUtils.contains(hints, AUTOFILL_HINT_PASSWORD_AUTO)) {
7583                 hints = ArrayUtils.appendElement(String.class, hints,
7584                         AUTOFILL_HINT_PASSWORD_AUTO);
7585             }
7586         }
7587         return hints;
7588     }
7589 
7590     /**
7591      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
7592      *         a {@code Locale} object that can be used to customize key various listeners.
7593      * @see DateKeyListener#getInstance(Locale)
7594      * @see DateTimeKeyListener#getInstance(Locale)
7595      * @see DigitsKeyListener#getInstance(Locale)
7596      * @see TimeKeyListener#getInstance(Locale)
7597      */
7598     @Nullable
getCustomLocaleForKeyListenerOrNull()7599     private Locale getCustomLocaleForKeyListenerOrNull() {
7600         if (!mUseInternationalizedInput) {
7601             // If the application does not target O, stick to the previous behavior.
7602             return null;
7603         }
7604         final LocaleList locales = getImeHintLocales();
7605         if (locales == null) {
7606             // If the application does not explicitly specify IME hint locale, also stick to the
7607             // previous behavior.
7608             return null;
7609         }
7610         return locales.get(0);
7611     }
7612 
7613     @UnsupportedAppUsage
setInputType(int type, boolean direct)7614     private void setInputType(int type, boolean direct) {
7615         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
7616         KeyListener input;
7617         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
7618             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
7619             TextKeyListener.Capitalize cap;
7620             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
7621                 cap = TextKeyListener.Capitalize.CHARACTERS;
7622             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
7623                 cap = TextKeyListener.Capitalize.WORDS;
7624             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
7625                 cap = TextKeyListener.Capitalize.SENTENCES;
7626             } else {
7627                 cap = TextKeyListener.Capitalize.NONE;
7628             }
7629             input = TextKeyListener.getInstance(autotext, cap);
7630         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
7631             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7632             input = DigitsKeyListener.getInstance(
7633                     locale,
7634                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
7635                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
7636             if (locale != null) {
7637                 // Override type, if necessary for i18n.
7638                 int newType = input.getInputType();
7639                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
7640                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
7641                     // The class is different from the original class. So we need to override
7642                     // 'type'. But we want to keep the password flag if it's there.
7643                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
7644                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
7645                     }
7646                     type = newType;
7647                 }
7648             }
7649         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
7650             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7651             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
7652                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
7653                     input = DateKeyListener.getInstance(locale);
7654                     break;
7655                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
7656                     input = TimeKeyListener.getInstance(locale);
7657                     break;
7658                 default:
7659                     input = DateTimeKeyListener.getInstance(locale);
7660                     break;
7661             }
7662             if (mUseInternationalizedInput) {
7663                 type = input.getInputType(); // Override type, if necessary for i18n.
7664             }
7665         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
7666             input = DialerKeyListener.getInstance();
7667         } else {
7668             input = TextKeyListener.getInstance();
7669         }
7670         setRawInputType(type);
7671         mListenerChanged = false;
7672         if (direct) {
7673             createEditorIfNeeded();
7674             mEditor.mKeyListener = input;
7675         } else {
7676             setKeyListenerOnly(input);
7677         }
7678     }
7679 
7680     /**
7681      * Get the type of the editable content.
7682      *
7683      * @see #setInputType(int)
7684      * @see android.text.InputType
7685      */
7686     @InspectableProperty(flagMapping = {
7687             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
7688             @FlagEntry(
7689                     name = "text",
7690                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7691                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
7692             @FlagEntry(
7693                     name = "textUri",
7694                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7695                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
7696             @FlagEntry(
7697                     name = "textEmailAddress",
7698                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7699                     target = InputType.TYPE_CLASS_TEXT
7700                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
7701             @FlagEntry(
7702                     name = "textEmailSubject",
7703                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7704                     target = InputType.TYPE_CLASS_TEXT
7705                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
7706             @FlagEntry(
7707                     name = "textShortMessage",
7708                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7709                     target = InputType.TYPE_CLASS_TEXT
7710                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
7711             @FlagEntry(
7712                     name = "textLongMessage",
7713                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7714                     target = InputType.TYPE_CLASS_TEXT
7715                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
7716             @FlagEntry(
7717                     name = "textPersonName",
7718                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7719                     target = InputType.TYPE_CLASS_TEXT
7720                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
7721             @FlagEntry(
7722                     name = "textPostalAddress",
7723                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7724                     target = InputType.TYPE_CLASS_TEXT
7725                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
7726             @FlagEntry(
7727                     name = "textPassword",
7728                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7729                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
7730             @FlagEntry(
7731                     name = "textVisiblePassword",
7732                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7733                     target = InputType.TYPE_CLASS_TEXT
7734                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
7735             @FlagEntry(
7736                     name = "textWebEditText",
7737                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7738                     target = InputType.TYPE_CLASS_TEXT
7739                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
7740             @FlagEntry(
7741                     name = "textFilter",
7742                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7743                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
7744             @FlagEntry(
7745                     name = "textPhonetic",
7746                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7747                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
7748             @FlagEntry(
7749                     name = "textWebEmailAddress",
7750                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7751                     target = InputType.TYPE_CLASS_TEXT
7752                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
7753             @FlagEntry(
7754                     name = "textWebPassword",
7755                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7756                     target = InputType.TYPE_CLASS_TEXT
7757                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
7758             @FlagEntry(
7759                     name = "number",
7760                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7761                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
7762             @FlagEntry(
7763                     name = "numberPassword",
7764                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7765                     target = InputType.TYPE_CLASS_NUMBER
7766                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
7767             @FlagEntry(
7768                     name = "phone",
7769                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7770                     target = InputType.TYPE_CLASS_PHONE),
7771             @FlagEntry(
7772                     name = "datetime",
7773                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7774                     target = InputType.TYPE_CLASS_DATETIME
7775                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
7776             @FlagEntry(
7777                     name = "date",
7778                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7779                     target = InputType.TYPE_CLASS_DATETIME
7780                             | InputType.TYPE_DATETIME_VARIATION_DATE),
7781             @FlagEntry(
7782                     name = "time",
7783                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7784                     target = InputType.TYPE_CLASS_DATETIME
7785                             | InputType.TYPE_DATETIME_VARIATION_TIME),
7786             @FlagEntry(
7787                     name = "textCapCharacters",
7788                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7789                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
7790             @FlagEntry(
7791                     name = "textCapWords",
7792                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7793                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
7794             @FlagEntry(
7795                     name = "textCapSentences",
7796                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7797                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
7798             @FlagEntry(
7799                     name = "textAutoCorrect",
7800                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7801                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
7802             @FlagEntry(
7803                     name = "textAutoComplete",
7804                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7805                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
7806             @FlagEntry(
7807                     name = "textMultiLine",
7808                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7809                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
7810             @FlagEntry(
7811                     name = "textImeMultiLine",
7812                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7813                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
7814             @FlagEntry(
7815                     name = "textNoSuggestions",
7816                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7817                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
7818             @FlagEntry(
7819                     name = "numberSigned",
7820                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7821                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
7822             @FlagEntry(
7823                     name = "numberDecimal",
7824                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
7825                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
7826     })
getInputType()7827     public int getInputType() {
7828         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
7829     }
7830 
7831     /**
7832      * Change the editor type integer associated with the text view, which
7833      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
7834      * when it has focus.
7835      * @see #getImeOptions
7836      * @see android.view.inputmethod.EditorInfo
7837      * @attr ref android.R.styleable#TextView_imeOptions
7838      */
setImeOptions(int imeOptions)7839     public void setImeOptions(int imeOptions) {
7840         createEditorIfNeeded();
7841         mEditor.createInputContentTypeIfNeeded();
7842         mEditor.mInputContentType.imeOptions = imeOptions;
7843     }
7844 
7845     /**
7846      * Get the type of the Input Method Editor (IME).
7847      * @return the type of the IME
7848      * @see #setImeOptions(int)
7849      * @see EditorInfo
7850      */
7851     @InspectableProperty(flagMapping = {
7852             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
7853             @FlagEntry(
7854                     name = "actionUnspecified",
7855                     mask = EditorInfo.IME_MASK_ACTION,
7856                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
7857             @FlagEntry(
7858                     name = "actionNone",
7859                     mask = EditorInfo.IME_MASK_ACTION,
7860                     target = EditorInfo.IME_ACTION_NONE),
7861             @FlagEntry(
7862                     name = "actionGo",
7863                     mask = EditorInfo.IME_MASK_ACTION,
7864                     target = EditorInfo.IME_ACTION_GO),
7865             @FlagEntry(
7866                     name = "actionSearch",
7867                     mask = EditorInfo.IME_MASK_ACTION,
7868                     target = EditorInfo.IME_ACTION_SEARCH),
7869             @FlagEntry(
7870                     name = "actionSend",
7871                     mask = EditorInfo.IME_MASK_ACTION,
7872                     target = EditorInfo.IME_ACTION_SEND),
7873             @FlagEntry(
7874                     name = "actionNext",
7875                     mask = EditorInfo.IME_MASK_ACTION,
7876                     target = EditorInfo.IME_ACTION_NEXT),
7877             @FlagEntry(
7878                     name = "actionDone",
7879                     mask = EditorInfo.IME_MASK_ACTION,
7880                     target = EditorInfo.IME_ACTION_DONE),
7881             @FlagEntry(
7882                     name = "actionPrevious",
7883                     mask = EditorInfo.IME_MASK_ACTION,
7884                     target = EditorInfo.IME_ACTION_PREVIOUS),
7885             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
7886             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
7887             @FlagEntry(
7888                     name = "flagNavigatePrevious",
7889                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
7890             @FlagEntry(
7891                     name = "flagNoAccessoryAction",
7892                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
7893             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
7894             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
7895             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
7896             @FlagEntry(
7897                     name = "flagNoPersonalizedLearning",
7898                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
7899     })
getImeOptions()7900     public int getImeOptions() {
7901         return mEditor != null && mEditor.mInputContentType != null
7902                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
7903     }
7904 
7905     /**
7906      * Change the custom IME action associated with the text view, which
7907      * will be reported to an IME with {@link EditorInfo#actionLabel}
7908      * and {@link EditorInfo#actionId} when it has focus.
7909      * @see #getImeActionLabel
7910      * @see #getImeActionId
7911      * @see android.view.inputmethod.EditorInfo
7912      * @attr ref android.R.styleable#TextView_imeActionLabel
7913      * @attr ref android.R.styleable#TextView_imeActionId
7914      */
setImeActionLabel(CharSequence label, int actionId)7915     public void setImeActionLabel(CharSequence label, int actionId) {
7916         createEditorIfNeeded();
7917         mEditor.createInputContentTypeIfNeeded();
7918         mEditor.mInputContentType.imeActionLabel = label;
7919         mEditor.mInputContentType.imeActionId = actionId;
7920     }
7921 
7922     /**
7923      * Get the IME action label previous set with {@link #setImeActionLabel}.
7924      *
7925      * @see #setImeActionLabel
7926      * @see android.view.inputmethod.EditorInfo
7927      */
7928     @InspectableProperty
getImeActionLabel()7929     public CharSequence getImeActionLabel() {
7930         return mEditor != null && mEditor.mInputContentType != null
7931                 ? mEditor.mInputContentType.imeActionLabel : null;
7932     }
7933 
7934     /**
7935      * Get the IME action ID previous set with {@link #setImeActionLabel}.
7936      *
7937      * @see #setImeActionLabel
7938      * @see android.view.inputmethod.EditorInfo
7939      */
7940     @InspectableProperty
getImeActionId()7941     public int getImeActionId() {
7942         return mEditor != null && mEditor.mInputContentType != null
7943                 ? mEditor.mInputContentType.imeActionId : 0;
7944     }
7945 
7946     /**
7947      * Set a special listener to be called when an action is performed
7948      * on the text view.  This will be called when the enter key is pressed,
7949      * or when an action supplied to the IME is selected by the user.  Setting
7950      * this means that the normal hard key event will not insert a newline
7951      * into the text view, even if it is multi-line; holding down the ALT
7952      * modifier will, however, allow the user to insert a newline character.
7953      */
setOnEditorActionListener(OnEditorActionListener l)7954     public void setOnEditorActionListener(OnEditorActionListener l) {
7955         createEditorIfNeeded();
7956         mEditor.createInputContentTypeIfNeeded();
7957         mEditor.mInputContentType.onEditorActionListener = l;
7958     }
7959 
7960     /**
7961      * Called when an attached input method calls
7962      * {@link InputConnection#performEditorAction(int)
7963      * InputConnection.performEditorAction()}
7964      * for this text view.  The default implementation will call your action
7965      * listener supplied to {@link #setOnEditorActionListener}, or perform
7966      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
7967      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
7968      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
7969      * EditorInfo.IME_ACTION_DONE}.
7970      *
7971      * <p>For backwards compatibility, if no IME options have been set and the
7972      * text view would not normally advance focus on enter, then
7973      * the NEXT and DONE actions received here will be turned into an enter
7974      * key down/up pair to go through the normal key handling.
7975      *
7976      * @param actionCode The code of the action being performed.
7977      *
7978      * @see #setOnEditorActionListener
7979      */
onEditorAction(int actionCode)7980     public void onEditorAction(int actionCode) {
7981         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
7982         if (ict != null) {
7983             if (ict.onEditorActionListener != null) {
7984                 if (ict.onEditorActionListener.onEditorAction(this,
7985                         actionCode, null)) {
7986                     return;
7987                 }
7988             }
7989 
7990             // This is the handling for some default action.
7991             // Note that for backwards compatibility we don't do this
7992             // default handling if explicit ime options have not been given,
7993             // instead turning this into the normal enter key codes that an
7994             // app may be expecting.
7995             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
7996                 View v = focusSearch(FOCUS_FORWARD);
7997                 if (v != null) {
7998                     if (!v.requestFocus(FOCUS_FORWARD)) {
7999                         throw new IllegalStateException("focus search returned a view "
8000                                 + "that wasn't able to take focus!");
8001                     }
8002                 }
8003                 return;
8004 
8005             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
8006                 View v = focusSearch(FOCUS_BACKWARD);
8007                 if (v != null) {
8008                     if (!v.requestFocus(FOCUS_BACKWARD)) {
8009                         throw new IllegalStateException("focus search returned a view "
8010                                 + "that wasn't able to take focus!");
8011                     }
8012                 }
8013                 return;
8014 
8015             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
8016                 InputMethodManager imm = getInputMethodManager();
8017                 if (imm != null && imm.isActive(this)) {
8018                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
8019                 }
8020                 return;
8021             }
8022         }
8023 
8024         ViewRootImpl viewRootImpl = getViewRootImpl();
8025         if (viewRootImpl != null) {
8026             long eventTime = SystemClock.uptimeMillis();
8027             viewRootImpl.dispatchKeyFromIme(
8028                     new KeyEvent(eventTime, eventTime,
8029                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
8030                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
8031                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
8032                     | KeyEvent.FLAG_EDITOR_ACTION));
8033             viewRootImpl.dispatchKeyFromIme(
8034                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
8035                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
8036                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
8037                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
8038                     | KeyEvent.FLAG_EDITOR_ACTION));
8039         }
8040     }
8041 
8042     /**
8043      * Set the private content type of the text, which is the
8044      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
8045      * field that will be filled in when creating an input connection.
8046      *
8047      * @see #getPrivateImeOptions()
8048      * @see EditorInfo#privateImeOptions
8049      * @attr ref android.R.styleable#TextView_privateImeOptions
8050      */
setPrivateImeOptions(String type)8051     public void setPrivateImeOptions(String type) {
8052         createEditorIfNeeded();
8053         mEditor.createInputContentTypeIfNeeded();
8054         mEditor.mInputContentType.privateImeOptions = type;
8055     }
8056 
8057     /**
8058      * Get the private type of the content.
8059      *
8060      * @see #setPrivateImeOptions(String)
8061      * @see EditorInfo#privateImeOptions
8062      */
8063     @InspectableProperty
getPrivateImeOptions()8064     public String getPrivateImeOptions() {
8065         return mEditor != null && mEditor.mInputContentType != null
8066                 ? mEditor.mInputContentType.privateImeOptions : null;
8067     }
8068 
8069     /**
8070      * Set the extra input data of the text, which is the
8071      * {@link EditorInfo#extras TextBoxAttribute.extras}
8072      * Bundle that will be filled in when creating an input connection.  The
8073      * given integer is the resource identifier of an XML resource holding an
8074      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
8075      *
8076      * @see #getInputExtras(boolean)
8077      * @see EditorInfo#extras
8078      * @attr ref android.R.styleable#TextView_editorExtras
8079      */
setInputExtras(@mlRes int xmlResId)8080     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
8081         createEditorIfNeeded();
8082         XmlResourceParser parser = getResources().getXml(xmlResId);
8083         mEditor.createInputContentTypeIfNeeded();
8084         mEditor.mInputContentType.extras = new Bundle();
8085         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
8086     }
8087 
8088     /**
8089      * Retrieve the input extras currently associated with the text view, which
8090      * can be viewed as well as modified.
8091      *
8092      * @param create If true, the extras will be created if they don't already
8093      * exist.  Otherwise, null will be returned if none have been created.
8094      * @see #setInputExtras(int)
8095      * @see EditorInfo#extras
8096      * @attr ref android.R.styleable#TextView_editorExtras
8097      */
getInputExtras(boolean create)8098     public Bundle getInputExtras(boolean create) {
8099         if (mEditor == null && !create) return null;
8100         createEditorIfNeeded();
8101         if (mEditor.mInputContentType == null) {
8102             if (!create) return null;
8103             mEditor.createInputContentTypeIfNeeded();
8104         }
8105         if (mEditor.mInputContentType.extras == null) {
8106             if (!create) return null;
8107             mEditor.mInputContentType.extras = new Bundle();
8108         }
8109         return mEditor.mInputContentType.extras;
8110     }
8111 
8112     /**
8113      * Change "hint" locales associated with the text view, which will be reported to an IME with
8114      * {@link EditorInfo#hintLocales} when it has focus.
8115      *
8116      * Starting with Android O, this also causes internationalized listeners to be created (or
8117      * change locale) based on the first locale in the input locale list.
8118      *
8119      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
8120      * call {@link InputMethodManager#restartInput(View)}.</p>
8121      * @param hintLocales List of the languages that the user is supposed to switch to no matter
8122      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
8123      * @see #getImeHintLocales()
8124      * @see android.view.inputmethod.EditorInfo#hintLocales
8125      */
setImeHintLocales(@ullable LocaleList hintLocales)8126     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
8127         createEditorIfNeeded();
8128         mEditor.createInputContentTypeIfNeeded();
8129         mEditor.mInputContentType.imeHintLocales = hintLocales;
8130         if (mUseInternationalizedInput) {
8131             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
8132         }
8133     }
8134 
8135     /**
8136      * @return The current languages list "hint". {@code null} when no "hint" is available.
8137      * @see #setImeHintLocales(LocaleList)
8138      * @see android.view.inputmethod.EditorInfo#hintLocales
8139      */
8140     @Nullable
getImeHintLocales()8141     public LocaleList getImeHintLocales() {
8142         if (mEditor == null) {
8143             return null;
8144         }
8145         if (mEditor.mInputContentType == null) {
8146             return null;
8147         }
8148         return mEditor.mInputContentType.imeHintLocales;
8149     }
8150 
8151     /**
8152      * Returns the error message that was set to be displayed with
8153      * {@link #setError}, or <code>null</code> if no error was set
8154      * or if it the error was cleared by the widget after user input.
8155      */
getError()8156     public CharSequence getError() {
8157         return mEditor == null ? null : mEditor.mError;
8158     }
8159 
8160     /**
8161      * Sets the right-hand compound drawable of the TextView to the "error"
8162      * icon and sets an error message that will be displayed in a popup when
8163      * the TextView has focus.  The icon and error message will be reset to
8164      * null when any key events cause changes to the TextView's text.  If the
8165      * <code>error</code> is <code>null</code>, the error message and icon
8166      * will be cleared.
8167      */
8168     @android.view.RemotableViewMethod
setError(CharSequence error)8169     public void setError(CharSequence error) {
8170         if (error == null) {
8171             setError(null, null);
8172         } else {
8173             Drawable dr = getContext().getDrawable(
8174                     com.android.internal.R.drawable.indicator_input_error);
8175 
8176             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
8177             setError(error, dr);
8178         }
8179     }
8180 
8181     /**
8182      * Sets the right-hand compound drawable of the TextView to the specified
8183      * icon and sets an error message that will be displayed in a popup when
8184      * the TextView has focus.  The icon and error message will be reset to
8185      * null when any key events cause changes to the TextView's text.  The
8186      * drawable must already have had {@link Drawable#setBounds} set on it.
8187      * If the <code>error</code> is <code>null</code>, the error message will
8188      * be cleared (and you should provide a <code>null</code> icon as well).
8189      */
setError(CharSequence error, Drawable icon)8190     public void setError(CharSequence error, Drawable icon) {
8191         createEditorIfNeeded();
8192         mEditor.setError(error, icon);
8193         notifyViewAccessibilityStateChangedIfNeeded(
8194                 AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR
8195                         | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID);
8196     }
8197 
8198     @Override
setFrame(int l, int t, int r, int b)8199     protected boolean setFrame(int l, int t, int r, int b) {
8200         boolean result = super.setFrame(l, t, r, b);
8201 
8202         if (mEditor != null) mEditor.setFrame();
8203 
8204         restartMarqueeIfNeeded();
8205 
8206         return result;
8207     }
8208 
restartMarqueeIfNeeded()8209     private void restartMarqueeIfNeeded() {
8210         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8211             mRestartMarquee = false;
8212             startMarquee();
8213         }
8214     }
8215 
8216     /**
8217      * Sets the list of input filters that will be used if the buffer is
8218      * Editable. Has no effect otherwise.
8219      *
8220      * @attr ref android.R.styleable#TextView_maxLength
8221      */
setFilters(InputFilter[] filters)8222     public void setFilters(InputFilter[] filters) {
8223         if (filters == null) {
8224             throw new IllegalArgumentException();
8225         }
8226 
8227         mFilters = filters;
8228 
8229         if (mText instanceof Editable) {
8230             setFilters((Editable) mText, filters);
8231         }
8232     }
8233 
8234     /**
8235      * Sets the list of input filters on the specified Editable,
8236      * and includes mInput in the list if it is an InputFilter.
8237      */
setFilters(Editable e, InputFilter[] filters)8238     private void setFilters(Editable e, InputFilter[] filters) {
8239         if (mEditor != null) {
8240             final boolean undoFilter = mEditor.mUndoInputFilter != null;
8241             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
8242             int num = 0;
8243             if (undoFilter) num++;
8244             if (keyFilter) num++;
8245             if (num > 0) {
8246                 InputFilter[] nf = new InputFilter[filters.length + num];
8247 
8248                 System.arraycopy(filters, 0, nf, 0, filters.length);
8249                 num = 0;
8250                 if (undoFilter) {
8251                     nf[filters.length] = mEditor.mUndoInputFilter;
8252                     num++;
8253                 }
8254                 if (keyFilter) {
8255                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
8256                 }
8257 
8258                 e.setFilters(nf);
8259                 return;
8260             }
8261         }
8262         e.setFilters(filters);
8263     }
8264 
8265     /**
8266      * Returns the current list of input filters.
8267      *
8268      * @attr ref android.R.styleable#TextView_maxLength
8269      */
getFilters()8270     public InputFilter[] getFilters() {
8271         return mFilters;
8272     }
8273 
8274     /////////////////////////////////////////////////////////////////////////
8275 
getBoxHeight(Layout l)8276     private int getBoxHeight(Layout l) {
8277         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
8278         int padding = (l == mHintLayout)
8279                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
8280                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
8281         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
8282     }
8283 
8284     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)8285     int getVerticalOffset(boolean forceNormal) {
8286         int voffset = 0;
8287         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
8288 
8289         Layout l = mLayout;
8290         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
8291             l = mHintLayout;
8292         }
8293 
8294         if (gravity != Gravity.TOP) {
8295             int boxht = getBoxHeight(l);
8296             int textht = l.getHeight();
8297 
8298             if (textht < boxht) {
8299                 if (gravity == Gravity.BOTTOM) {
8300                     voffset = boxht - textht;
8301                 } else { // (gravity == Gravity.CENTER_VERTICAL)
8302                     voffset = (boxht - textht) >> 1;
8303                 }
8304             }
8305         }
8306         return voffset;
8307     }
8308 
getBottomVerticalOffset(boolean forceNormal)8309     private int getBottomVerticalOffset(boolean forceNormal) {
8310         int voffset = 0;
8311         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
8312 
8313         Layout l = mLayout;
8314         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
8315             l = mHintLayout;
8316         }
8317 
8318         if (gravity != Gravity.BOTTOM) {
8319             int boxht = getBoxHeight(l);
8320             int textht = l.getHeight();
8321 
8322             if (textht < boxht) {
8323                 if (gravity == Gravity.TOP) {
8324                     voffset = boxht - textht;
8325                 } else { // (gravity == Gravity.CENTER_VERTICAL)
8326                     voffset = (boxht - textht) >> 1;
8327                 }
8328             }
8329         }
8330         return voffset;
8331     }
8332 
invalidateCursorPath()8333     void invalidateCursorPath() {
8334         if (mHighlightPathBogus) {
8335             invalidateCursor();
8336         } else {
8337             final int horizontalPadding = getCompoundPaddingLeft();
8338             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
8339 
8340             if (mEditor.mDrawableForCursor == null) {
8341                 synchronized (TEMP_RECTF) {
8342                     /*
8343                      * The reason for this concern about the thickness of the
8344                      * cursor and doing the floor/ceil on the coordinates is that
8345                      * some EditTexts (notably textfields in the Browser) have
8346                      * anti-aliased text where not all the characters are
8347                      * necessarily at integer-multiple locations.  This should
8348                      * make sure the entire cursor gets invalidated instead of
8349                      * sometimes missing half a pixel.
8350                      */
8351                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
8352                     if (thick < 1.0f) {
8353                         thick = 1.0f;
8354                     }
8355 
8356                     thick /= 2.0f;
8357 
8358                     // mHighlightPath is guaranteed to be non null at that point.
8359                     mHighlightPath.computeBounds(TEMP_RECTF, false);
8360 
8361                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
8362                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
8363                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
8364                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
8365                 }
8366             } else {
8367                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
8368                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
8369                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
8370             }
8371         }
8372     }
8373 
invalidateCursor()8374     void invalidateCursor() {
8375         int where = getSelectionEnd();
8376 
8377         invalidateCursor(where, where, where);
8378     }
8379 
invalidateCursor(int a, int b, int c)8380     private void invalidateCursor(int a, int b, int c) {
8381         if (a >= 0 || b >= 0 || c >= 0) {
8382             int start = Math.min(Math.min(a, b), c);
8383             int end = Math.max(Math.max(a, b), c);
8384             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
8385         }
8386     }
8387 
8388     /**
8389      * Invalidates the region of text enclosed between the start and end text offsets.
8390      */
invalidateRegion(int start, int end, boolean invalidateCursor)8391     void invalidateRegion(int start, int end, boolean invalidateCursor) {
8392         if (mLayout == null) {
8393             invalidate();
8394         } else {
8395             start = originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
8396             end = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
8397             int lineStart = mLayout.getLineForOffset(start);
8398             int top = mLayout.getLineTop(lineStart);
8399 
8400             // This is ridiculous, but the descent from the line above
8401             // can hang down into the line we really want to redraw,
8402             // so we have to invalidate part of the line above to make
8403             // sure everything that needs to be redrawn really is.
8404             // (But not the whole line above, because that would cause
8405             // the same problem with the descenders on the line above it!)
8406             if (lineStart > 0) {
8407                 top -= mLayout.getLineDescent(lineStart - 1);
8408             }
8409 
8410             int lineEnd;
8411 
8412             if (start == end) {
8413                 lineEnd = lineStart;
8414             } else {
8415                 lineEnd = mLayout.getLineForOffset(end);
8416             }
8417 
8418             int bottom = mLayout.getLineBottom(lineEnd);
8419 
8420             // mEditor can be null in case selection is set programmatically.
8421             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
8422                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
8423                 top = Math.min(top, bounds.top);
8424                 bottom = Math.max(bottom, bounds.bottom);
8425             }
8426 
8427             final int compoundPaddingLeft = getCompoundPaddingLeft();
8428             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
8429 
8430             int left, right;
8431             if (lineStart == lineEnd && !invalidateCursor) {
8432                 left = (int) mLayout.getPrimaryHorizontal(start);
8433                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
8434                 left += compoundPaddingLeft;
8435                 right += compoundPaddingLeft;
8436             } else {
8437                 // Rectangle bounding box when the region spans several lines
8438                 left = compoundPaddingLeft;
8439                 right = getWidth() - getCompoundPaddingRight();
8440             }
8441 
8442             invalidate(mScrollX + left, verticalPadding + top,
8443                     mScrollX + right, verticalPadding + bottom);
8444         }
8445     }
8446 
registerForPreDraw()8447     private void registerForPreDraw() {
8448         if (!mPreDrawRegistered) {
8449             getViewTreeObserver().addOnPreDrawListener(this);
8450             mPreDrawRegistered = true;
8451         }
8452     }
8453 
unregisterForPreDraw()8454     private void unregisterForPreDraw() {
8455         getViewTreeObserver().removeOnPreDrawListener(this);
8456         mPreDrawRegistered = false;
8457         mPreDrawListenerDetached = false;
8458     }
8459 
8460     /**
8461      * {@inheritDoc}
8462      */
8463     @Override
onPreDraw()8464     public boolean onPreDraw() {
8465         if (mLayout == null) {
8466             assumeLayout();
8467         }
8468 
8469         if (mMovement != null) {
8470             /* This code also provides auto-scrolling when a cursor is moved using a
8471              * CursorController (insertion point or selection limits).
8472              * For selection, ensure start or end is visible depending on controller's state.
8473              */
8474             int curs = getSelectionEnd();
8475             // Do not create the controller if it is not already created.
8476             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
8477                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
8478                 curs = getSelectionStart();
8479             }
8480 
8481             /*
8482              * TODO: This should really only keep the end in view if
8483              * it already was before the text changed.  I'm not sure
8484              * of a good way to tell from here if it was.
8485              */
8486             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8487                 curs = mText.length();
8488             }
8489 
8490             if (curs >= 0) {
8491                 bringPointIntoView(curs);
8492             }
8493         } else {
8494             bringTextIntoView();
8495         }
8496 
8497         // This has to be checked here since:
8498         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
8499         //   a screen rotation) since layout is not yet initialized at that point.
8500         if (mEditor != null && mEditor.mCreatedWithASelection) {
8501             mEditor.refreshTextActionMode();
8502             mEditor.mCreatedWithASelection = false;
8503         }
8504 
8505         unregisterForPreDraw();
8506 
8507         return true;
8508     }
8509 
8510     @Override
onAttachedToWindow()8511     protected void onAttachedToWindow() {
8512         super.onAttachedToWindow();
8513 
8514         if (mEditor != null) mEditor.onAttachedToWindow();
8515 
8516         if (mPreDrawListenerDetached) {
8517             getViewTreeObserver().addOnPreDrawListener(this);
8518             mPreDrawListenerDetached = false;
8519         }
8520     }
8521 
8522     /** @hide */
8523     @Override
onDetachedFromWindowInternal()8524     protected void onDetachedFromWindowInternal() {
8525         if (mPreDrawRegistered) {
8526             getViewTreeObserver().removeOnPreDrawListener(this);
8527             mPreDrawListenerDetached = true;
8528         }
8529 
8530         resetResolvedDrawables();
8531 
8532         if (mEditor != null) mEditor.onDetachedFromWindow();
8533 
8534         super.onDetachedFromWindowInternal();
8535     }
8536 
8537     @Override
onScreenStateChanged(int screenState)8538     public void onScreenStateChanged(int screenState) {
8539         super.onScreenStateChanged(screenState);
8540         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
8541     }
8542 
8543     @Override
isPaddingOffsetRequired()8544     protected boolean isPaddingOffsetRequired() {
8545         return mShadowRadius != 0 || mDrawables != null;
8546     }
8547 
8548     @Override
getLeftPaddingOffset()8549     protected int getLeftPaddingOffset() {
8550         return getCompoundPaddingLeft() - mPaddingLeft
8551                 + (int) Math.min(0, mShadowDx - mShadowRadius);
8552     }
8553 
8554     @Override
getTopPaddingOffset()8555     protected int getTopPaddingOffset() {
8556         return (int) Math.min(0, mShadowDy - mShadowRadius);
8557     }
8558 
8559     @Override
getBottomPaddingOffset()8560     protected int getBottomPaddingOffset() {
8561         return (int) Math.max(0, mShadowDy + mShadowRadius);
8562     }
8563 
8564     @Override
getRightPaddingOffset()8565     protected int getRightPaddingOffset() {
8566         return -(getCompoundPaddingRight() - mPaddingRight)
8567                 + (int) Math.max(0, mShadowDx + mShadowRadius);
8568     }
8569 
8570     @Override
verifyDrawable(@onNull Drawable who)8571     protected boolean verifyDrawable(@NonNull Drawable who) {
8572         final boolean verified = super.verifyDrawable(who);
8573         if (!verified && mDrawables != null) {
8574             for (Drawable dr : mDrawables.mShowing) {
8575                 if (who == dr) {
8576                     return true;
8577                 }
8578             }
8579         }
8580         return verified;
8581     }
8582 
8583     @Override
jumpDrawablesToCurrentState()8584     public void jumpDrawablesToCurrentState() {
8585         super.jumpDrawablesToCurrentState();
8586         if (mDrawables != null) {
8587             for (Drawable dr : mDrawables.mShowing) {
8588                 if (dr != null) {
8589                     dr.jumpToCurrentState();
8590                 }
8591             }
8592         }
8593     }
8594 
8595     @Override
invalidateDrawable(@onNull Drawable drawable)8596     public void invalidateDrawable(@NonNull Drawable drawable) {
8597         boolean handled = false;
8598 
8599         if (verifyDrawable(drawable)) {
8600             final Rect dirty = drawable.getBounds();
8601             int scrollX = mScrollX;
8602             int scrollY = mScrollY;
8603 
8604             // IMPORTANT: The coordinates below are based on the coordinates computed
8605             // for each compound drawable in onDraw(). Make sure to update each section
8606             // accordingly.
8607             final TextView.Drawables drawables = mDrawables;
8608             if (drawables != null) {
8609                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
8610                     final int compoundPaddingTop = getCompoundPaddingTop();
8611                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8612                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8613 
8614                     scrollX += mPaddingLeft;
8615                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
8616                     handled = true;
8617                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
8618                     final int compoundPaddingTop = getCompoundPaddingTop();
8619                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8620                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8621 
8622                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
8623                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
8624                     handled = true;
8625                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
8626                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8627                     final int compoundPaddingRight = getCompoundPaddingRight();
8628                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8629 
8630                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
8631                     scrollY += mPaddingTop;
8632                     handled = true;
8633                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
8634                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8635                     final int compoundPaddingRight = getCompoundPaddingRight();
8636                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8637 
8638                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
8639                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
8640                     handled = true;
8641                 }
8642             }
8643 
8644             if (handled) {
8645                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
8646                         dirty.right + scrollX, dirty.bottom + scrollY);
8647             }
8648         }
8649 
8650         if (!handled) {
8651             super.invalidateDrawable(drawable);
8652         }
8653     }
8654 
8655     @Override
hasOverlappingRendering()8656     public boolean hasOverlappingRendering() {
8657         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
8658         return ((getBackground() != null && getBackground().getCurrent() != null)
8659                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
8660                 || mShadowColor != 0);
8661     }
8662 
8663     /**
8664      *
8665      * Returns the state of the {@code textIsSelectable} flag (See
8666      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
8667      * to allow users to select and copy text in a non-editable TextView, the content of an
8668      * {@link EditText} can always be selected, independently of the value of this flag.
8669      * <p>
8670      *
8671      * @return True if the text displayed in this TextView can be selected by the user.
8672      *
8673      * @attr ref android.R.styleable#TextView_textIsSelectable
8674      */
8675     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()8676     public boolean isTextSelectable() {
8677         return mEditor == null ? false : mEditor.mTextIsSelectable;
8678     }
8679 
8680     /**
8681      * Sets whether the content of this view is selectable by the user. The default is
8682      * {@code false}, meaning that the content is not selectable.
8683      * <p>
8684      * When you use a TextView to display a useful piece of information to the user (such as a
8685      * contact's address), make it selectable, so that the user can select and copy its
8686      * content. You can also use set the XML attribute
8687      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
8688      * <p>
8689      * When you call this method to set the value of {@code textIsSelectable}, it sets
8690      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
8691      * and {@code longClickable} to the same value. These flags correspond to the attributes
8692      * {@link android.R.styleable#View_focusable android:focusable},
8693      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
8694      * {@link android.R.styleable#View_clickable android:clickable}, and
8695      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
8696      * flags to a state you had set previously, call one or more of the following methods:
8697      * {@link #setFocusable(boolean) setFocusable()},
8698      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
8699      * {@link #setClickable(boolean) setClickable()} or
8700      * {@link #setLongClickable(boolean) setLongClickable()}.
8701      *
8702      * @param selectable Whether the content of this TextView should be selectable.
8703      */
setTextIsSelectable(boolean selectable)8704     public void setTextIsSelectable(boolean selectable) {
8705         if (!selectable && mEditor == null) return; // false is default value with no edit data
8706 
8707         createEditorIfNeeded();
8708         if (mEditor.mTextIsSelectable == selectable) return;
8709 
8710         mEditor.mTextIsSelectable = selectable;
8711         setFocusableInTouchMode(selectable);
8712         setFocusable(FOCUSABLE_AUTO);
8713         setClickable(selectable);
8714         setLongClickable(selectable);
8715 
8716         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
8717 
8718         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
8719         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
8720 
8721         // Called by setText above, but safer in case of future code changes
8722         mEditor.prepareCursorControllers();
8723     }
8724 
8725     @Override
onCreateDrawableState(int extraSpace)8726     protected int[] onCreateDrawableState(int extraSpace) {
8727         final int[] drawableState;
8728 
8729         if (mSingleLine) {
8730             drawableState = super.onCreateDrawableState(extraSpace);
8731         } else {
8732             drawableState = super.onCreateDrawableState(extraSpace + 1);
8733             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
8734         }
8735 
8736         if (isTextSelectable()) {
8737             // Disable pressed state, which was introduced when TextView was made clickable.
8738             // Prevents text color change.
8739             // setClickable(false) would have a similar effect, but it also disables focus changes
8740             // and long press actions, which are both needed by text selection.
8741             final int length = drawableState.length;
8742             for (int i = 0; i < length; i++) {
8743                 if (drawableState[i] == R.attr.state_pressed) {
8744                     final int[] nonPressedState = new int[length - 1];
8745                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
8746                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
8747                     return nonPressedState;
8748                 }
8749             }
8750         }
8751 
8752         return drawableState;
8753     }
8754 
maybeUpdateHighlightPaths()8755     private void maybeUpdateHighlightPaths() {
8756         if (!mHighlightPathsBogus) {
8757             return;
8758         }
8759 
8760         if (mHighlightPaths != null) {
8761             mPathRecyclePool.addAll(mHighlightPaths);
8762             mHighlightPaths.clear();
8763             mHighlightPaints.clear();
8764         } else {
8765             mHighlightPaths = new ArrayList<>();
8766             mHighlightPaints = new ArrayList<>();
8767         }
8768 
8769         if (mHighlights != null) {
8770             for (int i = 0; i < mHighlights.getSize(); ++i) {
8771                 final int[] ranges = mHighlights.getRanges(i);
8772                 final Paint paint = mHighlights.getPaint(i);
8773                 final Path path;
8774                 if (mPathRecyclePool.isEmpty()) {
8775                     path = new Path();
8776                 } else {
8777                     path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
8778                     mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
8779                     path.reset();
8780                 }
8781 
8782                 boolean atLeastOnePathAdded = false;
8783                 for (int j = 0; j < ranges.length / 2; ++j) {
8784                     final int start = ranges[2 * j];
8785                     final int end = ranges[2 * j + 1];
8786                     if (start < end) {
8787                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
8788                                 path.addRect(left, top, right, bottom, Path.Direction.CW)
8789                         );
8790                         atLeastOnePathAdded = true;
8791                     }
8792                 }
8793                 if (atLeastOnePathAdded) {
8794                     mHighlightPaths.add(path);
8795                     mHighlightPaints.add(paint);
8796                 }
8797             }
8798         }
8799 
8800         addSearchHighlightPaths();
8801 
8802         if (hasGesturePreviewHighlight()) {
8803             final Path path;
8804             if (mPathRecyclePool.isEmpty()) {
8805                 path = new Path();
8806             } else {
8807                 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
8808                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
8809                 path.reset();
8810             }
8811             mLayout.getSelectionPath(
8812                     mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path);
8813             mHighlightPaths.add(path);
8814             mHighlightPaints.add(mGesturePreviewHighlightPaint);
8815         }
8816 
8817         mHighlightPathsBogus = false;
8818     }
8819 
addSearchHighlightPaths()8820     private void addSearchHighlightPaths() {
8821         if (mSearchResultHighlights != null) {
8822             final Path searchResultPath;
8823             if (mPathRecyclePool.isEmpty()) {
8824                 searchResultPath = new Path();
8825             } else {
8826                 searchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
8827                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
8828                 searchResultPath.reset();
8829             }
8830             final Path focusedSearchResultPath;
8831             if (mFocusedSearchResultIndex == FOCUSED_SEARCH_RESULT_INDEX_NONE) {
8832                 focusedSearchResultPath = null;
8833             } else if (mPathRecyclePool.isEmpty()) {
8834                 focusedSearchResultPath = new Path();
8835             } else {
8836                 focusedSearchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
8837                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
8838                 focusedSearchResultPath.reset();
8839             }
8840 
8841             boolean atLeastOnePathAdded = false;
8842             for (int j = 0; j < mSearchResultHighlights.length / 2; ++j) {
8843                 final int start = mSearchResultHighlights[2 * j];
8844                 final int end = mSearchResultHighlights[2 * j + 1];
8845                 if (start < end) {
8846                     if (j == mFocusedSearchResultIndex) {
8847                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
8848                                 focusedSearchResultPath.addRect(left, top, right, bottom,
8849                                         Path.Direction.CW)
8850                         );
8851                     } else {
8852                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
8853                                 searchResultPath.addRect(left, top, right, bottom,
8854                                         Path.Direction.CW)
8855                         );
8856                         atLeastOnePathAdded = true;
8857                     }
8858                 }
8859             }
8860             if (atLeastOnePathAdded) {
8861                 if (mSearchResultHighlightPaint == null) {
8862                     mSearchResultHighlightPaint = new Paint();
8863                 }
8864                 mSearchResultHighlightPaint.setColor(mSearchResultHighlightColor);
8865                 mSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
8866                 mHighlightPaths.add(searchResultPath);
8867                 mHighlightPaints.add(mSearchResultHighlightPaint);
8868             }
8869             if (focusedSearchResultPath != null) {
8870                 if (mFocusedSearchResultHighlightPaint == null) {
8871                     mFocusedSearchResultHighlightPaint = new Paint();
8872                 }
8873                 mFocusedSearchResultHighlightPaint.setColor(mFocusedSearchResultHighlightColor);
8874                 mFocusedSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
8875                 mHighlightPaths.add(focusedSearchResultPath);
8876                 mHighlightPaints.add(mFocusedSearchResultHighlightPaint);
8877             }
8878         }
8879     }
8880 
8881     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getUpdatedHighlightPath()8882     private Path getUpdatedHighlightPath() {
8883         Path highlight = null;
8884         Paint highlightPaint = mHighlightPaint;
8885 
8886         final int selStart = getSelectionStartTransformed();
8887         final int selEnd = getSelectionEndTransformed();
8888         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
8889             if (selStart == selEnd) {
8890                 if (mEditor != null && mEditor.shouldRenderCursor()) {
8891                     if (mHighlightPathBogus) {
8892                         if (mHighlightPath == null) mHighlightPath = new Path();
8893                         mHighlightPath.reset();
8894                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
8895                         mEditor.updateCursorPosition();
8896                         mHighlightPathBogus = false;
8897                     }
8898 
8899                     // XXX should pass to skin instead of drawing directly
8900                     highlightPaint.setColor(mCurTextColor);
8901                     highlightPaint.setStyle(Paint.Style.STROKE);
8902                     highlight = mHighlightPath;
8903                 }
8904             } else {
8905                 if (mHighlightPathBogus) {
8906                     if (mHighlightPath == null) mHighlightPath = new Path();
8907                     mHighlightPath.reset();
8908                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8909                     mHighlightPathBogus = false;
8910                 }
8911 
8912                 // XXX should pass to skin instead of drawing directly
8913                 highlightPaint.setColor(mHighlightColor);
8914                 highlightPaint.setStyle(Paint.Style.FILL);
8915 
8916                 highlight = mHighlightPath;
8917             }
8918         }
8919         return highlight;
8920     }
8921 
8922     /**
8923      * @hide
8924      */
getHorizontalOffsetForDrawables()8925     public int getHorizontalOffsetForDrawables() {
8926         return 0;
8927     }
8928 
8929     @Override
onDraw(Canvas canvas)8930     protected void onDraw(Canvas canvas) {
8931         restartMarqueeIfNeeded();
8932 
8933         // Draw the background for this view
8934         super.onDraw(canvas);
8935 
8936         final int compoundPaddingLeft = getCompoundPaddingLeft();
8937         final int compoundPaddingTop = getCompoundPaddingTop();
8938         final int compoundPaddingRight = getCompoundPaddingRight();
8939         final int compoundPaddingBottom = getCompoundPaddingBottom();
8940         final int scrollX = mScrollX;
8941         final int scrollY = mScrollY;
8942         final int right = mRight;
8943         final int left = mLeft;
8944         final int bottom = mBottom;
8945         final int top = mTop;
8946         final boolean isLayoutRtl = isLayoutRtl();
8947         final int offset = getHorizontalOffsetForDrawables();
8948         final int leftOffset = isLayoutRtl ? 0 : offset;
8949         final int rightOffset = isLayoutRtl ? offset : 0;
8950 
8951         final Drawables dr = mDrawables;
8952         if (dr != null) {
8953             /*
8954              * Compound, not extended, because the icon is not clipped
8955              * if the text height is smaller.
8956              */
8957 
8958             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
8959             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
8960 
8961             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8962             // Make sure to update invalidateDrawable() when changing this code.
8963             if (dr.mShowing[Drawables.LEFT] != null) {
8964                 canvas.save();
8965                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
8966                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
8967                 dr.mShowing[Drawables.LEFT].draw(canvas);
8968                 canvas.restore();
8969             }
8970 
8971             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8972             // Make sure to update invalidateDrawable() when changing this code.
8973             if (dr.mShowing[Drawables.RIGHT] != null) {
8974                 canvas.save();
8975                 canvas.translate(scrollX + right - left - mPaddingRight
8976                         - dr.mDrawableSizeRight - rightOffset,
8977                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
8978                 dr.mShowing[Drawables.RIGHT].draw(canvas);
8979                 canvas.restore();
8980             }
8981 
8982             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8983             // Make sure to update invalidateDrawable() when changing this code.
8984             if (dr.mShowing[Drawables.TOP] != null) {
8985                 canvas.save();
8986                 canvas.translate(scrollX + compoundPaddingLeft
8987                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
8988                 dr.mShowing[Drawables.TOP].draw(canvas);
8989                 canvas.restore();
8990             }
8991 
8992             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
8993             // Make sure to update invalidateDrawable() when changing this code.
8994             if (dr.mShowing[Drawables.BOTTOM] != null) {
8995                 canvas.save();
8996                 canvas.translate(scrollX + compoundPaddingLeft
8997                         + (hspace - dr.mDrawableWidthBottom) / 2,
8998                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
8999                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
9000                 canvas.restore();
9001             }
9002         }
9003 
9004         int color = mCurTextColor;
9005 
9006         if (mLayout == null) {
9007             assumeLayout();
9008         }
9009 
9010         Layout layout = mLayout;
9011 
9012         if (mHint != null && !mHideHint && mText.length() == 0) {
9013             if (mHintTextColor != null) {
9014                 color = mCurHintTextColor;
9015             }
9016 
9017             layout = mHintLayout;
9018         }
9019 
9020         mTextPaint.setColor(color);
9021         mTextPaint.drawableState = getDrawableState();
9022 
9023         canvas.save();
9024         /*  Would be faster if we didn't have to do this. Can we chop the
9025             (displayable) text so that we don't need to do this ever?
9026         */
9027 
9028         int extendedPaddingTop = getExtendedPaddingTop();
9029         int extendedPaddingBottom = getExtendedPaddingBottom();
9030 
9031         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
9032         final int maxScrollY = mLayout.getHeight() - vspace;
9033 
9034         float clipLeft = compoundPaddingLeft + scrollX;
9035         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
9036         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
9037         float clipBottom = bottom - top + scrollY
9038                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
9039 
9040         if (mShadowRadius != 0) {
9041             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
9042             clipRight += Math.max(0, mShadowDx + mShadowRadius);
9043 
9044             clipTop += Math.min(0, mShadowDy - mShadowRadius);
9045             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
9046         }
9047 
9048         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
9049 
9050         int voffsetText = 0;
9051         int voffsetCursor = 0;
9052 
9053         // translate in by our padding
9054         /* shortcircuit calling getVerticaOffset() */
9055         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9056             voffsetText = getVerticalOffset(false);
9057             voffsetCursor = getVerticalOffset(true);
9058         }
9059         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
9060 
9061         final int layoutDirection = getLayoutDirection();
9062         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
9063         if (isMarqueeFadeEnabled()) {
9064             if (!mSingleLine && getLineCount() == 1 && canMarquee()
9065                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
9066                 final int width = mRight - mLeft;
9067                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
9068                 final float dx = mLayout.getLineRight(0) - (width - padding);
9069                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9070             }
9071 
9072             if (mMarquee != null && mMarquee.isRunning()) {
9073                 final float dx = -mMarquee.getScroll();
9074                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9075             }
9076         }
9077 
9078         final int cursorOffsetVertical = voffsetCursor - voffsetText;
9079 
9080         maybeUpdateHighlightPaths();
9081         // If there is a gesture preview highlight, then the selection or cursor is not drawn.
9082         Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
9083         if (mEditor != null) {
9084             mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
9085                     mHighlightPaint, cursorOffsetVertical);
9086         } else {
9087             layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
9088                     cursorOffsetVertical);
9089         }
9090 
9091         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
9092             final float dx = mMarquee.getGhostOffset();
9093             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9094             layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
9095                     cursorOffsetVertical);
9096         }
9097 
9098         canvas.restore();
9099     }
9100 
9101     @Override
getFocusedRect(Rect r)9102     public void getFocusedRect(Rect r) {
9103         if (mLayout == null) {
9104             super.getFocusedRect(r);
9105             return;
9106         }
9107 
9108         int selEnd = getSelectionEndTransformed();
9109         if (selEnd < 0) {
9110             super.getFocusedRect(r);
9111             return;
9112         }
9113 
9114         int selStart = getSelectionStartTransformed();
9115         if (selStart < 0 || selStart >= selEnd) {
9116             int line = mLayout.getLineForOffset(selEnd);
9117             r.top = mLayout.getLineTop(line);
9118             r.bottom = mLayout.getLineBottom(line);
9119             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
9120             r.right = r.left + 4;
9121         } else {
9122             int lineStart = mLayout.getLineForOffset(selStart);
9123             int lineEnd = mLayout.getLineForOffset(selEnd);
9124             r.top = mLayout.getLineTop(lineStart);
9125             r.bottom = mLayout.getLineBottom(lineEnd);
9126             if (lineStart == lineEnd) {
9127                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
9128                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
9129             } else {
9130                 // Selection extends across multiple lines -- make the focused
9131                 // rect cover the entire width.
9132                 if (mHighlightPathBogus) {
9133                     if (mHighlightPath == null) mHighlightPath = new Path();
9134                     mHighlightPath.reset();
9135                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
9136                     mHighlightPathBogus = false;
9137                 }
9138                 synchronized (TEMP_RECTF) {
9139                     mHighlightPath.computeBounds(TEMP_RECTF, true);
9140                     r.left = (int) TEMP_RECTF.left - 1;
9141                     r.right = (int) TEMP_RECTF.right + 1;
9142                 }
9143             }
9144         }
9145 
9146         // Adjust for padding and gravity.
9147         int paddingLeft = getCompoundPaddingLeft();
9148         int paddingTop = getExtendedPaddingTop();
9149         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9150             paddingTop += getVerticalOffset(false);
9151         }
9152         r.offset(paddingLeft, paddingTop);
9153         int paddingBottom = getExtendedPaddingBottom();
9154         r.bottom += paddingBottom;
9155     }
9156 
9157     /**
9158      * Return the number of lines of text, or 0 if the internal Layout has not
9159      * been built.
9160      */
getLineCount()9161     public int getLineCount() {
9162         return mLayout != null ? mLayout.getLineCount() : 0;
9163     }
9164 
9165     /**
9166      * Return the baseline for the specified line (0...getLineCount() - 1)
9167      * If bounds is not null, return the top, left, right, bottom extents
9168      * of the specified line in it. If the internal Layout has not been built,
9169      * return 0 and set bounds to (0, 0, 0, 0)
9170      * @param line which line to examine (0..getLineCount() - 1)
9171      * @param bounds Optional. If not null, it returns the extent of the line
9172      * @return the Y-coordinate of the baseline
9173      */
getLineBounds(int line, Rect bounds)9174     public int getLineBounds(int line, Rect bounds) {
9175         if (mLayout == null) {
9176             if (bounds != null) {
9177                 bounds.set(0, 0, 0, 0);
9178             }
9179             return 0;
9180         } else {
9181             int baseline = mLayout.getLineBounds(line, bounds);
9182 
9183             int voffset = getExtendedPaddingTop();
9184             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9185                 voffset += getVerticalOffset(true);
9186             }
9187             if (bounds != null) {
9188                 bounds.offset(getCompoundPaddingLeft(), voffset);
9189             }
9190             return baseline + voffset;
9191         }
9192     }
9193 
9194     @Override
getBaseline()9195     public int getBaseline() {
9196         if (mLayout == null) {
9197             return super.getBaseline();
9198         }
9199 
9200         return getBaselineOffset() + mLayout.getLineBaseline(0);
9201     }
9202 
getBaselineOffset()9203     int getBaselineOffset() {
9204         int voffset = 0;
9205         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9206             voffset = getVerticalOffset(true);
9207         }
9208 
9209         if (isLayoutModeOptical(mParent)) {
9210             voffset -= getOpticalInsets().top;
9211         }
9212 
9213         return getExtendedPaddingTop() + voffset;
9214     }
9215 
9216     /**
9217      * @hide
9218      */
9219     @Override
getFadeTop(boolean offsetRequired)9220     protected int getFadeTop(boolean offsetRequired) {
9221         if (mLayout == null) return 0;
9222 
9223         int voffset = 0;
9224         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9225             voffset = getVerticalOffset(true);
9226         }
9227 
9228         if (offsetRequired) voffset += getTopPaddingOffset();
9229 
9230         return getExtendedPaddingTop() + voffset;
9231     }
9232 
9233     /**
9234      * @hide
9235      */
9236     @Override
getFadeHeight(boolean offsetRequired)9237     protected int getFadeHeight(boolean offsetRequired) {
9238         return mLayout != null ? mLayout.getHeight() : 0;
9239     }
9240 
9241     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)9242     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
9243         if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
9244             if (mSpannable != null && mLinksClickable) {
9245                 final float x = event.getX(pointerIndex);
9246                 final float y = event.getY(pointerIndex);
9247                 final int offset = getOffsetForPosition(x, y);
9248                 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
9249                         ClickableSpan.class);
9250                 if (clickables.length > 0) {
9251                     return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
9252                 }
9253             }
9254             if (isTextSelectable() || isTextEditable()) {
9255                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
9256             }
9257         }
9258         return super.onResolvePointerIcon(event, pointerIndex);
9259     }
9260 
9261     @Override
onKeyPreIme(int keyCode, KeyEvent event)9262     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
9263         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
9264         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
9265         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
9266         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
9267             return true;
9268         }
9269         return super.onKeyPreIme(keyCode, event);
9270     }
9271 
9272     /**
9273      * @hide
9274      */
handleBackInTextActionModeIfNeeded(KeyEvent event)9275     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
9276         // Do nothing unless mEditor is in text action mode.
9277         if (mEditor == null || mEditor.getTextActionMode() == null) {
9278             return false;
9279         }
9280 
9281         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
9282             KeyEvent.DispatcherState state = getKeyDispatcherState();
9283             if (state != null) {
9284                 state.startTracking(event, this);
9285             }
9286             return true;
9287         } else if (event.getAction() == KeyEvent.ACTION_UP) {
9288             KeyEvent.DispatcherState state = getKeyDispatcherState();
9289             if (state != null) {
9290                 state.handleUpEvent(event);
9291             }
9292             if (event.isTracking() && !event.isCanceled()) {
9293                 stopTextActionMode();
9294                 return true;
9295             }
9296         }
9297         return false;
9298     }
9299 
9300     @Override
onKeyDown(int keyCode, KeyEvent event)9301     public boolean onKeyDown(int keyCode, KeyEvent event) {
9302         final int which = doKeyDown(keyCode, event, null);
9303         if (which == KEY_EVENT_NOT_HANDLED) {
9304             return super.onKeyDown(keyCode, event);
9305         }
9306 
9307         return true;
9308     }
9309 
9310     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)9311     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
9312         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
9313         final int which = doKeyDown(keyCode, down, event);
9314         if (which == KEY_EVENT_NOT_HANDLED) {
9315             // Go through default dispatching.
9316             return super.onKeyMultiple(keyCode, repeatCount, event);
9317         }
9318         if (which == KEY_EVENT_HANDLED) {
9319             // Consumed the whole thing.
9320             return true;
9321         }
9322 
9323         repeatCount--;
9324 
9325         // We are going to dispatch the remaining events to either the input
9326         // or movement method.  To do this, we will just send a repeated stream
9327         // of down and up events until we have done the complete repeatCount.
9328         // It would be nice if those interfaces had an onKeyMultiple() method,
9329         // but adding that is a more complicated change.
9330         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
9331         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
9332             // mEditor and mEditor.mInput are not null from doKeyDown
9333             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
9334             while (--repeatCount > 0) {
9335                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
9336                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
9337             }
9338             hideErrorIfUnchanged();
9339 
9340         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
9341             // mMovement is not null from doKeyDown
9342             mMovement.onKeyUp(this, mSpannable, keyCode, up);
9343             while (--repeatCount > 0) {
9344                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
9345                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
9346             }
9347         }
9348 
9349         return true;
9350     }
9351 
9352     /**
9353      * Returns true if pressing ENTER in this field advances focus instead
9354      * of inserting the character.  This is true mostly in single-line fields,
9355      * but also in mail addresses and subjects which will display on multiple
9356      * lines but where it doesn't make sense to insert newlines.
9357      */
shouldAdvanceFocusOnEnter()9358     private boolean shouldAdvanceFocusOnEnter() {
9359         if (getKeyListener() == null) {
9360             return false;
9361         }
9362 
9363         if (mSingleLine) {
9364             return true;
9365         }
9366 
9367         if (mEditor != null
9368                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
9369                         == EditorInfo.TYPE_CLASS_TEXT) {
9370             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9371             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
9372                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
9373                 return true;
9374             }
9375         }
9376 
9377         return false;
9378     }
9379 
isDirectionalNavigationKey(int keyCode)9380     private boolean isDirectionalNavigationKey(int keyCode) {
9381         switch(keyCode) {
9382             case KeyEvent.KEYCODE_DPAD_UP:
9383             case KeyEvent.KEYCODE_DPAD_DOWN:
9384             case KeyEvent.KEYCODE_DPAD_LEFT:
9385             case KeyEvent.KEYCODE_DPAD_RIGHT:
9386                 return true;
9387         }
9388         return false;
9389     }
9390 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)9391     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
9392         if (!isEnabled()) {
9393             return KEY_EVENT_NOT_HANDLED;
9394         }
9395 
9396         // If this is the initial keydown, we don't want to prevent a movement away from this view.
9397         // While this shouldn't be necessary because any time we're preventing default movement we
9398         // should be restricting the focus to remain within this view, thus we'll also receive
9399         // the key up event, occasionally key up events will get dropped and we don't want to
9400         // prevent the user from traversing out of this on the next key down.
9401         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
9402             mPreventDefaultMovement = false;
9403         }
9404 
9405         switch (keyCode) {
9406             case KeyEvent.KEYCODE_ENTER:
9407             case KeyEvent.KEYCODE_NUMPAD_ENTER:
9408                 if (event.hasNoModifiers()) {
9409                     // When mInputContentType is set, we know that we are
9410                     // running in a "modern" cupcake environment, so don't need
9411                     // to worry about the application trying to capture
9412                     // enter key events.
9413                     if (mEditor != null && mEditor.mInputContentType != null) {
9414                         // If there is an action listener, given them a
9415                         // chance to consume the event.
9416                         if (mEditor.mInputContentType.onEditorActionListener != null
9417                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
9418                                         this,
9419                                         getActionIdForEnterEvent(),
9420                                         event)) {
9421                             mEditor.mInputContentType.enterDown = true;
9422                             // We are consuming the enter key for them.
9423                             return KEY_EVENT_HANDLED;
9424                         }
9425                     }
9426 
9427                     // If our editor should move focus when enter is pressed, or
9428                     // this is a generated event from an IME action button, then
9429                     // don't let it be inserted into the text.
9430                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
9431                             || shouldAdvanceFocusOnEnter()) {
9432                         if (hasOnClickListeners()) {
9433                             return KEY_EVENT_NOT_HANDLED;
9434                         }
9435                         return KEY_EVENT_HANDLED;
9436                     }
9437                 }
9438                 break;
9439 
9440             case KeyEvent.KEYCODE_DPAD_CENTER:
9441                 if (event.hasNoModifiers()) {
9442                     if (shouldAdvanceFocusOnEnter()) {
9443                         return KEY_EVENT_NOT_HANDLED;
9444                     }
9445                 }
9446                 break;
9447 
9448             case KeyEvent.KEYCODE_TAB:
9449                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
9450                     // Tab is used to move focus.
9451                     return KEY_EVENT_NOT_HANDLED;
9452                 }
9453                 break;
9454 
9455                 // Has to be done on key down (and not on key up) to correctly be intercepted.
9456             case KeyEvent.KEYCODE_BACK:
9457                 if (mEditor != null && mEditor.getTextActionMode() != null) {
9458                     stopTextActionMode();
9459                     return KEY_EVENT_HANDLED;
9460                 }
9461                 break;
9462 
9463             case KeyEvent.KEYCODE_CUT:
9464                 if (event.hasNoModifiers() && canCut()) {
9465                     if (onTextContextMenuItem(ID_CUT)) {
9466                         return KEY_EVENT_HANDLED;
9467                     }
9468                 }
9469                 break;
9470 
9471             case KeyEvent.KEYCODE_COPY:
9472                 if (event.hasNoModifiers() && canCopy()) {
9473                     if (onTextContextMenuItem(ID_COPY)) {
9474                         return KEY_EVENT_HANDLED;
9475                     }
9476                 }
9477                 break;
9478 
9479             case KeyEvent.KEYCODE_PASTE:
9480                 if (event.hasNoModifiers() && canPaste()) {
9481                     if (onTextContextMenuItem(ID_PASTE)) {
9482                         return KEY_EVENT_HANDLED;
9483                     }
9484                 }
9485                 break;
9486 
9487             case KeyEvent.KEYCODE_FORWARD_DEL:
9488                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
9489                     if (onTextContextMenuItem(ID_CUT)) {
9490                         return KEY_EVENT_HANDLED;
9491                     }
9492                 }
9493                 break;
9494 
9495             case KeyEvent.KEYCODE_INSERT:
9496                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
9497                     if (onTextContextMenuItem(ID_COPY)) {
9498                         return KEY_EVENT_HANDLED;
9499                     }
9500                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
9501                     if (onTextContextMenuItem(ID_PASTE)) {
9502                         return KEY_EVENT_HANDLED;
9503                     }
9504                 }
9505                 break;
9506         }
9507 
9508         if (mEditor != null && mEditor.mKeyListener != null) {
9509             boolean doDown = true;
9510             if (otherEvent != null) {
9511                 try {
9512                     beginBatchEdit();
9513                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
9514                             otherEvent);
9515                     hideErrorIfUnchanged();
9516                     doDown = false;
9517                     if (handled) {
9518                         return KEY_EVENT_HANDLED;
9519                     }
9520                 } catch (AbstractMethodError e) {
9521                     // onKeyOther was added after 1.0, so if it isn't
9522                     // implemented we need to try to dispatch as a regular down.
9523                 } finally {
9524                     endBatchEdit();
9525                 }
9526             }
9527 
9528             if (doDown) {
9529                 beginBatchEdit();
9530                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
9531                         keyCode, event);
9532                 endBatchEdit();
9533                 hideErrorIfUnchanged();
9534                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
9535             }
9536         }
9537 
9538         // bug 650865: sometimes we get a key event before a layout.
9539         // don't try to move around if we don't know the layout.
9540 
9541         if (mMovement != null && mLayout != null) {
9542             boolean doDown = true;
9543             if (otherEvent != null) {
9544                 try {
9545                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
9546                     doDown = false;
9547                     if (handled) {
9548                         return KEY_EVENT_HANDLED;
9549                     }
9550                 } catch (AbstractMethodError e) {
9551                     // onKeyOther was added after 1.0, so if it isn't
9552                     // implemented we need to try to dispatch as a regular down.
9553                 }
9554             }
9555             if (doDown) {
9556                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
9557                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
9558                         mPreventDefaultMovement = true;
9559                     }
9560                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
9561                 }
9562             }
9563             // Consume arrows from keyboard devices to prevent focus leaving the editor.
9564             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
9565             // to move focus with arrows.
9566             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
9567                     && isDirectionalNavigationKey(keyCode)) {
9568                 return KEY_EVENT_HANDLED;
9569             }
9570         }
9571 
9572         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
9573                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
9574     }
9575 
9576     /**
9577      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
9578      * can be recorded.
9579      * @hide
9580      */
resetErrorChangedFlag()9581     public void resetErrorChangedFlag() {
9582         /*
9583          * Keep track of what the error was before doing the input
9584          * so that if an input filter changed the error, we leave
9585          * that error showing.  Otherwise, we take down whatever
9586          * error was showing when the user types something.
9587          */
9588         if (mEditor != null) mEditor.mErrorWasChanged = false;
9589     }
9590 
9591     /**
9592      * @hide
9593      */
hideErrorIfUnchanged()9594     public void hideErrorIfUnchanged() {
9595         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
9596             setError(null, null);
9597         }
9598     }
9599 
9600     @Override
onKeyUp(int keyCode, KeyEvent event)9601     public boolean onKeyUp(int keyCode, KeyEvent event) {
9602         if (!isEnabled()) {
9603             return super.onKeyUp(keyCode, event);
9604         }
9605 
9606         if (!KeyEvent.isModifierKey(keyCode)) {
9607             mPreventDefaultMovement = false;
9608         }
9609 
9610         switch (keyCode) {
9611             case KeyEvent.KEYCODE_DPAD_CENTER:
9612                 if (event.hasNoModifiers()) {
9613                     /*
9614                      * If there is a click listener, just call through to
9615                      * super, which will invoke it.
9616                      *
9617                      * If there isn't a click listener, try to show the soft
9618                      * input method.  (It will also
9619                      * call performClick(), but that won't do anything in
9620                      * this case.)
9621                      */
9622                     if (!hasOnClickListeners()) {
9623                         if (mMovement != null && mText instanceof Editable
9624                                 && mLayout != null && onCheckIsTextEditor()) {
9625                             InputMethodManager imm = getInputMethodManager();
9626                             viewClicked(imm);
9627                             if (imm != null && getShowSoftInputOnFocus()) {
9628                                 imm.showSoftInput(this, 0);
9629                             }
9630                         }
9631                     }
9632                 }
9633                 return super.onKeyUp(keyCode, event);
9634 
9635             case KeyEvent.KEYCODE_ENTER:
9636             case KeyEvent.KEYCODE_NUMPAD_ENTER:
9637                 if (event.hasNoModifiers()) {
9638                     if (mEditor != null && mEditor.mInputContentType != null
9639                             && mEditor.mInputContentType.onEditorActionListener != null
9640                             && mEditor.mInputContentType.enterDown) {
9641                         mEditor.mInputContentType.enterDown = false;
9642                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
9643                                 this, getActionIdForEnterEvent(), event)) {
9644                             return true;
9645                         }
9646                     }
9647 
9648                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
9649                             || shouldAdvanceFocusOnEnter()) {
9650                         /*
9651                          * If there is a click listener, just call through to
9652                          * super, which will invoke it.
9653                          *
9654                          * If there isn't a click listener, try to advance focus,
9655                          * but still call through to super, which will reset the
9656                          * pressed state and longpress state.  (It will also
9657                          * call performClick(), but that won't do anything in
9658                          * this case.)
9659                          */
9660                         if (!hasOnClickListeners()) {
9661                             View v = focusSearch(FOCUS_DOWN);
9662 
9663                             if (v != null) {
9664                                 if (!v.requestFocus(FOCUS_DOWN)) {
9665                                     throw new IllegalStateException("focus search returned a view "
9666                                             + "that wasn't able to take focus!");
9667                                 }
9668 
9669                                 /*
9670                                  * Return true because we handled the key; super
9671                                  * will return false because there was no click
9672                                  * listener.
9673                                  */
9674                                 super.onKeyUp(keyCode, event);
9675                                 return true;
9676                             } else if ((event.getFlags()
9677                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
9678                                 // No target for next focus, but make sure the IME
9679                                 // if this came from it.
9680                                 InputMethodManager imm = getInputMethodManager();
9681                                 if (imm != null && imm.isActive(this)) {
9682                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
9683                                 }
9684                             }
9685                         }
9686                     }
9687                     return super.onKeyUp(keyCode, event);
9688                 }
9689                 break;
9690         }
9691 
9692         if (mEditor != null && mEditor.mKeyListener != null) {
9693             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
9694                 return true;
9695             }
9696         }
9697 
9698         if (mMovement != null && mLayout != null) {
9699             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
9700                 return true;
9701             }
9702         }
9703 
9704         return super.onKeyUp(keyCode, event);
9705     }
9706 
getActionIdForEnterEvent()9707     private int getActionIdForEnterEvent() {
9708         // If it's not single line, no action
9709         if (!isSingleLine()) {
9710             return EditorInfo.IME_NULL;
9711         }
9712         // Return the action that was specified for Enter
9713         return getImeOptions() & EditorInfo.IME_MASK_ACTION;
9714     }
9715 
9716     @Override
onCheckIsTextEditor()9717     public boolean onCheckIsTextEditor() {
9718         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
9719     }
9720 
hasEditorInFocusSearchDirection(@ocusRealDirection int direction)9721     private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) {
9722         final View nextView = focusSearch(direction);
9723         return nextView != null && nextView.onCheckIsTextEditor();
9724     }
9725 
9726     @Override
onCreateInputConnection(EditorInfo outAttrs)9727     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
9728         if (onCheckIsTextEditor() && isEnabled()) {
9729             mEditor.createInputMethodStateIfNeeded();
9730             mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = 0;
9731             mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = 0;
9732 
9733             outAttrs.inputType = getInputType();
9734             if (mEditor.mInputContentType != null) {
9735                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
9736                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
9737                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
9738                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
9739                 outAttrs.extras = mEditor.mInputContentType.extras;
9740                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
9741             } else {
9742                 outAttrs.imeOptions = EditorInfo.IME_NULL;
9743                 outAttrs.hintLocales = null;
9744             }
9745             if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) {
9746                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
9747             }
9748             if (hasEditorInFocusSearchDirection(FOCUS_UP)) {
9749                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
9750             }
9751             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
9752                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
9753                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
9754                     // An action has not been set, but the enter key will move to
9755                     // the next focus, so set the action to that.
9756                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
9757                 } else {
9758                     // An action has not been set, and there is no focus to move
9759                     // to, so let's just supply a "done" action.
9760                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
9761                 }
9762                 if (!shouldAdvanceFocusOnEnter()) {
9763                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
9764                 }
9765             }
9766             if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) {
9767                 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT;
9768             }
9769             if (isMultilineInputType(outAttrs.inputType)) {
9770                 // Multi-line text editors should always show an enter key.
9771                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
9772             }
9773             outAttrs.hintText = mHint;
9774             outAttrs.targetInputMethodUser = mTextOperationUser;
9775             if (mText instanceof Editable) {
9776                 InputConnection ic = new EditableInputConnection(this);
9777                 outAttrs.initialSelStart = getSelectionStart();
9778                 outAttrs.initialSelEnd = getSelectionEnd();
9779                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
9780                 outAttrs.setInitialSurroundingText(mText);
9781                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
9782 
9783                 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
9784                 gestures.add(SelectGesture.class);
9785                 gestures.add(SelectRangeGesture.class);
9786                 gestures.add(DeleteGesture.class);
9787                 gestures.add(DeleteRangeGesture.class);
9788                 gestures.add(InsertGesture.class);
9789                 gestures.add(RemoveSpaceGesture.class);
9790                 gestures.add(JoinOrSplitGesture.class);
9791                 gestures.add(InsertModeGesture.class);
9792                 outAttrs.setSupportedHandwritingGestures(gestures);
9793 
9794                 Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>();
9795                 previews.add(SelectGesture.class);
9796                 previews.add(SelectRangeGesture.class);
9797                 previews.add(DeleteGesture.class);
9798                 previews.add(DeleteRangeGesture.class);
9799                 outAttrs.setSupportedHandwritingGesturePreviews(previews);
9800 
9801                 return ic;
9802             }
9803         }
9804         return null;
9805     }
9806 
9807     /**
9808      * Called back by the system to handle {@link InputConnection#requestCursorUpdates(int, int)}.
9809      *
9810      * @param cursorUpdateMode modes defined in {@link InputConnection.CursorUpdateMode}.
9811      * @param cursorUpdateFilter modes defined in {@link InputConnection.CursorUpdateFilter}.
9812      *
9813      * @hide
9814      */
onRequestCursorUpdatesInternal( @nputConnection.CursorUpdateMode int cursorUpdateMode, @InputConnection.CursorUpdateFilter int cursorUpdateFilter)9815     public void onRequestCursorUpdatesInternal(
9816             @InputConnection.CursorUpdateMode int cursorUpdateMode,
9817             @InputConnection.CursorUpdateFilter int cursorUpdateFilter) {
9818         mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = cursorUpdateMode;
9819         mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = cursorUpdateFilter;
9820         if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) == 0) {
9821             return;
9822         }
9823         if (isInLayout()) {
9824             // In this case, the view hierarchy is currently undergoing a layout pass.
9825             // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
9826             // pass is finished.
9827         } else {
9828             // This will schedule a layout pass of the view tree, and the layout event
9829             // eventually triggers IMM#updateCursorAnchorInfo.
9830             requestLayout();
9831         }
9832     }
9833 
9834     /**
9835      * If this TextView contains editable content, extract a portion of it
9836      * based on the information in <var>request</var> in to <var>outText</var>.
9837      * @return Returns true if the text was successfully extracted, else false.
9838      */
extractText(ExtractedTextRequest request, ExtractedText outText)9839     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
9840         createEditorIfNeeded();
9841         return mEditor.extractText(request, outText);
9842     }
9843 
9844     /**
9845      * This is used to remove all style-impacting spans from text before new
9846      * extracted text is being replaced into it, so that we don't have any
9847      * lingering spans applied during the replace.
9848      */
removeParcelableSpans(Spannable spannable, int start, int end)9849     static void removeParcelableSpans(Spannable spannable, int start, int end) {
9850         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
9851         int i = spans.length;
9852         while (i > 0) {
9853             i--;
9854             spannable.removeSpan(spans[i]);
9855         }
9856     }
9857 
9858     /**
9859      * Apply to this text view the given extracted text, as previously
9860      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
9861      */
setExtractedText(ExtractedText text)9862     public void setExtractedText(ExtractedText text) {
9863         Editable content = getEditableText();
9864         if (text.text != null) {
9865             if (content == null) {
9866                 setText(text.text, TextView.BufferType.EDITABLE);
9867             } else {
9868                 int start = 0;
9869                 int end = content.length();
9870 
9871                 if (text.partialStartOffset >= 0) {
9872                     final int N = content.length();
9873                     start = text.partialStartOffset;
9874                     if (start > N) start = N;
9875                     end = text.partialEndOffset;
9876                     if (end > N) end = N;
9877                 }
9878 
9879                 removeParcelableSpans(content, start, end);
9880                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
9881                     if (text.text instanceof Spanned) {
9882                         // OK to copy spans only.
9883                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
9884                                 Object.class, content, start);
9885                     }
9886                 } else {
9887                     content.replace(start, end, text.text);
9888                 }
9889             }
9890         }
9891 
9892         // Now set the selection position...  make sure it is in range, to
9893         // avoid crashes.  If this is a partial update, it is possible that
9894         // the underlying text may have changed, causing us problems here.
9895         // Also we just don't want to trust clients to do the right thing.
9896         Spannable sp = (Spannable) getText();
9897         final int N = sp.length();
9898         int start = text.selectionStart;
9899         if (start < 0) {
9900             start = 0;
9901         } else if (start > N) {
9902             start = N;
9903         }
9904         int end = text.selectionEnd;
9905         if (end < 0) {
9906             end = 0;
9907         } else if (end > N) {
9908             end = N;
9909         }
9910         Selection.setSelection(sp, start, end);
9911 
9912         // Finally, update the selection mode.
9913         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
9914             MetaKeyKeyListener.startSelecting(this, sp);
9915         } else {
9916             MetaKeyKeyListener.stopSelecting(this, sp);
9917         }
9918 
9919         setHintInternal(text.hint);
9920     }
9921 
9922     /**
9923      * @hide
9924      */
setExtracting(ExtractedTextRequest req)9925     public void setExtracting(ExtractedTextRequest req) {
9926         if (mEditor.mInputMethodState != null) {
9927             mEditor.mInputMethodState.mExtractedTextRequest = req;
9928         }
9929         // This would stop a possible selection mode, but no such mode is started in case
9930         // extracted mode will start. Some text is selected though, and will trigger an action mode
9931         // in the extracted view.
9932         mEditor.hideCursorAndSpanControllers();
9933         stopTextActionMode();
9934         if (mEditor.mSelectionModifierCursorController != null) {
9935             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
9936         }
9937     }
9938 
9939     /**
9940      * Called by the framework in response to a text completion from
9941      * the current input method, provided by it calling
9942      * {@link InputConnection#commitCompletion
9943      * InputConnection.commitCompletion()}.  The default implementation does
9944      * nothing; text views that are supporting auto-completion should override
9945      * this to do their desired behavior.
9946      *
9947      * @param text The auto complete text the user has selected.
9948      */
onCommitCompletion(CompletionInfo text)9949     public void onCommitCompletion(CompletionInfo text) {
9950         // intentionally empty
9951     }
9952 
9953     /**
9954      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
9955      * dictionary) from the current input method, provided by it calling
9956      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
9957      * The default implementation flashes the background of the corrected word to provide
9958      * feedback to the user.
9959      *
9960      * @param info The auto correct info about the text that was corrected.
9961      */
onCommitCorrection(CorrectionInfo info)9962     public void onCommitCorrection(CorrectionInfo info) {
9963         if (mEditor != null) mEditor.onCommitCorrection(info);
9964     }
9965 
beginBatchEdit()9966     public void beginBatchEdit() {
9967         if (mEditor != null) mEditor.beginBatchEdit();
9968     }
9969 
endBatchEdit()9970     public void endBatchEdit() {
9971         if (mEditor != null) mEditor.endBatchEdit();
9972     }
9973 
9974     /**
9975      * Called by the framework in response to a request to begin a batch
9976      * of edit operations through a call to link {@link #beginBatchEdit()}.
9977      */
onBeginBatchEdit()9978     public void onBeginBatchEdit() {
9979         // intentionally empty
9980     }
9981 
9982     /**
9983      * Called by the framework in response to a request to end a batch
9984      * of edit operations through a call to link {@link #endBatchEdit}.
9985      */
onEndBatchEdit()9986     public void onEndBatchEdit() {
9987         // intentionally empty
9988     }
9989 
9990     /** @hide */
onPerformSpellCheck()9991     public void onPerformSpellCheck() {
9992         if (mEditor != null && mEditor.mSpellChecker != null) {
9993             mEditor.mSpellChecker.onPerformSpellCheck();
9994         }
9995     }
9996 
9997     /**
9998      * Called by the framework in response to a private command from the
9999      * current method, provided by it calling
10000      * {@link InputConnection#performPrivateCommand
10001      * InputConnection.performPrivateCommand()}.
10002      *
10003      * @param action The action name of the command.
10004      * @param data Any additional data for the command.  This may be null.
10005      * @return Return true if you handled the command, else false.
10006      */
onPrivateIMECommand(String action, Bundle data)10007     public boolean onPrivateIMECommand(String action, Bundle data) {
10008         return false;
10009     }
10010 
10011     /**
10012      * Return whether the text is transformed and has {@link OffsetMapping}.
10013      * @hide
10014      */
isOffsetMappingAvailable()10015     public boolean isOffsetMappingAvailable() {
10016         return mTransformation != null && mTransformed instanceof OffsetMapping;
10017     }
10018 
10019     /** @hide */
previewHandwritingGesture( @onNull PreviewableHandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal)10020     public boolean previewHandwritingGesture(
10021             @NonNull PreviewableHandwritingGesture gesture,
10022             @Nullable CancellationSignal cancellationSignal) {
10023         if (gesture instanceof SelectGesture) {
10024             performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true);
10025         } else if (gesture instanceof SelectRangeGesture) {
10026             performHandwritingSelectRangeGesture(
10027                     (SelectRangeGesture) gesture, /* isPreview= */ true);
10028         } else if (gesture instanceof DeleteGesture) {
10029             performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true);
10030         } else if (gesture instanceof DeleteRangeGesture) {
10031             performHandwritingDeleteRangeGesture(
10032                     (DeleteRangeGesture) gesture, /* isPreview= */ true);
10033         } else {
10034             return false;
10035         }
10036         if (cancellationSignal != null) {
10037             cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight);
10038         }
10039         return true;
10040     }
10041 
10042     /** @hide */
performHandwritingSelectGesture(@onNull SelectGesture gesture)10043     public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
10044         return performHandwritingSelectGesture(gesture, /* isPreview= */ false);
10045     }
10046 
performHandwritingSelectGesture(@onNull SelectGesture gesture, boolean isPreview)10047     private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) {
10048         if (isOffsetMappingAvailable()) {
10049             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10050         }
10051         int[] range = getRangeForRect(
10052                 convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
10053                 gesture.getGranularity());
10054         if (range == null) {
10055             return handleGestureFailure(gesture, isPreview);
10056         }
10057         return performHandwritingSelectGesture(range, isPreview);
10058     }
10059 
performHandwritingSelectGesture(int[] range, boolean isPreview)10060     private int performHandwritingSelectGesture(int[] range, boolean isPreview) {
10061         if (isPreview) {
10062             setSelectGesturePreviewHighlight(range[0], range[1]);
10063         } else {
10064             Selection.setSelection(getEditableText(), range[0], range[1]);
10065             mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
10066         }
10067         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10068     }
10069 
10070     /** @hide */
performHandwritingSelectRangeGesture(@onNull SelectRangeGesture gesture)10071     public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) {
10072         return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false);
10073     }
10074 
performHandwritingSelectRangeGesture( @onNull SelectRangeGesture gesture, boolean isPreview)10075     private int performHandwritingSelectRangeGesture(
10076             @NonNull SelectRangeGesture gesture, boolean isPreview) {
10077         if (isOffsetMappingAvailable()) {
10078             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10079         }
10080         int[] startRange = getRangeForRect(
10081                 convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
10082                 gesture.getGranularity());
10083         if (startRange == null) {
10084             return handleGestureFailure(gesture, isPreview);
10085         }
10086         int[] endRange = getRangeForRect(
10087                 convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()),
10088                 gesture.getGranularity());
10089         if (endRange == null) {
10090             return handleGestureFailure(gesture, isPreview);
10091         }
10092         int[] range = new int[] {
10093                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
10094         };
10095         return performHandwritingSelectGesture(range, isPreview);
10096     }
10097 
10098     /** @hide */
performHandwritingDeleteGesture(@onNull DeleteGesture gesture)10099     public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
10100         return performHandwritingDeleteGesture(gesture, /* isPreview= */ false);
10101     }
10102 
performHandwritingDeleteGesture(@onNull DeleteGesture gesture, boolean isPreview)10103     private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) {
10104         if (isOffsetMappingAvailable()) {
10105             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10106         }
10107         int[] range = getRangeForRect(
10108                 convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
10109                 gesture.getGranularity());
10110         if (range == null) {
10111             return handleGestureFailure(gesture, isPreview);
10112         }
10113         return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
10114     }
10115 
performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview)10116     private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) {
10117         if (isPreview) {
10118             setDeleteGesturePreviewHighlight(range[0], range[1]);
10119         } else {
10120             if (granularity == HandwritingGesture.GRANULARITY_WORD) {
10121                 range = adjustHandwritingDeleteGestureRange(range);
10122             }
10123 
10124             Selection.setSelection(getEditableText(), range[0]);
10125             getEditableText().delete(range[0], range[1]);
10126         }
10127         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10128     }
10129 
10130     /** @hide */
performHandwritingDeleteRangeGesture(@onNull DeleteRangeGesture gesture)10131     public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) {
10132         return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false);
10133     }
10134 
performHandwritingDeleteRangeGesture( @onNull DeleteRangeGesture gesture, boolean isPreview)10135     private int performHandwritingDeleteRangeGesture(
10136             @NonNull DeleteRangeGesture gesture, boolean isPreview) {
10137         if (isOffsetMappingAvailable()) {
10138             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10139         }
10140         int[] startRange = getRangeForRect(
10141                 convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
10142                 gesture.getGranularity());
10143         if (startRange == null) {
10144             return handleGestureFailure(gesture, isPreview);
10145         }
10146         int[] endRange = getRangeForRect(
10147                 convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()),
10148                 gesture.getGranularity());
10149         if (endRange == null) {
10150             return handleGestureFailure(gesture, isPreview);
10151         }
10152         int[] range = new int[] {
10153                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
10154         };
10155         return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
10156     }
10157 
adjustHandwritingDeleteGestureRange(int[] range)10158     private int[] adjustHandwritingDeleteGestureRange(int[] range) {
10159         // For handwriting delete gestures with word granularity, adjust the start and end offsets
10160         // to remove extra whitespace around the deleted text.
10161 
10162         int start = range[0];
10163         int end = range[1];
10164 
10165         // If the deleted text is at the start of the text, the behavior is the same as the case
10166         // where the deleted text follows a new line character.
10167         int codePointBeforeStart = start > 0
10168                 ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT;
10169         // If the deleted text is at the end of the text, the behavior is the same as the case where
10170         // the deleted text precedes a new line character.
10171         int codePointAtEnd = end < mText.length()
10172                 ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT;
10173 
10174         if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)
10175                 && (TextUtils.isWhitespace(codePointAtEnd)
10176                         || TextUtils.isPunctuation(codePointAtEnd))) {
10177             // Remove whitespace (except new lines) before the deleted text, in these cases:
10178             // - There is whitespace following the deleted text
10179             //     e.g. "one [deleted] three" -> "one | three" -> "one| three"
10180             // - There is punctuation following the deleted text
10181             //     e.g. "one [deleted]!" -> "one |!" -> "one|!"
10182             // - There is a new line following the deleted text
10183             //     e.g. "one [deleted]\n" -> "one |\n" -> "one|\n"
10184             // - The deleted text is at the end of the text
10185             //     e.g. "one [deleted]" -> "one |" -> "one|"
10186             // (The pipe | indicates the cursor position.)
10187             do {
10188                 start -= Character.charCount(codePointBeforeStart);
10189                 if (start == 0) break;
10190                 codePointBeforeStart = Character.codePointBefore(mText, start);
10191             } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart));
10192             return new int[] {start, end};
10193         }
10194 
10195         if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)
10196                 && (TextUtils.isWhitespace(codePointBeforeStart)
10197                         || TextUtils.isPunctuation(codePointBeforeStart))) {
10198             // Remove whitespace (except new lines) after the deleted text, in these cases:
10199             // - There is punctuation preceding the deleted text
10200             //     e.g. "([deleted] two)" -> "(| two)" -> "(|two)"
10201             // - There is a new line preceding the deleted text
10202             //     e.g. "\n[deleted] two" -> "\n| two" -> "\n|two"
10203             // - The deleted text is at the start of the text
10204             //     e.g. "[deleted] two" -> "| two" -> "|two"
10205             // (The pipe | indicates the cursor position.)
10206             do {
10207                 end += Character.charCount(codePointAtEnd);
10208                 if (end == mText.length()) break;
10209                 codePointAtEnd = Character.codePointAt(mText, end);
10210             } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd));
10211             return new int[] {start, end};
10212         }
10213 
10214         // Return the original range.
10215         return range;
10216     }
10217 
10218     /** @hide */
10219     public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
10220         if (isOffsetMappingAvailable()) {
10221             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10222         }
10223         PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
10224         int line = getLineForHandwritingGesture(point);
10225         if (line == -1) {
10226             return handleGestureFailure(gesture);
10227         }
10228         int offset = mLayout.getOffsetForHorizontal(line, point.x);
10229         String textToInsert = gesture.getTextToInsert();
10230         return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture);
10231         // TODO(b/243980426): Insert extra spaces if necessary.
10232     }
10233 
10234     /** @hide */
10235     public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) {
10236         if (isOffsetMappingAvailable()) {
10237             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10238         }
10239         PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());
10240         PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint());
10241 
10242         // The operation should be applied to the first line of text containing one of the points.
10243         int startPointLine = getLineForHandwritingGesture(startPoint);
10244         int endPointLine = getLineForHandwritingGesture(endPoint);
10245         int line;
10246         if (startPointLine == -1) {
10247             if (endPointLine == -1) {
10248                 return handleGestureFailure(gesture);
10249             }
10250             line = endPointLine;
10251         } else {
10252             line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine);
10253         }
10254 
10255         // The operation should be applied to all characters touched by the line joining the points.
10256         float lineVerticalCenter = (mLayout.getLineTop(line)
10257                 + mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) / 2f;
10258         // Create a rectangle which is +/-0.1f around the line's vertical center, so that the
10259         // rectangle doesn't touch the line above or below. (The line height is at least 1f.)
10260         RectF area = new RectF(
10261                 Math.min(startPoint.x, endPoint.x),
10262                 lineVerticalCenter + 0.1f,
10263                 Math.max(startPoint.x, endPoint.x),
10264                 lineVerticalCenter - 0.1f);
10265         int[] range = mLayout.getRangeForRect(
10266                 area, new GraphemeClusterSegmentFinder(mText, mTextPaint),
10267                 Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
10268         if (range == null) {
10269             return handleGestureFailure(gesture);
10270         }
10271         int startOffset = range[0];
10272         int endOffset = range[1];
10273         // TODO(b/247557062): This doesn't handle bidirectional text correctly.
10274 
10275         Pattern whitespacePattern = getWhitespacePattern();
10276         Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
10277         int lastRemoveOffset = -1;
10278         while (matcher.find()) {
10279             lastRemoveOffset = startOffset + matcher.start();
10280             getEditableText().delete(lastRemoveOffset, startOffset + matcher.end());
10281             startOffset = lastRemoveOffset;
10282             endOffset -= matcher.end() - matcher.start();
10283             if (startOffset == endOffset) {
10284                 break;
10285             }
10286             matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
10287         }
10288         if (lastRemoveOffset == -1) {
10289             return handleGestureFailure(gesture);
10290         }
10291         Selection.setSelection(getEditableText(), lastRemoveOffset);
10292         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10293     }
10294 
10295     /** @hide */
10296     public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {
10297         if (isOffsetMappingAvailable()) {
10298             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10299         }
10300         PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint());
10301 
10302         int line = getLineForHandwritingGesture(point);
10303         if (line == -1) {
10304             return handleGestureFailure(gesture);
10305         }
10306 
10307         int startOffset = mLayout.getOffsetForHorizontal(line, point.x);
10308         if (mLayout.isLevelBoundary(startOffset)) {
10309             // TODO(b/247551937): Support gesture at level boundaries.
10310             return handleGestureFailure(gesture);
10311         }
10312 
10313         int endOffset = startOffset;
10314         while (startOffset > 0) {
10315             int codePointBeforeStart = Character.codePointBefore(mText, startOffset);
10316             if (!TextUtils.isWhitespace(codePointBeforeStart)) {
10317                 break;
10318             }
10319             startOffset -= Character.charCount(codePointBeforeStart);
10320         }
10321         while (endOffset < mText.length()) {
10322             int codePointAtEnd = Character.codePointAt(mText, endOffset);
10323             if (!TextUtils.isWhitespace(codePointAtEnd)) {
10324                 break;
10325             }
10326             endOffset += Character.charCount(codePointAtEnd);
10327         }
10328         if (startOffset < endOffset) {
10329             Selection.setSelection(getEditableText(), startOffset);
10330             getEditableText().delete(startOffset, endOffset);
10331             return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10332         } else {
10333             // No whitespace found, so insert a space.
10334             return tryInsertTextForHandwritingGesture(startOffset, " ", gesture);
10335         }
10336     }
10337 
10338     /** @hide */
10339     public int performHandwritingInsertModeGesture(@NonNull InsertModeGesture gesture) {
10340         final PointF insertPoint =
10341                 convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
10342         final int line = getLineForHandwritingGesture(insertPoint);
10343         final CancellationSignal cancellationSignal = gesture.getCancellationSignal();
10344 
10345         // If no cancellationSignal is provided, don't enter the insert mode.
10346         if (line == -1 || cancellationSignal == null) {
10347             return handleGestureFailure(gesture);
10348         }
10349 
10350         final int offset = mLayout.getOffsetForHorizontal(line, insertPoint.x);
10351 
10352         if (!mEditor.enterInsertMode(offset)) {
10353             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10354         }
10355         cancellationSignal.setOnCancelListener(() -> mEditor.exitInsertMode());
10356         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10357     }
10358 
10359     private int handleGestureFailure(HandwritingGesture gesture) {
10360         return handleGestureFailure(gesture, /* isPreview= */ false);
10361     }
10362 
10363     private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) {
10364         clearGesturePreviewHighlight();
10365         if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) {
10366             getEditableText()
10367                     .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText());
10368             return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK;
10369         }
10370         return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10371     }
10372 
10373     /**
10374      * Returns the closest line such that the point is either inside the line bounds or within
10375      * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns
10376      * -1 if the point is not within the margin of any line bounds.
10377      */
10378     private int getLineForHandwritingGesture(PointF point) {
10379         int line = mLayout.getLineForVertical((int) point.y);
10380         int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin();
10381         if (line < mLayout.getLineCount() - 1
10382                 && point.y > mLayout.getLineBottom(line) - lineMargin
10383                 && point.y
10384                         > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) {
10385             // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical
10386             // returns i. If the point is within lineMargin of line (i + 1), and closer to line
10387             // (i + 1) than line i, then the gesture operation should be applied to line (i + 1).
10388             line++;
10389         } else if (point.y < mLayout.getLineTop(line) - lineMargin
10390                 || point.y
10391                         > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)
10392                                 + lineMargin) {
10393             // The point is not within lineMargin of a line.
10394             return -1;
10395         }
10396         if (point.x < -lineMargin || point.x > mLayout.getWidth() + lineMargin) {
10397             // The point is not within lineMargin of a line.
10398             return -1;
10399         }
10400         return line;
10401     }
10402 
10403     @Nullable
10404     private int[] getRangeForRect(@NonNull RectF area, int granularity) {
10405         SegmentFinder segmentFinder;
10406         if (granularity == HandwritingGesture.GRANULARITY_WORD) {
10407             WordIterator wordIterator = getWordIterator();
10408             wordIterator.setCharSequence(mText, 0, mText.length());
10409             segmentFinder = new WordSegmentFinder(mText, wordIterator);
10410         } else {
10411             segmentFinder = new GraphemeClusterSegmentFinder(mText, mTextPaint);
10412         }
10413 
10414         return mLayout.getRangeForRect(
10415                 area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
10416     }
10417 
10418     private int tryInsertTextForHandwritingGesture(
10419             int offset, String textToInsert, HandwritingGesture gesture) {
10420         // A temporary cursor span is placed at the insertion offset. The span will be pushed
10421         // forward when text is inserted, then the real cursor can be placed after the inserted
10422         // text. A temporary cursor span is used in order to avoid modifying the real selection span
10423         // in the case that the text is filtered out.
10424         Editable editableText = getEditableText();
10425         if (mTempCursor == null) {
10426             mTempCursor = new NoCopySpan.Concrete();
10427         }
10428         editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT);
10429 
10430         editableText.insert(offset, textToInsert);
10431 
10432         int newOffset = editableText.getSpanStart(mTempCursor);
10433         editableText.removeSpan(mTempCursor);
10434         if (newOffset == offset) {
10435             // The inserted text was filtered out.
10436             return handleGestureFailure(gesture);
10437         } else {
10438             // Place the cursor after the inserted text.
10439             Selection.setSelection(editableText, newOffset);
10440             return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10441         }
10442     }
10443 
10444     private Pattern getWhitespacePattern() {
10445         if (mWhitespacePattern == null) {
10446             mWhitespacePattern = Pattern.compile("\\s+");
10447         }
10448         return mWhitespacePattern;
10449     }
10450 
10451     /** @hide */
10452     @VisibleForTesting
10453     @UnsupportedAppUsage
10454     public void nullLayouts() {
10455         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
10456             mSavedLayout = (BoringLayout) mLayout;
10457         }
10458         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
10459             mSavedHintLayout = (BoringLayout) mHintLayout;
10460         }
10461 
10462         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
10463 
10464         mBoring = mHintBoring = null;
10465 
10466         // Since it depends on the value of mLayout
10467         if (mEditor != null) mEditor.prepareCursorControllers();
10468     }
10469 
10470     /**
10471      * Make a new Layout based on the already-measured size of the view,
10472      * on the assumption that it was measured correctly at some point.
10473      */
10474     @UnsupportedAppUsage
10475     private void assumeLayout() {
10476         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10477 
10478         if (width < 1) {
10479             width = 0;
10480         }
10481 
10482         int physicalWidth = width;
10483 
10484         if (mHorizontallyScrolling) {
10485             width = VERY_WIDE;
10486         }
10487 
10488         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
10489                       physicalWidth, false);
10490     }
10491 
10492     @UnsupportedAppUsage
10493     private Layout.Alignment getLayoutAlignment() {
10494         Layout.Alignment alignment;
10495         switch (getTextAlignment()) {
10496             case TEXT_ALIGNMENT_GRAVITY:
10497                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
10498                     case Gravity.START:
10499                         alignment = Layout.Alignment.ALIGN_NORMAL;
10500                         break;
10501                     case Gravity.END:
10502                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
10503                         break;
10504                     case Gravity.LEFT:
10505                         alignment = Layout.Alignment.ALIGN_LEFT;
10506                         break;
10507                     case Gravity.RIGHT:
10508                         alignment = Layout.Alignment.ALIGN_RIGHT;
10509                         break;
10510                     case Gravity.CENTER_HORIZONTAL:
10511                         alignment = Layout.Alignment.ALIGN_CENTER;
10512                         break;
10513                     default:
10514                         alignment = Layout.Alignment.ALIGN_NORMAL;
10515                         break;
10516                 }
10517                 break;
10518             case TEXT_ALIGNMENT_TEXT_START:
10519                 alignment = Layout.Alignment.ALIGN_NORMAL;
10520                 break;
10521             case TEXT_ALIGNMENT_TEXT_END:
10522                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
10523                 break;
10524             case TEXT_ALIGNMENT_CENTER:
10525                 alignment = Layout.Alignment.ALIGN_CENTER;
10526                 break;
10527             case TEXT_ALIGNMENT_VIEW_START:
10528                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
10529                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
10530                 break;
10531             case TEXT_ALIGNMENT_VIEW_END:
10532                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
10533                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
10534                 break;
10535             case TEXT_ALIGNMENT_INHERIT:
10536                 // This should never happen as we have already resolved the text alignment
10537                 // but better safe than sorry so we just fall through
10538             default:
10539                 alignment = Layout.Alignment.ALIGN_NORMAL;
10540                 break;
10541         }
10542         return alignment;
10543     }
10544 
10545     /**
10546      * The width passed in is now the desired layout width,
10547      * not the full view width with padding.
10548      * {@hide}
10549      */
10550     @VisibleForTesting
10551     @UnsupportedAppUsage
10552     public void makeNewLayout(int wantWidth, int hintWidth,
10553                                  BoringLayout.Metrics boring,
10554                                  BoringLayout.Metrics hintBoring,
10555                                  int ellipsisWidth, boolean bringIntoView) {
10556         stopMarquee();
10557 
10558         // Update "old" cached values
10559         mOldMaximum = mMaximum;
10560         mOldMaxMode = mMaxMode;
10561 
10562         mHighlightPathBogus = true;
10563         mHighlightPathsBogus = true;
10564 
10565         if (wantWidth < 0) {
10566             wantWidth = 0;
10567         }
10568         if (hintWidth < 0) {
10569             hintWidth = 0;
10570         }
10571 
10572         Layout.Alignment alignment = getLayoutAlignment();
10573         final boolean testDirChange = mSingleLine && mLayout != null
10574                 && (alignment == Layout.Alignment.ALIGN_NORMAL
10575                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
10576         int oldDir = 0;
10577         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
10578         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
10579         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
10580                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
10581         TruncateAt effectiveEllipsize = mEllipsize;
10582         if (mEllipsize == TruncateAt.MARQUEE
10583                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10584             effectiveEllipsize = TruncateAt.END_SMALL;
10585         }
10586 
10587         if (mTextDir == null) {
10588             mTextDir = getTextDirectionHeuristic();
10589         }
10590 
10591         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
10592                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
10593         if (switchEllipsize) {
10594             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
10595                     ? TruncateAt.END : TruncateAt.MARQUEE;
10596             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
10597                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
10598         }
10599 
10600         shouldEllipsize = mEllipsize != null;
10601         mHintLayout = null;
10602 
10603         if (mHint != null) {
10604             if (shouldEllipsize) hintWidth = wantWidth;
10605 
10606             if (hintBoring == UNKNOWN_BORING) {
10607                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
10608                         isFallbackLineSpacingForBoringLayout(), mHintBoring);
10609                 if (hintBoring != null) {
10610                     mHintBoring = hintBoring;
10611                 }
10612             }
10613 
10614             if (hintBoring != null) {
10615                 if (hintBoring.width <= hintWidth
10616                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
10617                     if (mSavedHintLayout != null) {
10618                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
10619                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10620                                 hintBoring, mIncludePad);
10621                     } else {
10622                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
10623                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10624                                 hintBoring, mIncludePad);
10625                     }
10626 
10627                     mSavedHintLayout = (BoringLayout) mHintLayout;
10628                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
10629                     if (mSavedHintLayout != null) {
10630                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
10631                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10632                                 hintBoring, mIncludePad, mEllipsize,
10633                                 ellipsisWidth);
10634                     } else {
10635                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
10636                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10637                                 hintBoring, mIncludePad, mEllipsize,
10638                                 ellipsisWidth);
10639                     }
10640                 }
10641             }
10642             // TODO: code duplication with makeSingleLayout()
10643             if (mHintLayout == null) {
10644                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
10645                         mHint.length(), mTextPaint, hintWidth)
10646                         .setAlignment(alignment)
10647                         .setTextDirection(mTextDir)
10648                         .setLineSpacing(mSpacingAdd, mSpacingMult)
10649                         .setIncludePad(mIncludePad)
10650                         .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
10651                         .setBreakStrategy(mBreakStrategy)
10652                         .setHyphenationFrequency(mHyphenationFrequency)
10653                         .setJustificationMode(mJustificationMode)
10654                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
10655                         .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
10656                                 mLineBreakStyle, mLineBreakWordStyle));
10657                 if (shouldEllipsize) {
10658                     builder.setEllipsize(mEllipsize)
10659                             .setEllipsizedWidth(ellipsisWidth);
10660                 }
10661                 mHintLayout = builder.build();
10662             }
10663         }
10664 
10665         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
10666             registerForPreDraw();
10667         }
10668 
10669         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10670             if (!compressText(ellipsisWidth)) {
10671                 final int height = mLayoutParams.height;
10672                 // If the size of the view does not depend on the size of the text, try to
10673                 // start the marquee immediately
10674                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
10675                     startMarquee();
10676                 } else {
10677                     // Defer the start of the marquee until we know our width (see setFrame())
10678                     mRestartMarquee = true;
10679                 }
10680             }
10681         }
10682 
10683         // CursorControllers need a non-null mLayout
10684         if (mEditor != null) mEditor.prepareCursorControllers();
10685     }
10686 
10687     /**
10688      * Returns true if DynamicLayout is required
10689      *
10690      * @hide
10691      */
10692     @VisibleForTesting
10693     public boolean useDynamicLayout() {
10694         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
10695     }
10696 
10697     /**
10698      * @hide
10699      */
10700     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
10701             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
10702             boolean useSaved) {
10703         Layout result = null;
10704         if (useDynamicLayout()) {
10705             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
10706                     wantWidth)
10707                     .setDisplayText(mTransformed)
10708                     .setAlignment(alignment)
10709                     .setTextDirection(mTextDir)
10710                     .setLineSpacing(mSpacingAdd, mSpacingMult)
10711                     .setIncludePad(mIncludePad)
10712                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
10713                     .setBreakStrategy(mBreakStrategy)
10714                     .setHyphenationFrequency(mHyphenationFrequency)
10715                     .setJustificationMode(mJustificationMode)
10716                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
10717                     .setEllipsizedWidth(ellipsisWidth);
10718             result = builder.build();
10719         } else {
10720             if (boring == UNKNOWN_BORING) {
10721                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
10722                         isFallbackLineSpacingForBoringLayout(), mBoring);
10723                 if (boring != null) {
10724                     mBoring = boring;
10725                 }
10726             }
10727 
10728             if (boring != null) {
10729                 if (boring.width <= wantWidth
10730                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
10731                     if (useSaved && mSavedLayout != null) {
10732                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
10733                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
10734                                 boring, mIncludePad);
10735                     } else {
10736                         result = BoringLayout.make(mTransformed, mTextPaint,
10737                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
10738                                 boring, mIncludePad);
10739                     }
10740 
10741                     if (useSaved) {
10742                         mSavedLayout = (BoringLayout) result;
10743                     }
10744                 } else if (shouldEllipsize && boring.width <= wantWidth) {
10745                     if (useSaved && mSavedLayout != null) {
10746                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
10747                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
10748                                 boring, mIncludePad, effectiveEllipsize,
10749                                 ellipsisWidth);
10750                     } else {
10751                         result = BoringLayout.make(mTransformed, mTextPaint,
10752                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
10753                                 boring, mIncludePad, effectiveEllipsize,
10754                                 ellipsisWidth);
10755                     }
10756                 }
10757             }
10758         }
10759         if (result == null) {
10760             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
10761                     0, mTransformed.length(), mTextPaint, wantWidth)
10762                     .setAlignment(alignment)
10763                     .setTextDirection(mTextDir)
10764                     .setLineSpacing(mSpacingAdd, mSpacingMult)
10765                     .setIncludePad(mIncludePad)
10766                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
10767                     .setBreakStrategy(mBreakStrategy)
10768                     .setHyphenationFrequency(mHyphenationFrequency)
10769                     .setJustificationMode(mJustificationMode)
10770                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
10771                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
10772                             mLineBreakStyle, mLineBreakWordStyle));
10773             if (shouldEllipsize) {
10774                 builder.setEllipsize(effectiveEllipsize)
10775                         .setEllipsizedWidth(ellipsisWidth);
10776             }
10777             result = builder.build();
10778         }
10779         return result;
10780     }
10781 
10782     @UnsupportedAppUsage
10783     private boolean compressText(float width) {
10784         if (isHardwareAccelerated()) return false;
10785 
10786         // Only compress the text if it hasn't been compressed by the previous pass
10787         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
10788                 && mTextPaint.getTextScaleX() == 1.0f) {
10789             final float textWidth = mLayout.getLineWidth(0);
10790             final float overflow = (textWidth + 1.0f - width) / width;
10791             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
10792                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
10793                 post(new Runnable() {
10794                     public void run() {
10795                         requestLayout();
10796                     }
10797                 });
10798                 return true;
10799             }
10800         }
10801 
10802         return false;
10803     }
10804 
10805     private static int desired(Layout layout) {
10806         int n = layout.getLineCount();
10807         CharSequence text = layout.getText();
10808         float max = 0;
10809 
10810         // if any line was wrapped, we can't use it.
10811         // but it's ok for the last line not to have a newline
10812 
10813         for (int i = 0; i < n - 1; i++) {
10814             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
10815                 return -1;
10816             }
10817         }
10818 
10819         for (int i = 0; i < n; i++) {
10820             max = Math.max(max, layout.getLineMax(i));
10821         }
10822 
10823         return (int) Math.ceil(max);
10824     }
10825 
10826     /**
10827      * Set whether the TextView includes extra top and bottom padding to make
10828      * room for accents that go above the normal ascent and descent.
10829      * The default is true.
10830      *
10831      * @see #getIncludeFontPadding()
10832      *
10833      * @attr ref android.R.styleable#TextView_includeFontPadding
10834      */
10835     public void setIncludeFontPadding(boolean includepad) {
10836         if (mIncludePad != includepad) {
10837             mIncludePad = includepad;
10838 
10839             if (mLayout != null) {
10840                 nullLayouts();
10841                 requestLayout();
10842                 invalidate();
10843             }
10844         }
10845     }
10846 
10847     /**
10848      * Gets whether the TextView includes extra top and bottom padding to make
10849      * room for accents that go above the normal ascent and descent.
10850      *
10851      * @see #setIncludeFontPadding(boolean)
10852      *
10853      * @attr ref android.R.styleable#TextView_includeFontPadding
10854      */
10855     @InspectableProperty
10856     public boolean getIncludeFontPadding() {
10857         return mIncludePad;
10858     }
10859 
10860     /** @hide */
10861     @VisibleForTesting
10862     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
10863 
10864     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)10865     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
10866         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
10867         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
10868         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
10869         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
10870 
10871         int width;
10872         int height;
10873 
10874         BoringLayout.Metrics boring = UNKNOWN_BORING;
10875         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
10876 
10877         if (mTextDir == null) {
10878             mTextDir = getTextDirectionHeuristic();
10879         }
10880 
10881         int des = -1;
10882         boolean fromexisting = false;
10883         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
10884                 ?  (float) widthSize : Float.MAX_VALUE;
10885 
10886         if (widthMode == MeasureSpec.EXACTLY) {
10887             // Parent has told us how big to be. So be it.
10888             width = widthSize;
10889         } else {
10890             if (mLayout != null && mEllipsize == null) {
10891                 des = desired(mLayout);
10892             }
10893 
10894             if (des < 0) {
10895                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
10896                         isFallbackLineSpacingForBoringLayout(), mBoring);
10897                 if (boring != null) {
10898                     mBoring = boring;
10899                 }
10900             } else {
10901                 fromexisting = true;
10902             }
10903 
10904             if (boring == null || boring == UNKNOWN_BORING) {
10905                 if (des < 0) {
10906                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
10907                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
10908                 }
10909                 width = des;
10910             } else {
10911                 width = boring.width;
10912             }
10913 
10914             final Drawables dr = mDrawables;
10915             if (dr != null) {
10916                 width = Math.max(width, dr.mDrawableWidthTop);
10917                 width = Math.max(width, dr.mDrawableWidthBottom);
10918             }
10919 
10920             if (mHint != null) {
10921                 int hintDes = -1;
10922                 int hintWidth;
10923 
10924                 if (mHintLayout != null && mEllipsize == null) {
10925                     hintDes = desired(mHintLayout);
10926                 }
10927 
10928                 if (hintDes < 0) {
10929                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
10930                             isFallbackLineSpacingForBoringLayout(), mHintBoring);
10931                     if (hintBoring != null) {
10932                         mHintBoring = hintBoring;
10933                     }
10934                 }
10935 
10936                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
10937                     if (hintDes < 0) {
10938                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
10939                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
10940                     }
10941                     hintWidth = hintDes;
10942                 } else {
10943                     hintWidth = hintBoring.width;
10944                 }
10945 
10946                 if (hintWidth > width) {
10947                     width = hintWidth;
10948                 }
10949             }
10950 
10951             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
10952 
10953             if (mMaxWidthMode == EMS) {
10954                 width = Math.min(width, mMaxWidth * getLineHeight());
10955             } else {
10956                 width = Math.min(width, mMaxWidth);
10957             }
10958 
10959             if (mMinWidthMode == EMS) {
10960                 width = Math.max(width, mMinWidth * getLineHeight());
10961             } else {
10962                 width = Math.max(width, mMinWidth);
10963             }
10964 
10965             // Check against our minimum width
10966             width = Math.max(width, getSuggestedMinimumWidth());
10967 
10968             if (widthMode == MeasureSpec.AT_MOST) {
10969                 width = Math.min(widthSize, width);
10970             }
10971         }
10972 
10973         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
10974         int unpaddedWidth = want;
10975 
10976         if (mHorizontallyScrolling) want = VERY_WIDE;
10977 
10978         int hintWant = want;
10979         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
10980 
10981         if (mLayout == null) {
10982             makeNewLayout(want, hintWant, boring, hintBoring,
10983                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
10984         } else {
10985             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
10986                     || (mLayout.getEllipsizedWidth()
10987                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
10988 
10989             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
10990                     && (want > mLayout.getWidth())
10991                     && (mLayout instanceof BoringLayout
10992                             || (fromexisting && des >= 0 && des <= want));
10993 
10994             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
10995 
10996             if (layoutChanged || maximumChanged) {
10997                 if (!maximumChanged && widthChanged) {
10998                     mLayout.increaseWidthTo(want);
10999                 } else {
11000                     makeNewLayout(want, hintWant, boring, hintBoring,
11001                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
11002                 }
11003             } else {
11004                 // Nothing has changed
11005             }
11006         }
11007 
11008         if (heightMode == MeasureSpec.EXACTLY) {
11009             // Parent has told us how big to be. So be it.
11010             height = heightSize;
11011             mDesiredHeightAtMeasure = -1;
11012         } else {
11013             int desired = getDesiredHeight();
11014 
11015             height = desired;
11016             mDesiredHeightAtMeasure = desired;
11017 
11018             if (heightMode == MeasureSpec.AT_MOST) {
11019                 height = Math.min(desired, heightSize);
11020             }
11021         }
11022 
11023         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
11024         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
11025             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
11026         }
11027 
11028         /*
11029          * We didn't let makeNewLayout() register to bring the cursor into view,
11030          * so do it here if there is any possibility that it is needed.
11031          */
11032         if (mMovement != null
11033                 || mLayout.getWidth() > unpaddedWidth
11034                 || mLayout.getHeight() > unpaddedHeight) {
11035             registerForPreDraw();
11036         } else {
11037             scrollTo(0, 0);
11038         }
11039 
11040         setMeasuredDimension(width, height);
11041     }
11042 
11043     /**
11044      * Automatically computes and sets the text size.
11045      */
autoSizeText()11046     private void autoSizeText() {
11047         if (!isAutoSizeEnabled()) {
11048             return;
11049         }
11050 
11051         if (mNeedsAutoSizeText) {
11052             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
11053                 return;
11054             }
11055 
11056             final int availableWidth = mHorizontallyScrolling
11057                     ? VERY_WIDE
11058                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
11059             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
11060                     - getExtendedPaddingTop();
11061 
11062             if (availableWidth <= 0 || availableHeight <= 0) {
11063                 return;
11064             }
11065 
11066             synchronized (TEMP_RECTF) {
11067                 TEMP_RECTF.setEmpty();
11068                 TEMP_RECTF.right = availableWidth;
11069                 TEMP_RECTF.bottom = availableHeight;
11070                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
11071 
11072                 if (optimalTextSize != getTextSize()) {
11073                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
11074                             false /* shouldRequestLayout */);
11075 
11076                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
11077                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
11078                             false /* bringIntoView */);
11079                 }
11080             }
11081         }
11082         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
11083         // after the next layout pass should set this to false.
11084         mNeedsAutoSizeText = true;
11085     }
11086 
11087     /**
11088      * Performs a binary search to find the largest text size that will still fit within the size
11089      * available to this view.
11090      */
findLargestTextSizeWhichFits(RectF availableSpace)11091     private int findLargestTextSizeWhichFits(RectF availableSpace) {
11092         final int sizesCount = mAutoSizeTextSizesInPx.length;
11093         if (sizesCount == 0) {
11094             throw new IllegalStateException("No available text sizes to choose from.");
11095         }
11096 
11097         int bestSizeIndex = 0;
11098         int lowIndex = bestSizeIndex + 1;
11099         int highIndex = sizesCount - 1;
11100         int sizeToTryIndex;
11101         while (lowIndex <= highIndex) {
11102             sizeToTryIndex = (lowIndex + highIndex) / 2;
11103             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
11104                 bestSizeIndex = lowIndex;
11105                 lowIndex = sizeToTryIndex + 1;
11106             } else {
11107                 highIndex = sizeToTryIndex - 1;
11108                 bestSizeIndex = highIndex;
11109             }
11110         }
11111 
11112         return mAutoSizeTextSizesInPx[bestSizeIndex];
11113     }
11114 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)11115     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
11116         final CharSequence text = mTransformed != null
11117                 ? mTransformed
11118                 : getText();
11119         final int maxLines = getMaxLines();
11120         if (mTempTextPaint == null) {
11121             mTempTextPaint = new TextPaint();
11122         } else {
11123             mTempTextPaint.reset();
11124         }
11125         mTempTextPaint.set(getPaint());
11126         mTempTextPaint.setTextSize(suggestedSizeInPx);
11127 
11128         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
11129                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
11130         layoutBuilder.setAlignment(getLayoutAlignment())
11131                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
11132                 .setIncludePad(getIncludeFontPadding())
11133                 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11134                 .setBreakStrategy(getBreakStrategy())
11135                 .setHyphenationFrequency(getHyphenationFrequency())
11136                 .setJustificationMode(getJustificationMode())
11137                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
11138                 .setTextDirection(getTextDirectionHeuristic())
11139                 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11140                         mLineBreakStyle, mLineBreakWordStyle));
11141 
11142         final StaticLayout layout = layoutBuilder.build();
11143 
11144         // Lines overflow.
11145         if (maxLines != -1 && layout.getLineCount() > maxLines) {
11146             return false;
11147         }
11148 
11149         // Height overflow.
11150         if (layout.getHeight() > availableSpace.bottom) {
11151             return false;
11152         }
11153 
11154         return true;
11155     }
11156 
getDesiredHeight()11157     private int getDesiredHeight() {
11158         return Math.max(
11159                 getDesiredHeight(mLayout, true),
11160                 getDesiredHeight(mHintLayout, mEllipsize != null));
11161     }
11162 
getDesiredHeight(Layout layout, boolean cap)11163     private int getDesiredHeight(Layout layout, boolean cap) {
11164         if (layout == null) {
11165             return 0;
11166         }
11167 
11168         /*
11169         * Don't cap the hint to a certain number of lines.
11170         * (Do cap it, though, if we have a maximum pixel height.)
11171         */
11172         int desired = layout.getHeight(cap);
11173 
11174         final Drawables dr = mDrawables;
11175         if (dr != null) {
11176             desired = Math.max(desired, dr.mDrawableHeightLeft);
11177             desired = Math.max(desired, dr.mDrawableHeightRight);
11178         }
11179 
11180         int linecount = layout.getLineCount();
11181         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
11182         desired += padding;
11183 
11184         if (mMaxMode != LINES) {
11185             desired = Math.min(desired, mMaximum);
11186         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
11187                 || layout instanceof BoringLayout)) {
11188             desired = layout.getLineTop(mMaximum);
11189 
11190             if (dr != null) {
11191                 desired = Math.max(desired, dr.mDrawableHeightLeft);
11192                 desired = Math.max(desired, dr.mDrawableHeightRight);
11193             }
11194 
11195             desired += padding;
11196             linecount = mMaximum;
11197         }
11198 
11199         if (mMinMode == LINES) {
11200             if (linecount < mMinimum) {
11201                 desired += getLineHeight() * (mMinimum - linecount);
11202             }
11203         } else {
11204             desired = Math.max(desired, mMinimum);
11205         }
11206 
11207         // Check against our minimum height
11208         desired = Math.max(desired, getSuggestedMinimumHeight());
11209 
11210         return desired;
11211     }
11212 
11213     /**
11214      * Check whether a change to the existing text layout requires a
11215      * new view layout.
11216      */
checkForResize()11217     private void checkForResize() {
11218         boolean sizeChanged = false;
11219 
11220         if (mLayout != null) {
11221             // Check if our width changed
11222             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
11223                 sizeChanged = true;
11224                 invalidate();
11225             }
11226 
11227             // Check if our height changed
11228             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
11229                 int desiredHeight = getDesiredHeight();
11230 
11231                 if (desiredHeight != this.getHeight()) {
11232                     sizeChanged = true;
11233                 }
11234             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
11235                 if (mDesiredHeightAtMeasure >= 0) {
11236                     int desiredHeight = getDesiredHeight();
11237 
11238                     if (desiredHeight != mDesiredHeightAtMeasure) {
11239                         sizeChanged = true;
11240                     }
11241                 }
11242             }
11243         }
11244 
11245         if (sizeChanged) {
11246             requestLayout();
11247             // caller will have already invalidated
11248         }
11249     }
11250 
11251     /**
11252      * Check whether entirely new text requires a new view layout
11253      * or merely a new text layout.
11254      */
11255     @UnsupportedAppUsage
checkForRelayout()11256     private void checkForRelayout() {
11257         // If we have a fixed width, we can just swap in a new text layout
11258         // if the text height stays the same or if the view height is fixed.
11259 
11260         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
11261                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
11262                 && (mHint == null || mHintLayout != null)
11263                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
11264             // Static width, so try making a new text layout.
11265 
11266             int oldht = mLayout.getHeight();
11267             int want = mLayout.getWidth();
11268             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
11269 
11270             /*
11271              * No need to bring the text into view, since the size is not
11272              * changing (unless we do the requestLayout(), in which case it
11273              * will happen at measure).
11274              */
11275             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
11276                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
11277                           false);
11278 
11279             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
11280                 // In a fixed-height view, so use our new text layout.
11281                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
11282                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
11283                     autoSizeText();
11284                     invalidate();
11285                     return;
11286                 }
11287 
11288                 // Dynamic height, but height has stayed the same,
11289                 // so use our new text layout.
11290                 if (mLayout.getHeight() == oldht
11291                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
11292                     autoSizeText();
11293                     invalidate();
11294                     return;
11295                 }
11296             }
11297 
11298             // We lose: the height has changed and we have a dynamic height.
11299             // Request a new view layout using our new text layout.
11300             requestLayout();
11301             invalidate();
11302         } else {
11303             // Dynamic width, so we have no choice but to request a new
11304             // view layout with a new text layout.
11305             nullLayouts();
11306             requestLayout();
11307             invalidate();
11308         }
11309     }
11310 
11311     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)11312     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
11313         super.onLayout(changed, left, top, right, bottom);
11314         if (mDeferScroll >= 0) {
11315             int curs = mDeferScroll;
11316             mDeferScroll = -1;
11317             bringPointIntoView(Math.min(curs, mText.length()));
11318         }
11319         // Call auto-size after the width and height have been calculated.
11320         autoSizeText();
11321     }
11322 
isShowingHint()11323     private boolean isShowingHint() {
11324         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint) && !mHideHint;
11325     }
11326 
11327     /**
11328      * Returns true if anything changed.
11329      */
11330     @UnsupportedAppUsage
bringTextIntoView()11331     private boolean bringTextIntoView() {
11332         Layout layout = isShowingHint() ? mHintLayout : mLayout;
11333         int line = 0;
11334         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
11335             line = layout.getLineCount() - 1;
11336         }
11337 
11338         Layout.Alignment a = layout.getParagraphAlignment(line);
11339         int dir = layout.getParagraphDirection(line);
11340         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
11341         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
11342         int ht = layout.getHeight();
11343 
11344         int scrollx, scrolly;
11345 
11346         // Convert to left, center, or right alignment.
11347         if (a == Layout.Alignment.ALIGN_NORMAL) {
11348             a = dir == Layout.DIR_LEFT_TO_RIGHT
11349                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
11350         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
11351             a = dir == Layout.DIR_LEFT_TO_RIGHT
11352                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
11353         }
11354 
11355         if (a == Layout.Alignment.ALIGN_CENTER) {
11356             /*
11357              * Keep centered if possible, or, if it is too wide to fit,
11358              * keep leading edge in view.
11359              */
11360 
11361             int left = (int) Math.floor(layout.getLineLeft(line));
11362             int right = (int) Math.ceil(layout.getLineRight(line));
11363 
11364             if (right - left < hspace) {
11365                 scrollx = (right + left) / 2 - hspace / 2;
11366             } else {
11367                 if (dir < 0) {
11368                     scrollx = right - hspace;
11369                 } else {
11370                     scrollx = left;
11371                 }
11372             }
11373         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
11374             int right = (int) Math.ceil(layout.getLineRight(line));
11375             scrollx = right - hspace;
11376         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
11377             scrollx = (int) Math.floor(layout.getLineLeft(line));
11378         }
11379 
11380         if (ht < vspace) {
11381             scrolly = 0;
11382         } else {
11383             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
11384                 scrolly = ht - vspace;
11385             } else {
11386                 scrolly = 0;
11387             }
11388         }
11389 
11390         if (scrollx != mScrollX || scrolly != mScrollY) {
11391             scrollTo(scrollx, scrolly);
11392             return true;
11393         } else {
11394             return false;
11395         }
11396     }
11397 
11398     /**
11399      * Move the point, specified by the offset, into the view if it is needed.
11400      * This has to be called after layout. Returns true if anything changed.
11401      */
bringPointIntoView(int offset)11402     public boolean bringPointIntoView(int offset) {
11403         return bringPointIntoView(offset, false);
11404     }
11405 
11406     /**
11407      * Move the insertion position of the given offset into visible area of the View.
11408      *
11409      * If the View is focused or {@code requestRectWithoutFocus} is set to true, this API may call
11410      * {@link View#requestRectangleOnScreen(Rect)} to bring the point to the visible area if
11411      * necessary.
11412      *
11413      * @param offset an offset of the character.
11414      * @param requestRectWithoutFocus True for calling {@link View#requestRectangleOnScreen(Rect)}
11415      *                                in the unfocused state. False for calling it only the View has
11416      *                                the focus.
11417      * @return true if anything changed, otherwise false.
11418      *
11419      * @see #bringPointIntoView(int)
11420      */
bringPointIntoView(@ntRangefrom = 0) int offset, boolean requestRectWithoutFocus)11421     public boolean bringPointIntoView(@IntRange(from = 0) int offset,
11422             boolean requestRectWithoutFocus) {
11423         if (isLayoutRequested()) {
11424             mDeferScroll = offset;
11425             return false;
11426         }
11427         final int offsetTransformed =
11428                 originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
11429         boolean changed = false;
11430 
11431         Layout layout = isShowingHint() ? mHintLayout : mLayout;
11432 
11433         if (layout == null) return changed;
11434 
11435         int line = layout.getLineForOffset(offsetTransformed);
11436 
11437         int grav;
11438 
11439         switch (layout.getParagraphAlignment(line)) {
11440             case ALIGN_LEFT:
11441                 grav = 1;
11442                 break;
11443             case ALIGN_RIGHT:
11444                 grav = -1;
11445                 break;
11446             case ALIGN_NORMAL:
11447                 grav = layout.getParagraphDirection(line);
11448                 break;
11449             case ALIGN_OPPOSITE:
11450                 grav = -layout.getParagraphDirection(line);
11451                 break;
11452             case ALIGN_CENTER:
11453             default:
11454                 grav = 0;
11455                 break;
11456         }
11457 
11458         // We only want to clamp the cursor to fit within the layout width
11459         // in left-to-right modes, because in a right to left alignment,
11460         // we want to scroll to keep the line-right on the screen, as other
11461         // lines are likely to have text flush with the right margin, which
11462         // we want to keep visible.
11463         // A better long-term solution would probably be to measure both
11464         // the full line and a blank-trimmed version, and, for example, use
11465         // the latter measurement for centering and right alignment, but for
11466         // the time being we only implement the cursor clamping in left to
11467         // right where it is most likely to be annoying.
11468         final boolean clamped = grav > 0;
11469         // FIXME: Is it okay to truncate this, or should we round?
11470         final int x = (int) layout.getPrimaryHorizontal(offsetTransformed, clamped);
11471         final int top = layout.getLineTop(line);
11472         final int bottom = layout.getLineTop(line + 1);
11473 
11474         int left = (int) Math.floor(layout.getLineLeft(line));
11475         int right = (int) Math.ceil(layout.getLineRight(line));
11476         int ht = layout.getHeight();
11477 
11478         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
11479         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
11480         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
11481             // If cursor has been clamped, make sure we don't scroll.
11482             right = Math.max(x, left + hspace);
11483         }
11484 
11485         int hslack = (bottom - top) / 2;
11486         int vslack = hslack;
11487 
11488         if (vslack > vspace / 4) {
11489             vslack = vspace / 4;
11490         }
11491         if (hslack > hspace / 4) {
11492             hslack = hspace / 4;
11493         }
11494 
11495         int hs = mScrollX;
11496         int vs = mScrollY;
11497 
11498         if (top - vs < vslack) {
11499             vs = top - vslack;
11500         }
11501         if (bottom - vs > vspace - vslack) {
11502             vs = bottom - (vspace - vslack);
11503         }
11504         if (ht - vs < vspace) {
11505             vs = ht - vspace;
11506         }
11507         if (0 - vs > 0) {
11508             vs = 0;
11509         }
11510 
11511         if (grav != 0) {
11512             if (x - hs < hslack) {
11513                 hs = x - hslack;
11514             }
11515             if (x - hs > hspace - hslack) {
11516                 hs = x - (hspace - hslack);
11517             }
11518         }
11519 
11520         if (grav < 0) {
11521             if (left - hs > 0) {
11522                 hs = left;
11523             }
11524             if (right - hs < hspace) {
11525                 hs = right - hspace;
11526             }
11527         } else if (grav > 0) {
11528             if (right - hs < hspace) {
11529                 hs = right - hspace;
11530             }
11531             if (left - hs > 0) {
11532                 hs = left;
11533             }
11534         } else /* grav == 0 */ {
11535             if (right - left <= hspace) {
11536                 /*
11537                  * If the entire text fits, center it exactly.
11538                  */
11539                 hs = left - (hspace - (right - left)) / 2;
11540             } else if (x > right - hslack) {
11541                 /*
11542                  * If we are near the right edge, keep the right edge
11543                  * at the edge of the view.
11544                  */
11545                 hs = right - hspace;
11546             } else if (x < left + hslack) {
11547                 /*
11548                  * If we are near the left edge, keep the left edge
11549                  * at the edge of the view.
11550                  */
11551                 hs = left;
11552             } else if (left > hs) {
11553                 /*
11554                  * Is there whitespace visible at the left?  Fix it if so.
11555                  */
11556                 hs = left;
11557             } else if (right < hs + hspace) {
11558                 /*
11559                  * Is there whitespace visible at the right?  Fix it if so.
11560                  */
11561                 hs = right - hspace;
11562             } else {
11563                 /*
11564                  * Otherwise, float as needed.
11565                  */
11566                 if (x - hs < hslack) {
11567                     hs = x - hslack;
11568                 }
11569                 if (x - hs > hspace - hslack) {
11570                     hs = x - (hspace - hslack);
11571                 }
11572             }
11573         }
11574 
11575         if (hs != mScrollX || vs != mScrollY) {
11576             if (mScroller == null) {
11577                 scrollTo(hs, vs);
11578             } else {
11579                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
11580                 int dx = hs - mScrollX;
11581                 int dy = vs - mScrollY;
11582 
11583                 if (duration > ANIMATED_SCROLL_GAP) {
11584                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
11585                     awakenScrollBars(mScroller.getDuration());
11586                     invalidate();
11587                 } else {
11588                     if (!mScroller.isFinished()) {
11589                         mScroller.abortAnimation();
11590                     }
11591 
11592                     scrollBy(dx, dy);
11593                 }
11594 
11595                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
11596             }
11597 
11598             changed = true;
11599         }
11600 
11601         if (requestRectWithoutFocus || isFocused()) {
11602             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
11603             // requestRectangleOnScreen() is in terms of content coordinates.
11604 
11605             // The offsets here are to ensure the rectangle we are using is
11606             // within our view bounds, in case the cursor is on the far left
11607             // or right.  If it isn't withing the bounds, then this request
11608             // will be ignored.
11609             if (mTempRect == null) mTempRect = new Rect();
11610             mTempRect.set(x - 2, top, x + 2, bottom);
11611             getInterestingRect(mTempRect, line);
11612             mTempRect.offset(mScrollX, mScrollY);
11613 
11614             if (requestRectangleOnScreen(mTempRect)) {
11615                 changed = true;
11616             }
11617         }
11618 
11619         return changed;
11620     }
11621 
11622     /**
11623      * Move the cursor, if needed, so that it is at an offset that is visible
11624      * to the user.  This will not move the cursor if it represents more than
11625      * one character (a selection range).  This will only work if the
11626      * TextView contains spannable text; otherwise it will do nothing.
11627      *
11628      * @return True if the cursor was actually moved, false otherwise.
11629      */
moveCursorToVisibleOffset()11630     public boolean moveCursorToVisibleOffset() {
11631         if (!(mText instanceof Spannable)) {
11632             return false;
11633         }
11634         int start = getSelectionStartTransformed();
11635         int end = getSelectionEndTransformed();
11636         if (start != end) {
11637             return false;
11638         }
11639 
11640         // First: make sure the line is visible on screen:
11641 
11642         int line = mLayout.getLineForOffset(start);
11643 
11644         final int top = mLayout.getLineTop(line);
11645         final int bottom = mLayout.getLineTop(line + 1);
11646         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
11647         int vslack = (bottom - top) / 2;
11648         if (vslack > vspace / 4) {
11649             vslack = vspace / 4;
11650         }
11651         final int vs = mScrollY;
11652 
11653         if (top < (vs + vslack)) {
11654             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
11655         } else if (bottom > (vspace + vs - vslack)) {
11656             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
11657         }
11658 
11659         // Next: make sure the character is visible on screen:
11660 
11661         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
11662         final int hs = mScrollX;
11663         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
11664         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
11665 
11666         // line might contain bidirectional text
11667         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
11668         final int highChar = leftChar > rightChar ? leftChar : rightChar;
11669 
11670         int newStart = start;
11671         if (newStart < lowChar) {
11672             newStart = lowChar;
11673         } else if (newStart > highChar) {
11674             newStart = highChar;
11675         }
11676 
11677         if (newStart != start) {
11678             Selection.setSelection(mSpannable,
11679                     transformedToOriginal(newStart, OffsetMapping.MAP_STRATEGY_CURSOR));
11680             return true;
11681         }
11682 
11683         return false;
11684     }
11685 
11686     @Override
computeScroll()11687     public void computeScroll() {
11688         if (mScroller != null) {
11689             if (mScroller.computeScrollOffset()) {
11690                 mScrollX = mScroller.getCurrX();
11691                 mScrollY = mScroller.getCurrY();
11692                 invalidateParentCaches();
11693                 postInvalidate();  // So we draw again
11694             }
11695         }
11696     }
11697 
getInterestingRect(Rect r, int line)11698     private void getInterestingRect(Rect r, int line) {
11699         convertFromViewportToContentCoordinates(r);
11700 
11701         // Rectangle can can be expanded on first and last line to take
11702         // padding into account.
11703         // TODO Take left/right padding into account too?
11704         if (line == 0) r.top -= getExtendedPaddingTop();
11705         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
11706     }
11707 
convertFromViewportToContentCoordinates(Rect r)11708     private void convertFromViewportToContentCoordinates(Rect r) {
11709         final int horizontalOffset = viewportToContentHorizontalOffset();
11710         r.left += horizontalOffset;
11711         r.right += horizontalOffset;
11712 
11713         final int verticalOffset = viewportToContentVerticalOffset();
11714         r.top += verticalOffset;
11715         r.bottom += verticalOffset;
11716     }
11717 
convertFromScreenToContentCoordinates(PointF point)11718     private PointF convertFromScreenToContentCoordinates(PointF point) {
11719         int[] screenToViewport = getLocationOnScreen();
11720         PointF copy = new PointF(point);
11721         copy.offset(
11722                 -(screenToViewport[0] + viewportToContentHorizontalOffset()),
11723                 -(screenToViewport[1] + viewportToContentVerticalOffset()));
11724         return copy;
11725     }
11726 
convertFromScreenToContentCoordinates(RectF rect)11727     private RectF convertFromScreenToContentCoordinates(RectF rect) {
11728         int[] screenToViewport = getLocationOnScreen();
11729         RectF copy = new RectF(rect);
11730         copy.offset(
11731                 -(screenToViewport[0] + viewportToContentHorizontalOffset()),
11732                 -(screenToViewport[1] + viewportToContentVerticalOffset()));
11733         return copy;
11734     }
11735 
viewportToContentHorizontalOffset()11736     int viewportToContentHorizontalOffset() {
11737         return getCompoundPaddingLeft() - mScrollX;
11738     }
11739 
11740     @UnsupportedAppUsage
viewportToContentVerticalOffset()11741     int viewportToContentVerticalOffset() {
11742         int offset = getExtendedPaddingTop() - mScrollY;
11743         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
11744             offset += getVerticalOffset(false);
11745         }
11746         return offset;
11747     }
11748 
11749     @Override
debug(int depth)11750     public void debug(int depth) {
11751         super.debug(depth);
11752 
11753         String output = debugIndent(depth);
11754         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
11755                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
11756                 + "} ";
11757 
11758         if (mText != null) {
11759 
11760             output += "mText=\"" + mText + "\" ";
11761             if (mLayout != null) {
11762                 output += "mLayout width=" + mLayout.getWidth()
11763                         + " height=" + mLayout.getHeight();
11764             }
11765         } else {
11766             output += "mText=NULL";
11767         }
11768         Log.d(VIEW_LOG_TAG, output);
11769     }
11770 
11771     /**
11772      * Convenience for {@link Selection#getSelectionStart}.
11773      */
11774     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()11775     public int getSelectionStart() {
11776         return Selection.getSelectionStart(getText());
11777     }
11778 
11779     /**
11780      * Convenience for {@link Selection#getSelectionEnd}.
11781      */
11782     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()11783     public int getSelectionEnd() {
11784         return Selection.getSelectionEnd(getText());
11785     }
11786 
11787     /**
11788      * Calculates the rectangles which should be highlighted to indicate a selection between start
11789      * and end and feeds them into the given {@link Layout.SelectionRectangleConsumer}.
11790      *
11791      * @param start    the starting index of the selection
11792      * @param end      the ending index of the selection
11793      * @param consumer the {@link Layout.SelectionRectangleConsumer} which will receive the
11794      *                 generated rectangles. It will be called every time a rectangle is generated.
11795      * @hide
11796      */
getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer)11797     public void getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer) {
11798         final int transformedStart =
11799                 originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
11800         final int transformedEnd = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
11801         mLayout.getSelection(transformedStart, transformedEnd, consumer);
11802     }
11803 
getSelectionStartTransformed()11804     int getSelectionStartTransformed() {
11805         final int start = getSelectionStart();
11806         if (start < 0) return start;
11807         return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
11808     }
11809 
getSelectionEndTransformed()11810     int getSelectionEndTransformed() {
11811         final int end = getSelectionEnd();
11812         if (end < 0) return end;
11813         return originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
11814     }
11815 
11816     /**
11817      * Return true iff there is a selection of nonzero length inside this text view.
11818      */
hasSelection()11819     public boolean hasSelection() {
11820         final int selectionStart = getSelectionStart();
11821         final int selectionEnd = getSelectionEnd();
11822         final int selectionMin;
11823         final int selectionMax;
11824         if (selectionStart < selectionEnd) {
11825             selectionMin = selectionStart;
11826             selectionMax = selectionEnd;
11827         } else {
11828             selectionMin = selectionEnd;
11829             selectionMax = selectionStart;
11830         }
11831 
11832         return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax;
11833     }
11834 
getSelectedText()11835     String getSelectedText() {
11836         if (!hasSelection()) {
11837             return null;
11838         }
11839 
11840         final int start = getSelectionStart();
11841         final int end = getSelectionEnd();
11842         return String.valueOf(
11843                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
11844     }
11845 
11846     /**
11847      * Sets the properties of this field (lines, horizontally scrolling,
11848      * transformation method) to be for a single-line input.
11849      *
11850      * @attr ref android.R.styleable#TextView_singleLine
11851      */
setSingleLine()11852     public void setSingleLine() {
11853         setSingleLine(true);
11854     }
11855 
11856     /**
11857      * Sets the properties of this field to transform input to ALL CAPS
11858      * display. This may use a "small caps" formatting if available.
11859      * This setting will be ignored if this field is editable or selectable.
11860      *
11861      * This call replaces the current transformation method. Disabling this
11862      * will not necessarily restore the previous behavior from before this
11863      * was enabled.
11864      *
11865      * @see #setTransformationMethod(TransformationMethod)
11866      * @attr ref android.R.styleable#TextView_textAllCaps
11867      */
11868     @android.view.RemotableViewMethod
setAllCaps(boolean allCaps)11869     public void setAllCaps(boolean allCaps) {
11870         if (allCaps) {
11871             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
11872         } else {
11873             setTransformationMethod(null);
11874         }
11875     }
11876 
11877     /**
11878      *
11879      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
11880      * @return Whether the current transformation method is for ALL CAPS.
11881      *
11882      * @see #setAllCaps(boolean)
11883      * @see #setTransformationMethod(TransformationMethod)
11884      */
11885     @InspectableProperty(name = "textAllCaps")
isAllCaps()11886     public boolean isAllCaps() {
11887         final TransformationMethod method = getTransformationMethod();
11888         return method != null && method instanceof AllCapsTransformationMethod;
11889     }
11890 
11891     /**
11892      * If true, sets the properties of this field (number of lines, horizontally scrolling,
11893      * transformation method) to be for a single-line input; if false, restores these to the default
11894      * conditions.
11895      *
11896      * Note that the default conditions are not necessarily those that were in effect prior this
11897      * method, and you may want to reset these properties to your custom values.
11898      *
11899      * Note that due to performance reasons, by setting single line for the EditText, the maximum
11900      * text length is set to 5000 if no other character limitation are applied.
11901      *
11902      * @attr ref android.R.styleable#TextView_singleLine
11903      */
11904     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)11905     public void setSingleLine(boolean singleLine) {
11906         // Could be used, but may break backward compatibility.
11907         // if (mSingleLine == singleLine) return;
11908         setInputTypeSingleLine(singleLine);
11909         applySingleLine(singleLine, true, true, true);
11910     }
11911 
11912     /**
11913      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
11914      * @param singleLine
11915      */
setInputTypeSingleLine(boolean singleLine)11916     private void setInputTypeSingleLine(boolean singleLine) {
11917         if (mEditor != null
11918                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
11919                         == EditorInfo.TYPE_CLASS_TEXT) {
11920             if (singleLine) {
11921                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
11922             } else {
11923                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
11924             }
11925         }
11926     }
11927 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)11928     private void applySingleLine(boolean singleLine, boolean applyTransformation,
11929             boolean changeMaxLines, boolean changeMaxLength) {
11930         mSingleLine = singleLine;
11931 
11932         if (singleLine) {
11933             setLines(1);
11934             setHorizontallyScrolling(true);
11935             if (applyTransformation) {
11936                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
11937             }
11938 
11939             if (!changeMaxLength) return;
11940 
11941             // Single line length filter is only applicable editable text.
11942             if (mBufferType != BufferType.EDITABLE) return;
11943 
11944             final InputFilter[] prevFilters = getFilters();
11945             for (InputFilter filter: getFilters()) {
11946                 // We don't add LengthFilter if already there.
11947                 if (filter instanceof InputFilter.LengthFilter) return;
11948             }
11949 
11950             if (mSingleLineLengthFilter == null) {
11951                 mSingleLineLengthFilter = new InputFilter.LengthFilter(
11952                     MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
11953             }
11954 
11955             final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1];
11956             System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length);
11957             newFilters[prevFilters.length] = mSingleLineLengthFilter;
11958 
11959             setFilters(newFilters);
11960 
11961             // Since filter doesn't apply to existing text, trigger filter by setting text.
11962             setText(getText());
11963         } else {
11964             if (changeMaxLines) {
11965                 setMaxLines(Integer.MAX_VALUE);
11966             }
11967             setHorizontallyScrolling(false);
11968             if (applyTransformation) {
11969                 setTransformationMethod(null);
11970             }
11971 
11972             if (!changeMaxLength) return;
11973 
11974             // Single line length filter is only applicable editable text.
11975             if (mBufferType != BufferType.EDITABLE) return;
11976 
11977             final InputFilter[] prevFilters = getFilters();
11978             if (prevFilters.length == 0) return;
11979 
11980             // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated
11981             // single line char limit filter.
11982             if (mSingleLineLengthFilter == null) return;
11983 
11984             // If we need to remove mSingleLineLengthFilter, we need to allocate another array.
11985             // Since filter list is expected to be small and want to avoid unnecessary array
11986             // allocation, check if there is mSingleLengthFilter first.
11987             int targetIndex = -1;
11988             for (int i = 0; i < prevFilters.length; ++i) {
11989                 if (prevFilters[i] == mSingleLineLengthFilter) {
11990                     targetIndex = i;
11991                     break;
11992                 }
11993             }
11994             if (targetIndex == -1) return;  // not found. Do nothing.
11995 
11996             if (prevFilters.length == 1) {
11997                 setFilters(NO_FILTERS);
11998                 return;
11999             }
12000 
12001             // Create new array which doesn't include mSingleLengthFilter.
12002             final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1];
12003             System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex);
12004             System.arraycopy(
12005                     prevFilters,
12006                     targetIndex + 1,
12007                     newFilters,
12008                     targetIndex,
12009                     prevFilters.length - targetIndex - 1);
12010             setFilters(newFilters);
12011             mSingleLineLengthFilter = null;
12012         }
12013     }
12014 
12015     /**
12016      * Causes words in the text that are longer than the view's width
12017      * to be ellipsized instead of broken in the middle.  You may also
12018      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
12019      * to constrain the text to a single line.  Use <code>null</code>
12020      * to turn off ellipsizing.
12021      *
12022      * If {@link #setMaxLines} has been used to set two or more lines,
12023      * only {@link android.text.TextUtils.TruncateAt#END} and
12024      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
12025      * (other ellipsizing types will not do anything).
12026      *
12027      * @attr ref android.R.styleable#TextView_ellipsize
12028      */
setEllipsize(TextUtils.TruncateAt where)12029     public void setEllipsize(TextUtils.TruncateAt where) {
12030         // TruncateAt is an enum. != comparison is ok between these singleton objects.
12031         if (mEllipsize != where) {
12032             mEllipsize = where;
12033 
12034             if (mLayout != null) {
12035                 nullLayouts();
12036                 requestLayout();
12037                 invalidate();
12038             }
12039         }
12040     }
12041 
12042     /**
12043      * Sets how many times to repeat the marquee animation. Only applied if the
12044      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
12045      *
12046      * @see #getMarqueeRepeatLimit()
12047      *
12048      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
12049      */
setMarqueeRepeatLimit(int marqueeLimit)12050     public void setMarqueeRepeatLimit(int marqueeLimit) {
12051         mMarqueeRepeatLimit = marqueeLimit;
12052     }
12053 
12054     /**
12055      * Gets the number of times the marquee animation is repeated. Only meaningful if the
12056      * TextView has marquee enabled.
12057      *
12058      * @return the number of times the marquee animation is repeated. -1 if the animation
12059      * repeats indefinitely
12060      *
12061      * @see #setMarqueeRepeatLimit(int)
12062      *
12063      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
12064      */
12065     @InspectableProperty
getMarqueeRepeatLimit()12066     public int getMarqueeRepeatLimit() {
12067         return mMarqueeRepeatLimit;
12068     }
12069 
12070     /**
12071      * Returns where, if anywhere, words that are longer than the view
12072      * is wide should be ellipsized.
12073      */
12074     @InspectableProperty
12075     @ViewDebug.ExportedProperty
getEllipsize()12076     public TextUtils.TruncateAt getEllipsize() {
12077         return mEllipsize;
12078     }
12079 
12080     /**
12081      * Set the TextView so that when it takes focus, all the text is
12082      * selected.
12083      *
12084      * @attr ref android.R.styleable#TextView_selectAllOnFocus
12085      */
12086     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)12087     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
12088         createEditorIfNeeded();
12089         mEditor.mSelectAllOnFocus = selectAllOnFocus;
12090 
12091         if (selectAllOnFocus && !(mText instanceof Spannable)) {
12092             setText(mText, BufferType.SPANNABLE);
12093         }
12094     }
12095 
12096     /**
12097      * Set whether the cursor is visible. The default is true. Note that this property only
12098      * makes sense for editable TextView. If IME is consuming the input, the cursor will always be
12099      * invisible, visibility will be updated as the last state when IME does not consume
12100      * the input anymore.
12101      *
12102      * @see #isCursorVisible()
12103      *
12104      * @attr ref android.R.styleable#TextView_cursorVisible
12105      */
12106     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)12107     public void setCursorVisible(boolean visible) {
12108         mCursorVisibleFromAttr = visible;
12109         updateCursorVisibleInternal();
12110     }
12111 
12112     /**
12113      * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput}
12114      * is {@code true}. Otherwise, make the cursor visible.
12115      *
12116      * @param imeConsumesInput {@code true} if IME is consuming the input
12117      *
12118      * @hide
12119      */
setImeConsumesInput(boolean imeConsumesInput)12120     public void setImeConsumesInput(boolean imeConsumesInput) {
12121         mImeIsConsumingInput = imeConsumesInput;
12122         updateCursorVisibleInternal();
12123     }
12124 
updateCursorVisibleInternal()12125     private void updateCursorVisibleInternal()  {
12126         boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput;
12127         if (visible && mEditor == null) return; // visible is the default value with no edit data
12128         createEditorIfNeeded();
12129         if (mEditor.mCursorVisible != visible) {
12130             mEditor.mCursorVisible = visible;
12131             invalidate();
12132 
12133             mEditor.makeBlink();
12134 
12135             // InsertionPointCursorController depends on mCursorVisible
12136             mEditor.prepareCursorControllers();
12137         }
12138     }
12139 
12140     /**
12141      * @return whether or not the cursor is visible (assuming this TextView is editable). This
12142      * method may return {@code false} when the IME is consuming the input even if the
12143      * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)}
12144      * is called.
12145      *
12146      * @see #setCursorVisible(boolean)
12147      *
12148      * @attr ref android.R.styleable#TextView_cursorVisible
12149      */
12150     @InspectableProperty
isCursorVisible()12151     public boolean isCursorVisible() {
12152         // true is the default value
12153         return mEditor == null ? true : mEditor.mCursorVisible;
12154     }
12155 
12156     /**
12157      * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}.
12158      * {@code true} is the default value.
12159      *
12160      * @see #setCursorVisible(boolean)
12161      * @hide
12162      */
isCursorVisibleFromAttr()12163     public boolean isCursorVisibleFromAttr() {
12164         return mCursorVisibleFromAttr;
12165     }
12166 
canMarquee()12167     private boolean canMarquee() {
12168         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
12169         return width > 0 && (mLayout.getLineWidth(0) > width
12170                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
12171                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
12172     }
12173 
12174     /**
12175      * @hide
12176      */
12177     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()12178     protected void startMarquee() {
12179         // Do not ellipsize EditText
12180         if (getKeyListener() != null) return;
12181 
12182         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
12183             return;
12184         }
12185 
12186         if ((mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible()
12187                 && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) {
12188 
12189             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
12190                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
12191                 final Layout tmp = mLayout;
12192                 mLayout = mSavedMarqueeModeLayout;
12193                 mSavedMarqueeModeLayout = tmp;
12194                 setHorizontalFadingEdgeEnabled(true);
12195                 requestLayout();
12196                 invalidate();
12197             }
12198 
12199             if (mMarquee == null) mMarquee = new Marquee(this);
12200             mMarquee.start(mMarqueeRepeatLimit);
12201         }
12202     }
12203 
12204     /**
12205      * @hide
12206      */
stopMarquee()12207     protected void stopMarquee() {
12208         if (mMarquee != null && !mMarquee.isStopped()) {
12209             mMarquee.stop();
12210         }
12211 
12212         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
12213             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
12214             final Layout tmp = mSavedMarqueeModeLayout;
12215             mSavedMarqueeModeLayout = mLayout;
12216             mLayout = tmp;
12217             setHorizontalFadingEdgeEnabled(false);
12218             requestLayout();
12219             invalidate();
12220         }
12221     }
12222 
12223     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)12224     private void startStopMarquee(boolean start) {
12225         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
12226             if (start) {
12227                 startMarquee();
12228             } else {
12229                 stopMarquee();
12230             }
12231         }
12232     }
12233 
12234     /**
12235      * This method is called when the text is changed, in case any subclasses
12236      * would like to know.
12237      *
12238      * Within <code>text</code>, the <code>lengthAfter</code> characters
12239      * beginning at <code>start</code> have just replaced old text that had
12240      * length <code>lengthBefore</code>. It is an error to attempt to make
12241      * changes to <code>text</code> from this callback.
12242      *
12243      * @param text The text the TextView is displaying
12244      * @param start The offset of the start of the range of the text that was
12245      * modified
12246      * @param lengthBefore The length of the former text that has been replaced
12247      * @param lengthAfter The length of the replacement modified text
12248      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)12249     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
12250         // intentionally empty, template pattern method can be overridden by subclasses
12251     }
12252 
12253     /**
12254      * This method is called when the selection has changed, in case any
12255      * subclasses would like to know.
12256      * </p>
12257      * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs
12258      * the accessibility subsystem about the selection change.
12259      * </p>
12260      *
12261      * @param selStart The new selection start location.
12262      * @param selEnd The new selection end location.
12263      */
12264     @CallSuper
onSelectionChanged(int selStart, int selEnd)12265     protected void onSelectionChanged(int selStart, int selEnd) {
12266         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
12267     }
12268 
12269     /**
12270      * Adds a TextWatcher to the list of those whose methods are called
12271      * whenever this TextView's text changes.
12272      * <p>
12273      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
12274      * not called after {@link #setText} calls.  Now, doing {@link #setText}
12275      * if there are any text changed listeners forces the buffer type to
12276      * Editable if it would not otherwise be and does call this method.
12277      */
addTextChangedListener(TextWatcher watcher)12278     public void addTextChangedListener(TextWatcher watcher) {
12279         if (mListeners == null) {
12280             mListeners = new ArrayList<TextWatcher>();
12281         }
12282 
12283         mListeners.add(watcher);
12284     }
12285 
12286     /**
12287      * Removes the specified TextWatcher from the list of those whose
12288      * methods are called
12289      * whenever this TextView's text changes.
12290      */
removeTextChangedListener(TextWatcher watcher)12291     public void removeTextChangedListener(TextWatcher watcher) {
12292         if (mListeners != null) {
12293             int i = mListeners.indexOf(watcher);
12294 
12295             if (i >= 0) {
12296                 mListeners.remove(i);
12297             }
12298         }
12299     }
12300 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)12301     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
12302         if (mListeners != null) {
12303             final ArrayList<TextWatcher> list = mListeners;
12304             final int count = list.size();
12305             for (int i = 0; i < count; i++) {
12306                 list.get(i).beforeTextChanged(text, start, before, after);
12307             }
12308         }
12309 
12310         // The spans that are inside or intersect the modified region no longer make sense
12311         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
12312         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
12313     }
12314 
12315     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)12316     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
12317         if (!(mText instanceof Editable)) return;
12318         Editable text = (Editable) mText;
12319 
12320         T[] spans = text.getSpans(start, end, type);
12321         ArrayList<T> spansToRemove = new ArrayList<>();
12322         for (T span : spans) {
12323             final int spanStart = text.getSpanStart(span);
12324             final int spanEnd = text.getSpanEnd(span);
12325             if (spanEnd == start || spanStart == end) continue;
12326             spansToRemove.add(span);
12327         }
12328         for (T span : spansToRemove) {
12329             text.removeSpan(span);
12330         }
12331     }
12332 
removeAdjacentSuggestionSpans(final int pos)12333     void removeAdjacentSuggestionSpans(final int pos) {
12334         if (!(mText instanceof Editable)) return;
12335         final Editable text = (Editable) mText;
12336 
12337         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
12338         final int length = spans.length;
12339         for (int i = 0; i < length; i++) {
12340             final int spanStart = text.getSpanStart(spans[i]);
12341             final int spanEnd = text.getSpanEnd(spans[i]);
12342             if (spanEnd == pos || spanStart == pos) {
12343                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
12344                     text.removeSpan(spans[i]);
12345                 }
12346             }
12347         }
12348     }
12349 
12350     /**
12351      * Not private so it can be called from an inner class without going
12352      * through a thunk.
12353      */
sendOnTextChanged(CharSequence text, int start, int before, int after)12354     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
12355         if (mListeners != null) {
12356             final ArrayList<TextWatcher> list = mListeners;
12357             final int count = list.size();
12358             for (int i = 0; i < count; i++) {
12359                 list.get(i).onTextChanged(text, start, before, after);
12360             }
12361         }
12362 
12363         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
12364     }
12365 
12366     /**
12367      * Not private so it can be called from an inner class without going
12368      * through a thunk.
12369      */
sendAfterTextChanged(Editable text)12370     void sendAfterTextChanged(Editable text) {
12371         if (mListeners != null) {
12372             final ArrayList<TextWatcher> list = mListeners;
12373             final int count = list.size();
12374             for (int i = 0; i < count; i++) {
12375                 list.get(i).afterTextChanged(text);
12376             }
12377         }
12378 
12379         notifyListeningManagersAfterTextChanged();
12380 
12381         hideErrorIfUnchanged();
12382     }
12383 
12384     /**
12385      * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
12386      * interested on text changes.
12387      */
notifyListeningManagersAfterTextChanged()12388     private void notifyListeningManagersAfterTextChanged() {
12389 
12390         // Autofill
12391         if (isAutofillable()) {
12392             // It is important to not check whether the view is important for autofill
12393             // since the user can trigger autofill manually on not important views.
12394             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
12395             if (afm != null) {
12396                 if (android.view.autofill.Helper.sVerbose) {
12397                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
12398                 }
12399                 afm.notifyValueChanged(TextView.this);
12400             }
12401         }
12402 
12403         notifyContentCaptureTextChanged();
12404     }
12405 
12406     /**
12407      * Notifies the ContentCapture service that the text of the view has changed (only if
12408      * ContentCapture has been notified of this view's existence already).
12409      *
12410      * @hide
12411      */
notifyContentCaptureTextChanged()12412     public void notifyContentCaptureTextChanged() {
12413         // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
12414         // of using isLaidout(), so it's not called in cases where it's laid out but a
12415         // notifyAppeared was not sent.
12416         if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) {
12417             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
12418             if (cm != null && cm.isContentCaptureEnabled()) {
12419                 final ContentCaptureSession session = getContentCaptureSession();
12420                 if (session != null) {
12421                     // TODO(b/111276913): pass flags when edited by user / add CTS test
12422                     session.notifyViewTextChanged(getAutofillId(), getText());
12423                 }
12424             }
12425         }
12426     }
12427 
isAutofillable()12428     private boolean isAutofillable() {
12429         // It is important to not check whether the view is important for autofill
12430         // since the user can trigger autofill manually on not important views.
12431         return getAutofillType() != AUTOFILL_TYPE_NONE;
12432     }
12433 
updateAfterEdit()12434     void updateAfterEdit() {
12435         invalidate();
12436         int curs = getSelectionStart();
12437 
12438         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
12439             registerForPreDraw();
12440         }
12441 
12442         checkForResize();
12443 
12444         if (curs >= 0) {
12445             mHighlightPathBogus = true;
12446             if (mEditor != null) mEditor.makeBlink();
12447             bringPointIntoView(curs);
12448         }
12449     }
12450 
12451     /**
12452      * Not private so it can be called from an inner class without going
12453      * through a thunk.
12454      */
handleTextChanged(CharSequence buffer, int start, int before, int after)12455     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
12456         sLastCutCopyOrTextChangedTime = 0;
12457 
12458         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
12459         if (ims == null || ims.mBatchEditNesting == 0) {
12460             updateAfterEdit();
12461         }
12462         if (ims != null) {
12463             ims.mContentChanged = true;
12464             if (ims.mChangedStart < 0) {
12465                 ims.mChangedStart = start;
12466                 ims.mChangedEnd = start + before;
12467             } else {
12468                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
12469                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
12470             }
12471             ims.mChangedDelta += after - before;
12472         }
12473         resetErrorChangedFlag();
12474         sendOnTextChanged(buffer, start, before, after);
12475         onTextChanged(buffer, start, before, after);
12476 
12477         mHideHint = false;
12478         clearGesturePreviewHighlight();
12479     }
12480 
12481     /**
12482      * Not private so it can be called from an inner class without going
12483      * through a thunk.
12484      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)12485     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
12486         // XXX Make the start and end move together if this ends up
12487         // spending too much time invalidating.
12488 
12489         boolean selChanged = false;
12490         int newSelStart = -1, newSelEnd = -1;
12491 
12492         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
12493 
12494         if (what == Selection.SELECTION_END) {
12495             selChanged = true;
12496             newSelEnd = newStart;
12497 
12498             if (oldStart >= 0 || newStart >= 0) {
12499                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
12500                 checkForResize();
12501                 registerForPreDraw();
12502                 if (mEditor != null) mEditor.makeBlink();
12503             }
12504         }
12505 
12506         if (what == Selection.SELECTION_START) {
12507             selChanged = true;
12508             newSelStart = newStart;
12509 
12510             if (oldStart >= 0 || newStart >= 0) {
12511                 int end = Selection.getSelectionEnd(buf);
12512                 invalidateCursor(end, oldStart, newStart);
12513             }
12514         }
12515 
12516         if (selChanged) {
12517             clearGesturePreviewHighlight();
12518             mHighlightPathBogus = true;
12519             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
12520 
12521             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
12522                 if (newSelStart < 0) {
12523                     newSelStart = Selection.getSelectionStart(buf);
12524                 }
12525                 if (newSelEnd < 0) {
12526                     newSelEnd = Selection.getSelectionEnd(buf);
12527                 }
12528 
12529                 if (mEditor != null) {
12530                     mEditor.refreshTextActionMode();
12531                     if (!hasSelection()
12532                             && mEditor.getTextActionMode() == null && hasTransientState()) {
12533                         // User generated selection has been removed.
12534                         setHasTransientState(false);
12535                     }
12536                 }
12537                 onSelectionChanged(newSelStart, newSelEnd);
12538             }
12539         }
12540 
12541         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
12542                 || what instanceof CharacterStyle) {
12543             if (ims == null || ims.mBatchEditNesting == 0) {
12544                 invalidate();
12545                 mHighlightPathBogus = true;
12546                 checkForResize();
12547             } else {
12548                 ims.mContentChanged = true;
12549             }
12550             if (mEditor != null) {
12551                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
12552                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
12553                 mEditor.invalidateHandlesAndActionMode();
12554             }
12555         }
12556 
12557         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
12558             mHighlightPathBogus = true;
12559             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
12560                 ims.mSelectionModeChanged = true;
12561             }
12562 
12563             if (Selection.getSelectionStart(buf) >= 0) {
12564                 if (ims == null || ims.mBatchEditNesting == 0) {
12565                     invalidateCursor();
12566                 } else {
12567                     ims.mCursorChanged = true;
12568                 }
12569             }
12570         }
12571 
12572         if (what instanceof ParcelableSpan) {
12573             // If this is a span that can be sent to a remote process,
12574             // the current extract editor would be interested in it.
12575             if (ims != null && ims.mExtractedTextRequest != null) {
12576                 if (ims.mBatchEditNesting != 0) {
12577                     if (oldStart >= 0) {
12578                         if (ims.mChangedStart > oldStart) {
12579                             ims.mChangedStart = oldStart;
12580                         }
12581                         if (ims.mChangedStart > oldEnd) {
12582                             ims.mChangedStart = oldEnd;
12583                         }
12584                     }
12585                     if (newStart >= 0) {
12586                         if (ims.mChangedStart > newStart) {
12587                             ims.mChangedStart = newStart;
12588                         }
12589                         if (ims.mChangedStart > newEnd) {
12590                             ims.mChangedStart = newEnd;
12591                         }
12592                     }
12593                 } else {
12594                     if (DEBUG_EXTRACT) {
12595                         Log.v(LOG_TAG, "Span change outside of batch: "
12596                                 + oldStart + "-" + oldEnd + ","
12597                                 + newStart + "-" + newEnd + " " + what);
12598                     }
12599                     ims.mContentChanged = true;
12600                 }
12601             }
12602         }
12603 
12604         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
12605                 && what instanceof SpellCheckSpan) {
12606             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
12607         }
12608     }
12609 
12610     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)12611     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
12612         if (isTemporarilyDetached()) {
12613             // If we are temporarily in the detach state, then do nothing.
12614             super.onFocusChanged(focused, direction, previouslyFocusedRect);
12615             return;
12616         }
12617 
12618         mHideHint = false;
12619 
12620         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
12621 
12622         if (focused) {
12623             if (mSpannable != null) {
12624                 MetaKeyKeyListener.resetMetaState(mSpannable);
12625             }
12626         }
12627 
12628         startStopMarquee(focused);
12629 
12630         if (mTransformation != null) {
12631             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
12632         }
12633 
12634         super.onFocusChanged(focused, direction, previouslyFocusedRect);
12635     }
12636 
12637     @Override
onWindowFocusChanged(boolean hasWindowFocus)12638     public void onWindowFocusChanged(boolean hasWindowFocus) {
12639         super.onWindowFocusChanged(hasWindowFocus);
12640 
12641         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
12642 
12643         startStopMarquee(hasWindowFocus);
12644     }
12645 
12646     @Override
onVisibilityChanged(View changedView, int visibility)12647     protected void onVisibilityChanged(View changedView, int visibility) {
12648         super.onVisibilityChanged(changedView, visibility);
12649         if (mEditor != null && visibility != VISIBLE) {
12650             mEditor.hideCursorAndSpanControllers();
12651             stopTextActionMode();
12652         }
12653     }
12654 
12655     @Override
onVisibilityAggregated(boolean isVisible)12656     public void onVisibilityAggregated(boolean isVisible) {
12657         super.onVisibilityAggregated(isVisible);
12658         startStopMarquee(isVisible);
12659     }
12660 
12661     /**
12662      * Use {@link BaseInputConnection#removeComposingSpans
12663      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
12664      * state from this text view.
12665      */
clearComposingText()12666     public void clearComposingText() {
12667         if (mText instanceof Spannable) {
12668             BaseInputConnection.removeComposingSpans(mSpannable);
12669         }
12670     }
12671 
12672     @Override
setSelected(boolean selected)12673     public void setSelected(boolean selected) {
12674         boolean wasSelected = isSelected();
12675 
12676         super.setSelected(selected);
12677 
12678         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
12679             if (selected) {
12680                 startMarquee();
12681             } else {
12682                 stopMarquee();
12683             }
12684         }
12685     }
12686 
12687     /**
12688      * Called from onTouchEvent() to prevent the touches by secondary fingers.
12689      * Dragging on handles can revise cursor/selection, so can dragging on the text view.
12690      * This method is a lock to avoid processing multiple fingers on both text view and handles.
12691      * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work.
12692      *
12693      * @param event The motion event that is being handled and carries the pointer info.
12694      * @param fromHandleView true if the event is delivered to selection handle or insertion
12695      * handle; false if this event is delivered to TextView.
12696      * @return Returns true to indicate that onTouchEvent() can continue processing the motion
12697      * event, otherwise false.
12698      *  - Always returns true for the first finger.
12699      *  - For secondary fingers, if the first or current finger is from TextView, returns false.
12700      *    This is to make touch mutually exclusive between the TextView and the handles, but
12701      *    not among the handles.
12702      */
isFromPrimePointer(MotionEvent event, boolean fromHandleView)12703     boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) {
12704         boolean res = true;
12705         if (mPrimePointerId == NO_POINTER_ID)  {
12706             mPrimePointerId = event.getPointerId(0);
12707             mIsPrimePointerFromHandleView = fromHandleView;
12708         } else if (mPrimePointerId != event.getPointerId(0)) {
12709             res = mIsPrimePointerFromHandleView && fromHandleView;
12710         }
12711         if (event.getActionMasked() == MotionEvent.ACTION_UP
12712             || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
12713             mPrimePointerId = -1;
12714         }
12715         return res;
12716     }
12717 
12718     @Override
onTouchEvent(MotionEvent event)12719     public boolean onTouchEvent(MotionEvent event) {
12720         if (DEBUG_CURSOR) {
12721             logCursor("onTouchEvent", "%d: %s (%f,%f)",
12722                     event.getSequenceNumber(),
12723                     MotionEvent.actionToString(event.getActionMasked()),
12724                     event.getX(), event.getY());
12725         }
12726         mLastInputSource = event.getSource();
12727         final int action = event.getActionMasked();
12728         if (mEditor != null) {
12729             if (!isFromPrimePointer(event, false)) {
12730                 return true;
12731             }
12732 
12733             mEditor.onTouchEvent(event);
12734 
12735             if (mEditor.mInsertionPointCursorController != null
12736                     && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
12737                 return true;
12738             }
12739             if (mEditor.mSelectionModifierCursorController != null
12740                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
12741                 return true;
12742             }
12743         }
12744 
12745         final boolean superResult = super.onTouchEvent(event);
12746         if (DEBUG_CURSOR) {
12747             logCursor("onTouchEvent", "superResult=%s", superResult);
12748         }
12749 
12750         /*
12751          * Don't handle the release after a long press, because it will move the selection away from
12752          * whatever the menu action was trying to affect. If the long press should have triggered an
12753          * insertion action mode, we can now actually show it.
12754          */
12755         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
12756             mEditor.mDiscardNextActionUp = false;
12757             if (DEBUG_CURSOR) {
12758                 logCursor("onTouchEvent", "release after long press detected");
12759             }
12760             if (mEditor.mIsInsertionActionModeStartPending) {
12761                 mEditor.startInsertionActionMode();
12762                 mEditor.mIsInsertionActionModeStartPending = false;
12763             }
12764             return superResult;
12765         }
12766 
12767         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
12768                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
12769 
12770         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
12771                 && mText instanceof Spannable && mLayout != null) {
12772             boolean handled = false;
12773 
12774             if (mMovement != null) {
12775                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
12776             }
12777 
12778             final boolean textIsSelectable = isTextSelectable();
12779             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
12780                 // The LinkMovementMethod which should handle taps on links has not been installed
12781                 // on non editable text that support text selection.
12782                 // We reproduce its behavior here to open links for these.
12783                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
12784                     getSelectionEnd(), ClickableSpan.class);
12785 
12786                 if (links.length > 0) {
12787                     links[0].onClick(this);
12788                     handled = true;
12789                 }
12790             }
12791 
12792             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
12793                 // Show the IME, except when selecting in read-only text.
12794                 final InputMethodManager imm = getInputMethodManager();
12795                 viewClicked(imm);
12796                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null
12797                         && !showAutofillDialog()) {
12798                     imm.showSoftInput(this, 0);
12799                 }
12800 
12801                 // The above condition ensures that the mEditor is not null
12802                 mEditor.onTouchUpEvent(event);
12803 
12804                 handled = true;
12805             }
12806 
12807             if (handled) {
12808                 return true;
12809             }
12810         }
12811 
12812         return superResult;
12813     }
12814 
12815     /**
12816      * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
12817      *
12818      * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
12819      * @hide
12820      */
showUIForTouchScreen()12821     public final boolean showUIForTouchScreen() {
12822         return (mLastInputSource & InputDevice.SOURCE_TOUCHSCREEN)
12823                 == InputDevice.SOURCE_TOUCHSCREEN;
12824     }
12825 
12826     /**
12827      * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI.
12828      * If autofill suggestions are available when the user clicks on a field that supports filling
12829      * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area
12830      * to display the datasets, so it is easy for users to pay attention to the datasets and
12831      * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better
12832      * experience is not to show the IME if there is a fill dialog.
12833      */
showAutofillDialog()12834     private boolean showAutofillDialog() {
12835         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
12836         if (afm != null) {
12837             return afm.showAutofillDialog(this);
12838         }
12839         return false;
12840     }
12841 
12842     @Override
onGenericMotionEvent(MotionEvent event)12843     public boolean onGenericMotionEvent(MotionEvent event) {
12844         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
12845             try {
12846                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
12847                     return true;
12848                 }
12849             } catch (AbstractMethodError ex) {
12850                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
12851                 // Ignore its absence in case third party applications implemented the
12852                 // interface directly.
12853             }
12854         }
12855         return super.onGenericMotionEvent(event);
12856     }
12857 
12858     @Override
onCreateContextMenu(ContextMenu menu)12859     protected void onCreateContextMenu(ContextMenu menu) {
12860         if (mEditor != null) {
12861             mEditor.onCreateContextMenu(menu);
12862         }
12863     }
12864 
12865     @Override
showContextMenu()12866     public boolean showContextMenu() {
12867         if (mEditor != null) {
12868             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
12869         }
12870         return super.showContextMenu();
12871     }
12872 
12873     @Override
showContextMenu(float x, float y)12874     public boolean showContextMenu(float x, float y) {
12875         if (mEditor != null) {
12876             mEditor.setContextMenuAnchor(x, y);
12877         }
12878         return super.showContextMenu(x, y);
12879     }
12880 
12881     /**
12882      * @return True iff this TextView contains a text that can be edited, or if this is
12883      * a selectable TextView.
12884      */
12885     @UnsupportedAppUsage
isTextEditable()12886     boolean isTextEditable() {
12887         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
12888     }
12889 
12890     /**
12891      * @return true if this TextView could be filled by an Autofill service. Note that disabled
12892      * fields can still be filled.
12893      */
12894     @UnsupportedAppUsage
isTextAutofillable()12895     boolean isTextAutofillable() {
12896         return mText instanceof Editable && onCheckIsTextEditor();
12897     }
12898 
12899     /**
12900      * Returns true, only while processing a touch gesture, if the initial
12901      * touch down event caused focus to move to the text view and as a result
12902      * its selection changed.  Only valid while processing the touch gesture
12903      * of interest, in an editable text view.
12904      */
didTouchFocusSelect()12905     public boolean didTouchFocusSelect() {
12906         return mEditor != null && mEditor.mTouchFocusSelected;
12907     }
12908 
12909     @Override
cancelLongPress()12910     public void cancelLongPress() {
12911         super.cancelLongPress();
12912         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
12913     }
12914 
12915     @Override
onTrackballEvent(MotionEvent event)12916     public boolean onTrackballEvent(MotionEvent event) {
12917         if (mMovement != null && mSpannable != null && mLayout != null) {
12918             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
12919                 return true;
12920             }
12921         }
12922 
12923         return super.onTrackballEvent(event);
12924     }
12925 
12926     /**
12927      * Sets the Scroller used for producing a scrolling animation
12928      *
12929      * @param s A Scroller instance
12930      */
setScroller(Scroller s)12931     public void setScroller(Scroller s) {
12932         mScroller = s;
12933     }
12934 
12935     @Override
getLeftFadingEdgeStrength()12936     protected float getLeftFadingEdgeStrength() {
12937         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
12938             final Marquee marquee = mMarquee;
12939             if (marquee.shouldDrawLeftFade()) {
12940                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
12941             } else {
12942                 return 0.0f;
12943             }
12944         } else if (getLineCount() == 1) {
12945             final float lineLeft = getLayout().getLineLeft(0);
12946             if (lineLeft > mScrollX) return 0.0f;
12947             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
12948         }
12949         return super.getLeftFadingEdgeStrength();
12950     }
12951 
12952     @Override
getRightFadingEdgeStrength()12953     protected float getRightFadingEdgeStrength() {
12954         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
12955             final Marquee marquee = mMarquee;
12956             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
12957         } else if (getLineCount() == 1) {
12958             final float rightEdge = mScrollX +
12959                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
12960             final float lineRight = getLayout().getLineRight(0);
12961             if (lineRight < rightEdge) return 0.0f;
12962             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
12963         }
12964         return super.getRightFadingEdgeStrength();
12965     }
12966 
12967     /**
12968      * Calculates the fading edge strength as the ratio of the distance between two
12969      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
12970      * value for the distance calculation.
12971      *
12972      * @param position1 A horizontal position.
12973      * @param position2 A horizontal position.
12974      * @return Fading edge strength between [0.0f, 1.0f].
12975      */
12976     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)12977     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
12978         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
12979         if (horizontalFadingEdgeLength == 0) return 0.0f;
12980         final float diff = Math.abs(position1 - position2);
12981         if (diff > horizontalFadingEdgeLength) return 1.0f;
12982         return diff / horizontalFadingEdgeLength;
12983     }
12984 
isMarqueeFadeEnabled()12985     private boolean isMarqueeFadeEnabled() {
12986         return mEllipsize == TextUtils.TruncateAt.MARQUEE
12987                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
12988     }
12989 
12990     @Override
computeHorizontalScrollRange()12991     protected int computeHorizontalScrollRange() {
12992         if (mLayout != null) {
12993             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
12994                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
12995         }
12996 
12997         return super.computeHorizontalScrollRange();
12998     }
12999 
13000     @Override
computeVerticalScrollRange()13001     protected int computeVerticalScrollRange() {
13002         if (mLayout != null) {
13003             return mLayout.getHeight();
13004         }
13005         return super.computeVerticalScrollRange();
13006     }
13007 
13008     @Override
computeVerticalScrollExtent()13009     protected int computeVerticalScrollExtent() {
13010         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
13011     }
13012 
13013     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)13014     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
13015         super.findViewsWithText(outViews, searched, flags);
13016         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
13017                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
13018             String searchedLowerCase = searched.toString().toLowerCase();
13019             String textLowerCase = mText.toString().toLowerCase();
13020             if (textLowerCase.contains(searchedLowerCase)) {
13021                 outViews.add(this);
13022             }
13023         }
13024     }
13025 
13026     /**
13027      * Type of the text buffer that defines the characteristics of the text such as static,
13028      * styleable, or editable.
13029      */
13030     public enum BufferType {
13031         NORMAL, SPANNABLE, EDITABLE
13032     }
13033 
13034     /**
13035      * Returns the TextView_textColor attribute from the TypedArray, if set, or
13036      * the TextAppearance_textColor from the TextView_textAppearance attribute,
13037      * if TextView_textColor was not set directly.
13038      *
13039      * @removed
13040      */
getTextColors(Context context, TypedArray attrs)13041     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
13042         if (attrs == null) {
13043             // Preserve behavior prior to removal of this API.
13044             throw new NullPointerException();
13045         }
13046 
13047         // It's not safe to use this method from apps. The parameter 'attrs'
13048         // must have been obtained using the TextView filter array which is not
13049         // available to the SDK. As such, we grab a default TypedArray with the
13050         // right filter instead here.
13051         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
13052         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
13053         if (colors == null) {
13054             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
13055             if (ap != 0) {
13056                 final TypedArray appearance = context.obtainStyledAttributes(
13057                         ap, R.styleable.TextAppearance);
13058                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
13059                 appearance.recycle();
13060             }
13061         }
13062         a.recycle();
13063 
13064         return colors;
13065     }
13066 
13067     /**
13068      * Returns the default color from the TextView_textColor attribute from the
13069      * AttributeSet, if set, or the default color from the
13070      * TextAppearance_textColor from the TextView_textAppearance attribute, if
13071      * TextView_textColor was not set directly.
13072      *
13073      * @removed
13074      */
getTextColor(Context context, TypedArray attrs, int def)13075     public static int getTextColor(Context context, TypedArray attrs, int def) {
13076         final ColorStateList colors = getTextColors(context, attrs);
13077         if (colors == null) {
13078             return def;
13079         } else {
13080             return colors.getDefaultColor();
13081         }
13082     }
13083 
13084     @Override
onKeyShortcut(int keyCode, KeyEvent event)13085     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
13086         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
13087             // Handle Ctrl-only shortcuts.
13088             switch (keyCode) {
13089                 case KeyEvent.KEYCODE_A:
13090                     if (canSelectText()) {
13091                         return onTextContextMenuItem(ID_SELECT_ALL);
13092                     }
13093                     break;
13094                 case KeyEvent.KEYCODE_Z:
13095                     if (canUndo()) {
13096                         return onTextContextMenuItem(ID_UNDO);
13097                     }
13098                     break;
13099                 case KeyEvent.KEYCODE_X:
13100                     if (canCut()) {
13101                         return onTextContextMenuItem(ID_CUT);
13102                     }
13103                     break;
13104                 case KeyEvent.KEYCODE_C:
13105                     if (canCopy()) {
13106                         return onTextContextMenuItem(ID_COPY);
13107                     }
13108                     break;
13109                 case KeyEvent.KEYCODE_V:
13110                     if (canPaste()) {
13111                         return onTextContextMenuItem(ID_PASTE);
13112                     }
13113                     break;
13114                 case KeyEvent.KEYCODE_Y:
13115                     if (canRedo()) {
13116                         return onTextContextMenuItem(ID_REDO);
13117                     }
13118                     break;
13119             }
13120         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
13121             // Handle Ctrl-Shift shortcuts.
13122             switch (keyCode) {
13123                 case KeyEvent.KEYCODE_Z:
13124                     if (canRedo()) {
13125                         return onTextContextMenuItem(ID_REDO);
13126                     }
13127                     break;
13128                 case KeyEvent.KEYCODE_V:
13129                     if (canPaste()) {
13130                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
13131                     }
13132             }
13133         }
13134         return super.onKeyShortcut(keyCode, event);
13135     }
13136 
13137     /**
13138      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
13139      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
13140      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
13141      * sufficient.
13142      */
canSelectText()13143     boolean canSelectText() {
13144         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
13145     }
13146 
13147     /**
13148      * Test based on the <i>intrinsic</i> charateristics of the TextView.
13149      * The text must be spannable and the movement method must allow for arbitary selection.
13150      *
13151      * See also {@link #canSelectText()}.
13152      */
textCanBeSelected()13153     boolean textCanBeSelected() {
13154         // prepareCursorController() relies on this method.
13155         // If you change this condition, make sure prepareCursorController is called anywhere
13156         // the value of this condition might be changed.
13157         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
13158         return isTextEditable()
13159                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
13160     }
13161 
13162     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)13163     private Locale getTextServicesLocale(boolean allowNullLocale) {
13164         // Start fetching the text services locale asynchronously.
13165         updateTextServicesLocaleAsync();
13166         // If !allowNullLocale and there is no cached text services locale, just return the default
13167         // locale.
13168         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
13169                 : mCurrentSpellCheckerLocaleCache;
13170     }
13171 
13172     /**
13173      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
13174      * this {@link TextView}.
13175      *
13176      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
13177      * other apps may need to set this so that the system can user right user's resources and
13178      * services such as input methods and spell checkers.</p>
13179      *
13180      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
13181      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
13182      * @hide
13183      */
13184     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)13185     public final void setTextOperationUser(@Nullable UserHandle user) {
13186         if (Objects.equals(mTextOperationUser, user)) {
13187             return;
13188         }
13189         if (user != null && !Process.myUserHandle().equals(user)) {
13190             // Just for preventing people from accidentally using this hidden API without
13191             // the required permission.  The same permission is also checked in the system server.
13192             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
13193                     != PackageManager.PERMISSION_GRANTED) {
13194                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
13195                         + " userId=" + user.getIdentifier()
13196                         + " callingUserId" + UserHandle.myUserId());
13197             }
13198         }
13199         mTextOperationUser = user;
13200         // Invalidate some resources
13201         mCurrentSpellCheckerLocaleCache = null;
13202         if (mEditor != null) {
13203             mEditor.onTextOperationUserChanged();
13204         }
13205     }
13206 
13207     @Override
isAutoHandwritingEnabled()13208     public boolean isAutoHandwritingEnabled() {
13209         return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType();
13210     }
13211 
13212     /** @hide */
13213     @Override
isStylusHandwritingAvailable()13214     public boolean isStylusHandwritingAvailable() {
13215         if (mTextOperationUser == null) {
13216             return super.isStylusHandwritingAvailable();
13217         }
13218         final int userId = mTextOperationUser.getIdentifier();
13219         final InputMethodManager imm = getInputMethodManager();
13220         return imm.isStylusHandwritingAvailableAsUser(userId);
13221     }
13222 
13223     @Nullable
getTextServicesManagerForUser()13224     final TextServicesManager getTextServicesManagerForUser() {
13225         return getServiceManagerForUser("android", TextServicesManager.class);
13226     }
13227 
13228     @Nullable
getClipboardManagerForUser()13229     final ClipboardManager getClipboardManagerForUser() {
13230         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
13231     }
13232 
13233     @Nullable
getTextClassificationManagerForUser()13234     final TextClassificationManager getTextClassificationManagerForUser() {
13235         return getServiceManagerForUser(
13236                 getContext().getPackageName(), TextClassificationManager.class);
13237     }
13238 
13239     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)13240     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
13241         if (mTextOperationUser == null) {
13242             return getContext().getSystemService(managerClazz);
13243         }
13244         try {
13245             Context context = getContext().createPackageContextAsUser(
13246                     packageName, 0 /* flags */, mTextOperationUser);
13247             return context.getSystemService(managerClazz);
13248         } catch (PackageManager.NameNotFoundException e) {
13249             return null;
13250         }
13251     }
13252 
13253     /**
13254      * Starts {@link Activity} as a text-operation user if it is specified with
13255      * {@link #setTextOperationUser(UserHandle)}.
13256      *
13257      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
13258      *
13259      * @param intent The description of the activity to start.
13260      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)13261     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
13262         if (mTextOperationUser != null) {
13263             getContext().startActivityAsUser(intent, mTextOperationUser);
13264         } else {
13265             getContext().startActivity(intent);
13266         }
13267     }
13268 
13269     /**
13270      * This is a temporary method. Future versions may support multi-locale text.
13271      * Caveat: This method may not return the latest text services locale, but this should be
13272      * acceptable and it's more important to make this method asynchronous.
13273      *
13274      * @return The locale that should be used for a word iterator
13275      * in this TextView, based on the current spell checker settings,
13276      * the current IME's locale, or the system default locale.
13277      * Please note that a word iterator in this TextView is different from another word iterator
13278      * used by SpellChecker.java of TextView. This method should be used for the former.
13279      * @hide
13280      */
13281     // TODO: Support multi-locale
13282     // TODO: Update the text services locale immediately after the keyboard locale is switched
13283     // by catching intent of keyboard switch event
getTextServicesLocale()13284     public Locale getTextServicesLocale() {
13285         return getTextServicesLocale(false /* allowNullLocale */);
13286     }
13287 
13288     /**
13289      * @return {@code true} if this TextView is specialized for showing and interacting with the
13290      * extracted text in a full-screen input method.
13291      * @hide
13292      */
isInExtractedMode()13293     public boolean isInExtractedMode() {
13294         return false;
13295     }
13296 
13297     /**
13298      * @return {@code true} if this widget supports auto-sizing text and has been configured to
13299      * auto-size.
13300      */
isAutoSizeEnabled()13301     private boolean isAutoSizeEnabled() {
13302         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
13303     }
13304 
13305     /**
13306      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
13307      * @hide
13308      */
supportsAutoSizeText()13309     protected boolean supportsAutoSizeText() {
13310         return true;
13311     }
13312 
13313     /**
13314      * This is a temporary method. Future versions may support multi-locale text.
13315      * Caveat: This method may not return the latest spell checker locale, but this should be
13316      * acceptable and it's more important to make this method asynchronous.
13317      *
13318      * @return The locale that should be used for a spell checker in this TextView,
13319      * based on the current spell checker settings, the current IME's locale, or the system default
13320      * locale.
13321      * @hide
13322      */
getSpellCheckerLocale()13323     public Locale getSpellCheckerLocale() {
13324         return getTextServicesLocale(true /* allowNullLocale */);
13325     }
13326 
updateTextServicesLocaleAsync()13327     private void updateTextServicesLocaleAsync() {
13328         // AsyncTask.execute() uses a serial executor which means we don't have
13329         // to lock around updateTextServicesLocaleLocked() to prevent it from
13330         // being executed n times in parallel.
13331         AsyncTask.execute(new Runnable() {
13332             @Override
13333             public void run() {
13334                 updateTextServicesLocaleLocked();
13335             }
13336         });
13337     }
13338 
13339     @UnsupportedAppUsage
updateTextServicesLocaleLocked()13340     private void updateTextServicesLocaleLocked() {
13341         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
13342         if (textServicesManager == null) {
13343             return;
13344         }
13345         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
13346         final Locale locale;
13347         if (subtype != null) {
13348             locale = subtype.getLocaleObject();
13349         } else {
13350             locale = null;
13351         }
13352         mCurrentSpellCheckerLocaleCache = locale;
13353     }
13354 
onLocaleChanged()13355     void onLocaleChanged() {
13356         mEditor.onLocaleChanged();
13357     }
13358 
13359     /**
13360      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
13361      * Made available to achieve a consistent behavior.
13362      * @hide
13363      */
getWordIterator()13364     public WordIterator getWordIterator() {
13365         if (mEditor != null) {
13366             return mEditor.getWordIterator();
13367         } else {
13368             return null;
13369         }
13370     }
13371 
13372     /** @hide */
13373     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)13374     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
13375         super.onPopulateAccessibilityEventInternal(event);
13376 
13377         if (this.isAccessibilityDataSensitive() && !event.isAccessibilityDataSensitive()) {
13378             // This view's accessibility data is sensitive, but another view that generated this
13379             // event is not, so don't append this view's text to the event in order to prevent
13380             // sharing this view's contents with non-accessibility-tool services.
13381             return;
13382         }
13383 
13384         final CharSequence text = getTextForAccessibility();
13385         if (!TextUtils.isEmpty(text)) {
13386             event.getText().add(text);
13387         }
13388     }
13389 
13390     @Override
getAccessibilityClassName()13391     public CharSequence getAccessibilityClassName() {
13392         return TextView.class.getName();
13393     }
13394 
13395     /** @hide */
13396     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)13397     protected void onProvideStructure(@NonNull ViewStructure structure,
13398             @ViewStructureType int viewFor, int flags) {
13399         super.onProvideStructure(structure, viewFor, flags);
13400 
13401         final boolean isPassword = hasPasswordTransformationMethod()
13402                 || isPasswordInputType(getInputType());
13403         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
13404                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13405             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13406                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
13407             }
13408             if (mTextId != Resources.ID_NULL) {
13409                 try {
13410                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
13411                 } catch (Resources.NotFoundException e) {
13412                     if (android.view.autofill.Helper.sVerbose) {
13413                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
13414                                 + mTextId + ": " + e.getMessage());
13415                     }
13416                 }
13417             }
13418             String[] mimeTypes = getReceiveContentMimeTypes();
13419             if (mimeTypes == null && mEditor != null) {
13420                 // If the app hasn't set a listener for receiving content on this view (ie,
13421                 // getReceiveContentMimeTypes() returns null), check if it implements the
13422                 // keyboard image API and, if possible, use those MIME types as fallback.
13423                 // This fallback is only in place for autofill, not other mechanisms for
13424                 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER
13425                 // in TextViewOnReceiveContentListener for more info.
13426                 mimeTypes = mEditor.getDefaultOnReceiveContentListener()
13427                         .getFallbackMimeTypesForAutofill(this);
13428             }
13429             structure.setReceiveContentMimeTypes(mimeTypes);
13430         }
13431 
13432         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
13433                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13434             if (mLayout == null) {
13435                 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13436                     Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
13437                 }
13438                 assumeLayout();
13439             }
13440             Layout layout = mLayout;
13441             final int lineCount = layout.getLineCount();
13442             if (lineCount <= 1) {
13443                 // Simple case: this is a single line.
13444                 final CharSequence text = getText();
13445                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13446                     structure.setText(text);
13447                 } else {
13448                     structure.setText(text, getSelectionStart(), getSelectionEnd());
13449                 }
13450             } else {
13451                 // Complex case: multi-line, could be scrolled or within a scroll container
13452                 // so some lines are not visible.
13453                 final int[] tmpCords = new int[2];
13454                 getLocationInWindow(tmpCords);
13455                 final int topWindowLocation = tmpCords[1];
13456                 View root = this;
13457                 ViewParent viewParent = getParent();
13458                 while (viewParent instanceof View) {
13459                     root = (View) viewParent;
13460                     viewParent = root.getParent();
13461                 }
13462                 final int windowHeight = root.getHeight();
13463                 final int topLine;
13464                 final int bottomLine;
13465                 if (topWindowLocation >= 0) {
13466                     // The top of the view is fully within its window; start text at line 0.
13467                     topLine = getLineAtCoordinateUnclamped(0);
13468                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
13469                 } else {
13470                     // The top of hte window has scrolled off the top of the window; figure out
13471                     // the starting line for this.
13472                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
13473                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
13474                 }
13475                 // We want to return some contextual lines above/below the lines that are
13476                 // actually visible.
13477                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
13478                 if (expandedTopLine < 0) {
13479                     expandedTopLine = 0;
13480                 }
13481                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
13482                 if (expandedBottomLine >= lineCount) {
13483                     expandedBottomLine = lineCount - 1;
13484                 }
13485 
13486                 // Convert lines into character offsets.
13487                 int expandedTopChar = transformedToOriginal(
13488                         layout.getLineStart(expandedTopLine),
13489                         OffsetMapping.MAP_STRATEGY_CHARACTER);
13490                 int expandedBottomChar = transformedToOriginal(
13491                         layout.getLineEnd(expandedBottomLine),
13492                         OffsetMapping.MAP_STRATEGY_CHARACTER);
13493 
13494                 // Take into account selection -- if there is a selection, we need to expand
13495                 // the text we are returning to include that selection.
13496                 final int selStart = getSelectionStart();
13497                 final int selEnd = getSelectionEnd();
13498                 if (selStart < selEnd) {
13499                     if (selStart < expandedTopChar) {
13500                         expandedTopChar = selStart;
13501                     }
13502                     if (selEnd > expandedBottomChar) {
13503                         expandedBottomChar = selEnd;
13504                     }
13505                 }
13506 
13507                 // Get the text and trim it to the range we are reporting.
13508                 CharSequence text = getText();
13509 
13510                 if (text != null) {
13511                     if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
13512                         // Cap the offsets to avoid an OOB exception. That can happen if the
13513                         // displayed/layout text, on which these offsets are calculated, is longer
13514                         // than the original text (such as when the view is translated by the
13515                         // platform intelligence).
13516                         // TODO(b/196433694): Figure out how to better handle the offset
13517                         // calculations for this case (so we don't unnecessarily cutoff the original
13518                         // text, for example).
13519                         expandedTopChar = Math.min(expandedTopChar, text.length());
13520                         expandedBottomChar = Math.min(expandedBottomChar, text.length());
13521                         text = text.subSequence(expandedTopChar, expandedBottomChar);
13522                     }
13523 
13524                     if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13525                         structure.setText(text);
13526                     } else {
13527                         structure.setText(text,
13528                                 selStart - expandedTopChar,
13529                                 selEnd - expandedTopChar);
13530 
13531                         final int[] lineOffsets = new int[bottomLine - topLine + 1];
13532                         final int[] lineBaselines = new int[bottomLine - topLine + 1];
13533                         final int baselineOffset = getBaselineOffset();
13534                         for (int i = topLine; i <= bottomLine; i++) {
13535                             lineOffsets[i - topLine] = transformedToOriginal(layout.getLineStart(i),
13536                                     OffsetMapping.MAP_STRATEGY_CHARACTER);
13537                             lineBaselines[i - topLine] =
13538                                     layout.getLineBaseline(i) + baselineOffset;
13539                         }
13540                         structure.setTextLines(lineOffsets, lineBaselines);
13541                     }
13542                 }
13543             }
13544 
13545             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
13546                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13547                 // Extract style information that applies to the TextView as a whole.
13548                 int style = 0;
13549                 int typefaceStyle = getTypefaceStyle();
13550                 if ((typefaceStyle & Typeface.BOLD) != 0) {
13551                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
13552                 }
13553                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
13554                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
13555                 }
13556 
13557                 // Global styles can also be set via TextView.setPaintFlags().
13558                 int paintFlags = mTextPaint.getFlags();
13559                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
13560                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
13561                 }
13562                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
13563                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
13564                 }
13565                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
13566                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
13567                 }
13568 
13569                 // TextView does not have its own text background color. A background is either part
13570                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
13571                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
13572                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
13573             }
13574             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
13575                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13576                 structure.setMinTextEms(getMinEms());
13577                 structure.setMaxTextEms(getMaxEms());
13578                 int maxLength = -1;
13579                 for (InputFilter filter: getFilters()) {
13580                     if (filter instanceof InputFilter.LengthFilter) {
13581                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
13582                         break;
13583                     }
13584                 }
13585                 structure.setMaxTextLength(maxLength);
13586             }
13587         }
13588         if (mHintId != Resources.ID_NULL) {
13589             try {
13590                 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId));
13591             } catch (Resources.NotFoundException e) {
13592                 if (android.view.autofill.Helper.sVerbose) {
13593                     Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id "
13594                             + mHintId + ": " + e.getMessage());
13595                 }
13596             }
13597         }
13598         structure.setHint(getHint());
13599         structure.setInputType(getInputType());
13600     }
13601 
canRequestAutofill()13602     boolean canRequestAutofill() {
13603         if (!isAutofillable()) {
13604             return false;
13605         }
13606         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
13607         if (afm != null) {
13608             return afm.isEnabled();
13609         }
13610         return false;
13611     }
13612 
requestAutofill()13613     private void requestAutofill() {
13614         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
13615         if (afm != null) {
13616             afm.requestAutofill(this);
13617         }
13618     }
13619 
13620     @Override
autofill(AutofillValue value)13621     public void autofill(AutofillValue value) {
13622         if (!isTextAutofillable()) {
13623             Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
13624             return;
13625         }
13626         if (!value.isText()) {
13627             Log.w(LOG_TAG, "value of type " + value.describeContents()
13628                     + " cannot be autofilled into " + this);
13629             return;
13630         }
13631         final ClipData clip = ClipData.newPlainText("", value.getTextValue());
13632         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build();
13633         performReceiveContent(payload);
13634     }
13635 
13636     @Override
getAutofillType()13637     public @AutofillType int getAutofillType() {
13638         return isTextAutofillable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
13639     }
13640 
13641     /**
13642      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
13643      * {@code char}s if longer.
13644      *
13645      * @return current text, {@code null} if the text is not editable
13646      *
13647      * @see View#getAutofillValue()
13648      */
13649     @Override
13650     @Nullable
getAutofillValue()13651     public AutofillValue getAutofillValue() {
13652         if (isTextAutofillable()) {
13653             final CharSequence text = TextUtils.trimToParcelableSize(getText());
13654             return AutofillValue.forText(text);
13655         }
13656         return null;
13657     }
13658 
13659     /** @hide */
13660     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)13661     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
13662         super.onInitializeAccessibilityEventInternal(event);
13663 
13664         final boolean isPassword = hasPasswordTransformationMethod();
13665         event.setPassword(isPassword);
13666 
13667         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
13668             event.setFromIndex(Selection.getSelectionStart(mText));
13669             event.setToIndex(Selection.getSelectionEnd(mText));
13670             event.setItemCount(mText.length());
13671         }
13672     }
13673 
13674     /** @hide */
13675     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)13676     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
13677         super.onInitializeAccessibilityNodeInfoInternal(info);
13678 
13679         final boolean isPassword = hasPasswordTransformationMethod();
13680         info.setPassword(isPassword);
13681         info.setText(getTextForAccessibility());
13682         info.setHintText(mHint);
13683         info.setShowingHintText(isShowingHint());
13684 
13685         if (mBufferType == BufferType.EDITABLE) {
13686             info.setEditable(true);
13687             if (isEnabled()) {
13688                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
13689             }
13690         }
13691 
13692         if (mEditor != null) {
13693             info.setInputType(mEditor.mInputType);
13694 
13695             if (mEditor.mError != null) {
13696                 info.setContentInvalid(true);
13697                 info.setError(mEditor.mError);
13698             }
13699             // TextView will expose this action if it is editable and has focus.
13700             if (isTextEditable() && isFocused()) {
13701                 CharSequence imeActionLabel = mContext.getResources().getString(
13702                         com.android.internal.R.string.keyboardview_keycode_enter);
13703                 if (getImeActionLabel() != null) {
13704                     imeActionLabel = getImeActionLabel();
13705                 }
13706                 AccessibilityNodeInfo.AccessibilityAction action =
13707                         new AccessibilityNodeInfo.AccessibilityAction(
13708                                 R.id.accessibilityActionImeEnter, imeActionLabel);
13709                 info.addAction(action);
13710             }
13711         }
13712 
13713         if (!TextUtils.isEmpty(mText)) {
13714             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
13715             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
13716             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
13717                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
13718                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
13719                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
13720                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
13721             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
13722             info.setAvailableExtraData(Arrays.asList(
13723                     EXTRA_DATA_RENDERING_INFO_KEY,
13724                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
13725             ));
13726             info.setTextSelectable(isTextSelectable() || isTextEditable());
13727         } else {
13728             info.setAvailableExtraData(Arrays.asList(
13729                     EXTRA_DATA_RENDERING_INFO_KEY
13730             ));
13731         }
13732 
13733         if (isFocused()) {
13734             if (canCopy()) {
13735                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
13736             }
13737             if (canPaste()) {
13738                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
13739             }
13740             if (canCut()) {
13741                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
13742             }
13743             if (canReplace()) {
13744                 info.addAction(
13745                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS);
13746             }
13747             if (canShare()) {
13748                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
13749                         ACCESSIBILITY_ACTION_SHARE,
13750                         getResources().getString(com.android.internal.R.string.share)));
13751             }
13752             if (canProcessText()) {  // also implies mEditor is not null.
13753                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
13754                 mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info);
13755             }
13756         }
13757 
13758         // Check for known input filter types.
13759         final int numFilters = mFilters.length;
13760         for (int i = 0; i < numFilters; i++) {
13761             final InputFilter filter = mFilters[i];
13762             if (filter instanceof InputFilter.LengthFilter) {
13763                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
13764             }
13765         }
13766 
13767         if (!isSingleLine()) {
13768             info.setMultiLine(true);
13769         }
13770 
13771         // A view should not be exposed as clickable/long-clickable to a service because of a
13772         // LinkMovementMethod or because it has selectable and non-editable text.
13773         if ((info.isClickable() || info.isLongClickable())
13774                 && (mMovement instanceof LinkMovementMethod
13775                 || (isTextSelectable() && !isTextEditable()))) {
13776             if (!hasOnClickListeners()) {
13777                 info.setClickable(false);
13778                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
13779             }
13780             if (!hasOnLongClickListeners()) {
13781                 info.setLongClickable(false);
13782                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
13783             }
13784         }
13785     }
13786 
13787     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)13788     public void addExtraDataToAccessibilityNodeInfo(
13789             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
13790         if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
13791             int positionInfoStartIndex = arguments.getInt(
13792                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
13793             int positionInfoLength = arguments.getInt(
13794                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
13795             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
13796                     || (positionInfoStartIndex >= mText.length())) {
13797                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
13798                 return;
13799             }
13800             RectF[] boundingRects = new RectF[positionInfoLength];
13801             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
13802             populateCharacterBounds(builder, positionInfoStartIndex,
13803                     Math.min(positionInfoStartIndex + positionInfoLength, length()),
13804                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
13805             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
13806             for (int i = 0; i < positionInfoLength; i++) {
13807                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
13808                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
13809                     RectF bounds = cursorAnchorInfo
13810                             .getCharacterBounds(positionInfoStartIndex + i);
13811                     if (bounds != null) {
13812                         mapRectFromViewToScreenCoords(bounds, true);
13813                         boundingRects[i] = bounds;
13814                     }
13815                 }
13816             }
13817             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
13818             return;
13819         }
13820         if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) {
13821             final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo =
13822                     AccessibilityNodeInfo.ExtraRenderingInfo.obtain();
13823             extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height);
13824             extraRenderingInfo.setTextSizeInPx(getTextSize());
13825             extraRenderingInfo.setTextSizeUnit(getTextSizeUnit());
13826             info.setExtraRenderingInfo(extraRenderingInfo);
13827         }
13828     }
13829 
13830     /**
13831      * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
13832      * This method obtains the view's visible rectangle whereas the method
13833      * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
13834      *
13835      * @return true if at least part of the text content is visible; false if the text content is
13836      * completely clipped or translated out of the visible area.
13837      */
getViewVisibleRect(Rect rect)13838     private boolean getViewVisibleRect(Rect rect) {
13839         if (!getLocalVisibleRect(rect)) {
13840             return false;
13841         }
13842         // getLocalVisibleRect returns a rect relative to the unscrolled left top corner of the
13843         // view. In other words, the returned rectangle's origin point is (-scrollX, -scrollY) in
13844         // view's coordinates. So we need to offset it with the negative scrolled amount to convert
13845         // it to view's coordinate.
13846         rect.offset(-getScrollX(), -getScrollY());
13847         return true;
13848     }
13849 
13850     /**
13851      * Helper method to set {@code rect} to the text content's non-clipped area in the view's
13852      * coordinates.
13853      *
13854      * @return true if at least part of the text content is visible; false if the text content is
13855      * completely clipped or translated out of the visible area.
13856      */
getContentVisibleRect(Rect rect)13857     private boolean getContentVisibleRect(Rect rect) {
13858         if (!getViewVisibleRect(rect)) {
13859             return false;
13860         }
13861         // Clip the view's visible rect with the text layout's visible rect.
13862         return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
13863                 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
13864     }
13865 
13866     /**
13867      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
13868      *
13869      * @param builder The builder to populate
13870      * @param startIndex The starting character index to populate
13871      * @param endIndex The ending character index to populate
13872      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
13873      * content
13874      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
13875      * @hide
13876      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)13877     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
13878             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
13879             float viewportToContentVerticalOffset) {
13880         if (isOffsetMappingAvailable()) {
13881             // The text is transformed, and has different length, we don't support
13882             // character bounds in this case yet.
13883             return;
13884         }
13885         final Rect rect = new Rect();
13886         getContentVisibleRect(rect);
13887         final RectF visibleRect = new RectF(rect);
13888 
13889         final float[] characterBounds = getCharacterBounds(startIndex, endIndex,
13890                 viewportToContentHorizontalOffset, viewportToContentVerticalOffset);
13891         final int limit = endIndex - startIndex;
13892         for (int offset = 0; offset < limit; ++offset) {
13893             final float left = characterBounds[offset * 4];
13894             final float top = characterBounds[offset * 4 + 1];
13895             final float right = characterBounds[offset * 4 + 2];
13896             final float bottom = characterBounds[offset * 4 + 3];
13897 
13898             final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom);
13899             final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom);
13900             int characterBoundsFlags = 0;
13901             if (hasVisibleRegion) {
13902                 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
13903             }
13904             if (hasInVisibleRegion) {
13905                 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
13906             }
13907 
13908             if (mLayout.isRtlCharAt(offset)) {
13909                 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
13910             }
13911             builder.addCharacterBounds(offset + startIndex, left, top, right, bottom,
13912                     characterBoundsFlags);
13913         }
13914     }
13915 
13916     /**
13917      * Return the bounds of the characters in the given range, in TextView's coordinates.
13918      *
13919      * @param start the start index of the interested text range, inclusive.
13920      * @param end the end index of the interested text range, exclusive.
13921      * @param layoutLeft the left of the given {@code layout} in the editor view's coordinates.
13922      * @param layoutTop  the top of the given {@code layout} in the editor view's coordinates.
13923      * @return the character bounds stored in a flattened array, in the editor view's coordinates.
13924      */
getCharacterBounds(int start, int end, float layoutLeft, float layoutTop)13925     private float[] getCharacterBounds(int start, int end, float layoutLeft, float layoutTop) {
13926         final float[] characterBounds = new float[4 * (end - start)];
13927         mLayout.fillCharacterBounds(start, end, characterBounds, 0);
13928         for (int offset = 0; offset < end - start; ++offset) {
13929             characterBounds[4 * offset] += layoutLeft;
13930             characterBounds[4 * offset + 1] += layoutTop;
13931             characterBounds[4 * offset + 2] += layoutLeft;
13932             characterBounds[4 * offset + 3] += layoutTop;
13933         }
13934         return characterBounds;
13935     }
13936 
13937     /**
13938      * Compute {@link CursorAnchorInfo} from this {@link TextView}.
13939      *
13940      * @param filter the {@link CursorAnchorInfo} update filter which specified the needed
13941      *               information from IME.
13942      * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build
13943      *                                the result {@link CursorAnchorInfo}.
13944      * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen
13945      *                           matrix.
13946      * @return the result {@link CursorAnchorInfo} to be passed to IME.
13947      * @hide
13948      */
13949     @VisibleForTesting
13950     @Nullable
getCursorAnchorInfo(@nputConnection.CursorUpdateFilter int filter, @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, @NonNull Matrix viewToScreenMatrix)13951     public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter,
13952             @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder,
13953             @NonNull Matrix viewToScreenMatrix) {
13954         Layout layout = getLayout();
13955         if (layout == null) {
13956             return null;
13957         }
13958         boolean includeEditorBounds =
13959                 (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0;
13960         boolean includeCharacterBounds =
13961                 (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0;
13962         boolean includeInsertionMarker =
13963                 (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
13964         boolean includeVisibleLineBounds =
13965                 (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0;
13966         boolean includeTextAppearance =
13967                 (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0;
13968         boolean includeAll =
13969                 (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker
13970                         && !includeVisibleLineBounds && !includeTextAppearance);
13971 
13972         includeEditorBounds |= includeAll;
13973         includeCharacterBounds |= includeAll;
13974         includeInsertionMarker |= includeAll;
13975         includeVisibleLineBounds |= includeAll;
13976         includeTextAppearance |= includeAll;
13977 
13978         final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder;
13979         builder.reset();
13980 
13981         final int selectionStart = getSelectionStart();
13982         builder.setSelectionRange(selectionStart, getSelectionEnd());
13983 
13984         // Construct transformation matrix from view local coordinates to screen coordinates.
13985         viewToScreenMatrix.reset();
13986         transformMatrixToGlobal(viewToScreenMatrix);
13987         builder.setMatrix(viewToScreenMatrix);
13988 
13989         if (includeEditorBounds) {
13990             if (mTempRect == null) {
13991                 mTempRect = new Rect();
13992             }
13993             final Rect bounds = mTempRect;
13994             final RectF editorBounds;
13995             final RectF handwritingBounds;
13996             if (getViewVisibleRect(bounds)) {
13997                 editorBounds = new RectF(bounds);
13998                 handwritingBounds = new RectF(editorBounds);
13999                 handwritingBounds.top -= getHandwritingBoundsOffsetTop();
14000                 handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
14001                 handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
14002                 handwritingBounds.right += getHandwritingBoundsOffsetRight();
14003             } else {
14004                 // The editor is not visible at all, return empty rectangles. We still need to
14005                 // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
14006                 editorBounds = new RectF();
14007                 handwritingBounds = new RectF();
14008             }
14009             EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
14010             EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
14011                     .setHandwritingBounds(handwritingBounds).build();
14012             builder.setEditorBoundsInfo(editorBoundsInfo);
14013         }
14014 
14015         if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) {
14016             final float viewportToContentHorizontalOffset =
14017                     viewportToContentHorizontalOffset();
14018             final float viewportToContentVerticalOffset =
14019                     viewportToContentVerticalOffset();
14020             final boolean isTextTransformed = (getTransformationMethod() != null
14021                     && getTransformed() instanceof OffsetMapping);
14022             if (includeCharacterBounds && !isTextTransformed) {
14023                 final CharSequence text = getText();
14024                 if (text instanceof Spannable) {
14025                     final Spannable sp = (Spannable) text;
14026                     int composingTextStart = EditableInputConnection.getComposingSpanStart(sp);
14027                     int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp);
14028                     if (composingTextEnd < composingTextStart) {
14029                         final int temp = composingTextEnd;
14030                         composingTextEnd = composingTextStart;
14031                         composingTextStart = temp;
14032                     }
14033                     final boolean hasComposingText =
14034                             (0 <= composingTextStart) && (composingTextStart
14035                                     < composingTextEnd);
14036                     if (hasComposingText) {
14037                         final CharSequence composingText = text.subSequence(composingTextStart,
14038                                 composingTextEnd);
14039                         builder.setComposingText(composingTextStart, composingText);
14040                         populateCharacterBounds(builder, composingTextStart,
14041                                 composingTextEnd, viewportToContentHorizontalOffset,
14042                                 viewportToContentVerticalOffset);
14043                     }
14044                 }
14045             }
14046 
14047             if (includeInsertionMarker) {
14048                 // Treat selectionStart as the insertion point.
14049                 if (0 <= selectionStart) {
14050                     final int offsetTransformed = originalToTransformed(
14051                             selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR);
14052                     final int line = layout.getLineForOffset(offsetTransformed);
14053                     final float insertionMarkerX =
14054                             layout.getPrimaryHorizontal(
14055                                             offsetTransformed, layout.shouldClampCursor(line))
14056                                     + viewportToContentHorizontalOffset;
14057                     final float insertionMarkerTop = layout.getLineTop(line)
14058                             + viewportToContentVerticalOffset;
14059                     final float insertionMarkerBaseline = layout.getLineBaseline(line)
14060                             + viewportToContentVerticalOffset;
14061                     final float insertionMarkerBottom =
14062                             layout.getLineBottom(line, /* includeLineSpacing= */ false)
14063                                     + viewportToContentVerticalOffset;
14064                     final boolean isTopVisible =
14065                             isPositionVisible(insertionMarkerX, insertionMarkerTop);
14066                     final boolean isBottomVisible =
14067                             isPositionVisible(insertionMarkerX, insertionMarkerBottom);
14068                     int insertionMarkerFlags = 0;
14069                     if (isTopVisible || isBottomVisible) {
14070                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
14071                     }
14072                     if (!isTopVisible || !isBottomVisible) {
14073                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
14074                     }
14075                     if (layout.isRtlCharAt(offsetTransformed)) {
14076                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
14077                     }
14078                     builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
14079                             insertionMarkerBaseline, insertionMarkerBottom,
14080                             insertionMarkerFlags);
14081                 }
14082             }
14083 
14084             if (includeVisibleLineBounds) {
14085                 final Rect visibleRect = new Rect();
14086                 if (getContentVisibleRect(visibleRect)) {
14087                     // Subtract the viewportToContentVerticalOffset to convert the view
14088                     // coordinates to layout coordinates.
14089                     final float visibleTop =
14090                             visibleRect.top - viewportToContentVerticalOffset;
14091                     final float visibleBottom =
14092                             visibleRect.bottom - viewportToContentVerticalOffset;
14093                     final int firstLine =
14094                             layout.getLineForVertical((int) Math.floor(visibleTop));
14095                     final int lastLine =
14096                             layout.getLineForVertical((int) Math.ceil(visibleBottom));
14097 
14098                     for (int line = firstLine; line <= lastLine; ++line) {
14099                         final float left = layout.getLineLeft(line)
14100                                 + viewportToContentHorizontalOffset;
14101                         final float top = layout.getLineTop(line)
14102                                 + viewportToContentVerticalOffset;
14103                         final float right = layout.getLineRight(line)
14104                                 + viewportToContentHorizontalOffset;
14105                         final float bottom = layout.getLineBottom(line, false)
14106                                 + viewportToContentVerticalOffset;
14107                         builder.addVisibleLineBounds(left, top, right, bottom);
14108                     }
14109                 }
14110             }
14111         }
14112 
14113         if (includeTextAppearance) {
14114             builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this));
14115         }
14116         return builder.build();
14117     }
14118 
14119     /**
14120      * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}.
14121      * @hide
14122      */
getTextBoundsInfo(@onNull RectF bounds)14123     public TextBoundsInfo getTextBoundsInfo(@NonNull RectF bounds) {
14124         final Layout layout = getLayout();
14125         if (layout == null) {
14126             // No valid text layout, return null.
14127             return null;
14128         }
14129         final CharSequence text = layout.getText();
14130         if (text == null || isOffsetMappingAvailable()) {
14131             // The text is Null or the text has been transformed. Can't provide TextBoundsInfo.
14132             return null;
14133         }
14134 
14135         final Matrix localToGlobalMatrix = new Matrix();
14136         transformMatrixToGlobal(localToGlobalMatrix);
14137         final Matrix globalToLocalMatrix = new Matrix();
14138         if (!localToGlobalMatrix.invert(globalToLocalMatrix)) {
14139             // Can't map global rectF to local coordinates, this is almost impossible in practice.
14140             return null;
14141         }
14142 
14143         final float layoutLeft = viewportToContentHorizontalOffset();
14144         final float layoutTop = viewportToContentVerticalOffset();
14145 
14146         final RectF localBounds = new RectF(bounds);
14147         globalToLocalMatrix.mapRect(localBounds);
14148         localBounds.offset(-layoutLeft, -layoutTop);
14149 
14150         // Text length is 0. There is no character bounds, return empty TextBoundsInfo.
14151         // rectF doesn't intersect with the layout, return empty TextBoundsInfo.
14152         if (!localBounds.intersects(0f, 0f, layout.getWidth(), layout.getHeight())
14153                 || text.length() == 0) {
14154             final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 0);
14155             final SegmentFinder emptySegmentFinder =
14156                     new SegmentFinder.PrescribedSegmentFinder(new int[0]);
14157             builder.setMatrix(localToGlobalMatrix)
14158                     .setCharacterBounds(new float[0])
14159                     .setCharacterBidiLevel(new int[0])
14160                     .setCharacterFlags(new int[0])
14161                     .setGraphemeSegmentFinder(emptySegmentFinder)
14162                     .setLineSegmentFinder(emptySegmentFinder)
14163                     .setWordSegmentFinder(emptySegmentFinder);
14164             return  builder.build();
14165         }
14166 
14167         final int startLine = layout.getLineForVertical((int) Math.floor(localBounds.top));
14168         final int endLine = layout.getLineForVertical((int) Math.floor(localBounds.bottom));
14169         final int start = layout.getLineStart(startLine);
14170         final int end = layout.getLineEnd(endLine);
14171 
14172         // Compute character bounds.
14173         final float[] characterBounds = getCharacterBounds(start, end, layoutLeft, layoutTop);
14174 
14175         // Compute character flags and BiDi levels.
14176         final int[] characterFlags = new int[end - start];
14177         final int[] characterBidiLevels = new int[end - start];
14178         for (int line = startLine; line <= endLine; ++line) {
14179             final int lineStart = layout.getLineStart(line);
14180             final int lineEnd = layout.getLineEnd(line);
14181             final Layout.Directions directions = layout.getLineDirections(line);
14182             for (int i = 0; i < directions.getRunCount(); ++i) {
14183                 final int runStart = directions.getRunStart(i) + lineStart;
14184                 final int runEnd = Math.min(runStart + directions.getRunLength(i), lineEnd);
14185                 final int runLevel = directions.getRunLevel(i);
14186                 Arrays.fill(characterBidiLevels, runStart - start, runEnd - start, runLevel);
14187             }
14188 
14189             final boolean lineIsRtl =
14190                     layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT;
14191             for (int index = lineStart; index < lineEnd; ++index) {
14192                 int flags = 0;
14193                 if (TextUtils.isWhitespace(text.charAt(index))) {
14194                     flags |= TextBoundsInfo.FLAG_CHARACTER_WHITESPACE;
14195                 }
14196                 if (TextUtils.isPunctuation(Character.codePointAt(text, index))) {
14197                     flags |= TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION;
14198                 }
14199                 if (TextUtils.isNewline(Character.codePointAt(text, index))) {
14200                     flags |= TextBoundsInfo.FLAG_CHARACTER_LINEFEED;
14201                 }
14202                 if (lineIsRtl) {
14203                     flags |= TextBoundsInfo.FLAG_LINE_IS_RTL;
14204                 }
14205                 characterFlags[index - start] = flags;
14206             }
14207         }
14208 
14209         // Create grapheme SegmentFinder.
14210         final SegmentFinder graphemeSegmentFinder =
14211                 new GraphemeClusterSegmentFinder(text, layout.getPaint());
14212 
14213         // Create word SegmentFinder.
14214         final WordIterator wordIterator = getWordIterator();
14215         wordIterator.setCharSequence(text, 0, text.length());
14216         final SegmentFinder wordSegmentFinder = new WordSegmentFinder(text, wordIterator);
14217 
14218         // Create line SegmentFinder.
14219         final int lineCount = endLine - startLine + 1;
14220         final int[] lineRanges = new int[2 * lineCount];
14221         for (int line = startLine; line <= endLine; ++line) {
14222             final int offset = line - startLine;
14223             lineRanges[2 * offset] = layout.getLineStart(line);
14224             lineRanges[2 * offset + 1] = layout.getLineEnd(line);
14225         }
14226         final SegmentFinder lineSegmentFinder =
14227                 new SegmentFinder.PrescribedSegmentFinder(lineRanges);
14228 
14229         return new TextBoundsInfo.Builder(start, end)
14230                 .setMatrix(localToGlobalMatrix)
14231                 .setCharacterBounds(characterBounds)
14232                 .setCharacterBidiLevel(characterBidiLevels)
14233                 .setCharacterFlags(characterFlags)
14234                 .setGraphemeSegmentFinder(graphemeSegmentFinder)
14235                 .setLineSegmentFinder(lineSegmentFinder)
14236                 .setWordSegmentFinder(wordSegmentFinder)
14237                 .build();
14238     }
14239 
14240     /**
14241      * @hide
14242      */
isPositionVisible(final float positionX, final float positionY)14243     public boolean isPositionVisible(final float positionX, final float positionY) {
14244         synchronized (TEMP_POSITION) {
14245             final float[] position = TEMP_POSITION;
14246             position[0] = positionX;
14247             position[1] = positionY;
14248             View view = this;
14249 
14250             while (view != null) {
14251                 if (view != this) {
14252                     // Local scroll is already taken into account in positionX/Y
14253                     position[0] -= view.getScrollX();
14254                     position[1] -= view.getScrollY();
14255                 }
14256 
14257                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
14258                         || position[1] > view.getHeight()) {
14259                     return false;
14260                 }
14261 
14262                 if (!view.getMatrix().isIdentity()) {
14263                     view.getMatrix().mapPoints(position);
14264                 }
14265 
14266                 position[0] += view.getLeft();
14267                 position[1] += view.getTop();
14268 
14269                 final ViewParent parent = view.getParent();
14270                 if (parent instanceof View) {
14271                     view = (View) parent;
14272                 } else {
14273                     // We've reached the ViewRoot, stop iterating
14274                     view = null;
14275                 }
14276             }
14277         }
14278 
14279         // We've been able to walk up the view hierarchy and the position was never clipped
14280         return true;
14281     }
14282 
14283     /**
14284      * Performs an accessibility action after it has been offered to the
14285      * delegate.
14286      *
14287      * @hide
14288      */
14289     @Override
performAccessibilityActionInternal(int action, Bundle arguments)14290     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
14291         if (mEditor != null) {
14292             if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)
14293                     || mEditor.performSmartActionsAccessibilityAction(action)) {
14294                 return true;
14295             }
14296         }
14297         switch (action) {
14298             case AccessibilityNodeInfo.ACTION_CLICK: {
14299                 return performAccessibilityActionClick(arguments);
14300             }
14301             case AccessibilityNodeInfo.ACTION_COPY: {
14302                 if (isFocused() && canCopy()) {
14303                     if (onTextContextMenuItem(ID_COPY)) {
14304                         return true;
14305                     }
14306                 }
14307             } return false;
14308             case AccessibilityNodeInfo.ACTION_PASTE: {
14309                 if (isFocused() && canPaste()) {
14310                     if (onTextContextMenuItem(ID_PASTE)) {
14311                         return true;
14312                     }
14313                 }
14314             } return false;
14315             case AccessibilityNodeInfo.ACTION_CUT: {
14316                 if (isFocused() && canCut()) {
14317                     if (onTextContextMenuItem(ID_CUT)) {
14318                         return true;
14319                     }
14320                 }
14321             } return false;
14322             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
14323                 ensureIterableTextForAccessibilitySelectable();
14324                 CharSequence text = getIterableTextForAccessibility();
14325                 if (text == null) {
14326                     return false;
14327                 }
14328                 final int start = (arguments != null) ? arguments.getInt(
14329                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
14330                 final int end = (arguments != null) ? arguments.getInt(
14331                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
14332                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
14333                     // No arguments clears the selection.
14334                     if (start == end && end == -1) {
14335                         Selection.removeSelection((Spannable) text);
14336                         return true;
14337                     }
14338                     if (start >= 0 && start <= end && end <= text.length()) {
14339                         requestFocusOnNonEditableSelectableText();
14340                         Selection.setSelection((Spannable) text, start, end);
14341                         // Make sure selection mode is engaged.
14342                         if (mEditor != null) {
14343                             mEditor.startSelectionActionModeAsync(false);
14344                         }
14345                         return true;
14346                     }
14347                 }
14348             } return false;
14349             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
14350             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
14351                 ensureIterableTextForAccessibilitySelectable();
14352                 return super.performAccessibilityActionInternal(action, arguments);
14353             }
14354             case ACCESSIBILITY_ACTION_SHARE: {
14355                 if (isFocused() && canShare()) {
14356                     if (onTextContextMenuItem(ID_SHARE)) {
14357                         return true;
14358                     }
14359                 }
14360             } return false;
14361             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
14362                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
14363                     return false;
14364                 }
14365                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
14366                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
14367                 setText(text);
14368                 if (mText != null) {
14369                     int updatedTextLength = mText.length();
14370                     if (updatedTextLength > 0) {
14371                         Selection.setSelection(mSpannable, updatedTextLength);
14372                     }
14373                 }
14374             } return true;
14375             case R.id.accessibilityActionImeEnter: {
14376                 if (isFocused() && isTextEditable()) {
14377                     onEditorAction(getImeActionId());
14378                 }
14379             } return true;
14380             case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
14381                 if (isLongClickable()) {
14382                     boolean handled;
14383                     if (isEnabled() && (mBufferType == BufferType.EDITABLE)) {
14384                         mEditor.mIsBeingLongClickedByAccessibility = true;
14385                         try {
14386                             handled = performLongClick();
14387                         } finally {
14388                             mEditor.mIsBeingLongClickedByAccessibility = false;
14389                         }
14390                     } else {
14391                         handled = performLongClick();
14392                     }
14393                     return handled;
14394                 }
14395             }
14396             return false;
14397             default: {
14398                 // New ids have static blocks to assign values, so they can't be used in a case
14399                 // block.
14400                 if (action == R.id.accessibilityActionShowTextSuggestions) {
14401                     return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE);
14402                 }
14403                 return super.performAccessibilityActionInternal(action, arguments);
14404             }
14405         }
14406     }
14407 
performAccessibilityActionClick(Bundle arguments)14408     private boolean performAccessibilityActionClick(Bundle arguments) {
14409         boolean handled = false;
14410 
14411         if (!isEnabled()) {
14412             return false;
14413         }
14414 
14415         if (isClickable() || isLongClickable()) {
14416             // Simulate View.onTouchEvent for an ACTION_UP event
14417             if (isFocusable() && !isFocused()) {
14418                 requestFocus();
14419             }
14420 
14421             performClick();
14422             handled = true;
14423         }
14424 
14425         // Show the IME, except when selecting in read-only text.
14426         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
14427                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
14428             final InputMethodManager imm = getInputMethodManager();
14429             viewClicked(imm);
14430             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
14431                 handled |= imm.showSoftInput(this, 0);
14432             }
14433         }
14434 
14435         return handled;
14436     }
14437 
requestFocusOnNonEditableSelectableText()14438     private void requestFocusOnNonEditableSelectableText() {
14439         if (!isTextEditable() && isTextSelectable()) {
14440             if (!isEnabled()) {
14441                 return;
14442             }
14443 
14444             if (isFocusable() && !isFocused()) {
14445                 requestFocus();
14446             }
14447         }
14448     }
14449 
hasSpannableText()14450     private boolean hasSpannableText() {
14451         return mText != null && mText instanceof Spannable;
14452     }
14453 
14454     /** @hide */
14455     @Override
sendAccessibilityEventInternal(int eventType)14456     public void sendAccessibilityEventInternal(int eventType) {
14457         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
14458             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
14459         }
14460 
14461         super.sendAccessibilityEventInternal(eventType);
14462     }
14463 
14464     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)14465     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
14466         // Do not send scroll events since first they are not interesting for
14467         // accessibility and second such events a generated too frequently.
14468         // For details see the implementation of bringTextIntoView().
14469         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
14470             return;
14471         }
14472         super.sendAccessibilityEventUnchecked(event);
14473     }
14474 
14475     /**
14476      * Returns the text that should be exposed to accessibility services.
14477      * <p>
14478      * This approximates what is displayed visually.
14479      *
14480      * @return the text that should be exposed to accessibility services, may
14481      *         be {@code null} if no text is set
14482      */
14483     @Nullable
14484     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTextForAccessibility()14485     private CharSequence getTextForAccessibility() {
14486         // If the text is empty, we must be showing the hint text.
14487         if (TextUtils.isEmpty(mText)) {
14488             return mHint;
14489         }
14490 
14491         // Otherwise, return whatever text is being displayed.
14492         return TextUtils.trimToParcelableSize(mTransformed);
14493     }
14494 
isVisibleToAccessibility()14495     boolean isVisibleToAccessibility() {
14496         return AccessibilityManager.getInstance(mContext).isEnabled()
14497                 && (isFocused() || (isSelected() && isShown()));
14498     }
14499 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)14500     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
14501             int fromIndex, int removedCount, int addedCount) {
14502         AccessibilityEvent event =
14503                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
14504         event.setFromIndex(fromIndex);
14505         event.setRemovedCount(removedCount);
14506         event.setAddedCount(addedCount);
14507         event.setBeforeText(beforeText);
14508         sendAccessibilityEventUnchecked(event);
14509     }
14510 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int toIndex)14511     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
14512             int fromIndex, int toIndex) {
14513         AccessibilityEvent event =
14514                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
14515         event.setFromIndex(fromIndex);
14516         event.setToIndex(toIndex);
14517         event.setBeforeText(beforeText);
14518         sendAccessibilityEventUnchecked(event);
14519     }
14520 
getInputMethodManager()14521     private InputMethodManager getInputMethodManager() {
14522         return getContext().getSystemService(InputMethodManager.class);
14523     }
14524 
14525     /**
14526      * Returns whether this text view is a current input method target.  The
14527      * default implementation just checks with {@link InputMethodManager}.
14528      * @return True if the TextView is a current input method target; false otherwise.
14529      */
isInputMethodTarget()14530     public boolean isInputMethodTarget() {
14531         InputMethodManager imm = getInputMethodManager();
14532         return imm != null && imm.isActive(this);
14533     }
14534 
14535     static final int ID_SELECT_ALL = android.R.id.selectAll;
14536     static final int ID_UNDO = android.R.id.undo;
14537     static final int ID_REDO = android.R.id.redo;
14538     static final int ID_CUT = android.R.id.cut;
14539     static final int ID_COPY = android.R.id.copy;
14540     static final int ID_PASTE = android.R.id.paste;
14541     static final int ID_SHARE = android.R.id.shareText;
14542     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
14543     static final int ID_REPLACE = android.R.id.replaceText;
14544     static final int ID_ASSIST = android.R.id.textAssist;
14545     static final int ID_AUTOFILL = android.R.id.autofill;
14546 
14547     /**
14548      * Called when a context menu option for the text view is selected.  Currently
14549      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
14550      * {@link android.R.id#copy}, {@link android.R.id#paste},
14551      * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or
14552      * {@link android.R.id#shareText}.
14553      *
14554      * @return true if the context menu item action was performed.
14555      */
onTextContextMenuItem(int id)14556     public boolean onTextContextMenuItem(int id) {
14557         int min = 0;
14558         int max = mText.length();
14559 
14560         if (isFocused()) {
14561             final int selStart = getSelectionStart();
14562             final int selEnd = getSelectionEnd();
14563 
14564             min = Math.max(0, Math.min(selStart, selEnd));
14565             max = Math.max(0, Math.max(selStart, selEnd));
14566         }
14567 
14568         switch (id) {
14569             case ID_SELECT_ALL:
14570                 final boolean hadSelection = hasSelection();
14571                 selectAllText();
14572                 if (mEditor != null && hadSelection) {
14573                     mEditor.invalidateActionModeAsync();
14574                 }
14575                 return true;
14576 
14577             case ID_UNDO:
14578                 if (mEditor != null) {
14579                     mEditor.undo();
14580                 }
14581                 return true;  // Returns true even if nothing was undone.
14582 
14583             case ID_REDO:
14584                 if (mEditor != null) {
14585                     mEditor.redo();
14586                 }
14587                 return true;  // Returns true even if nothing was undone.
14588 
14589             case ID_PASTE:
14590                 paste(true /* withFormatting */);
14591                 return true;
14592 
14593             case ID_PASTE_AS_PLAIN_TEXT:
14594                 paste(false /* withFormatting */);
14595                 return true;
14596 
14597             case ID_CUT:
14598                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
14599                 if (setPrimaryClip(cutData)) {
14600                     deleteText_internal(min, max);
14601                 } else {
14602                     Toast.makeText(getContext(),
14603                             com.android.internal.R.string.failed_to_copy_to_clipboard,
14604                             Toast.LENGTH_SHORT).show();
14605                 }
14606                 return true;
14607 
14608             case ID_COPY:
14609                 // For link action mode in a non-selectable/non-focusable TextView,
14610                 // make sure that we set the appropriate min/max.
14611                 final int selStart = getSelectionStart();
14612                 final int selEnd = getSelectionEnd();
14613                 min = Math.max(0, Math.min(selStart, selEnd));
14614                 max = Math.max(0, Math.max(selStart, selEnd));
14615                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
14616                 if (setPrimaryClip(copyData)) {
14617                     stopTextActionMode();
14618                 } else {
14619                     Toast.makeText(getContext(),
14620                             com.android.internal.R.string.failed_to_copy_to_clipboard,
14621                             Toast.LENGTH_SHORT).show();
14622                 }
14623                 return true;
14624 
14625             case ID_REPLACE:
14626                 if (mEditor != null) {
14627                     mEditor.replace();
14628                 }
14629                 return true;
14630 
14631             case ID_SHARE:
14632                 shareSelectedText();
14633                 return true;
14634 
14635             case ID_AUTOFILL:
14636                 requestAutofill();
14637                 stopTextActionMode();
14638                 return true;
14639         }
14640         return false;
14641     }
14642 
14643     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTransformedText(int start, int end)14644     CharSequence getTransformedText(int start, int end) {
14645         return removeSuggestionSpans(mTransformed.subSequence(start, end));
14646     }
14647 
14648     @Override
performLongClick()14649     public boolean performLongClick() {
14650         if (DEBUG_CURSOR) {
14651             logCursor("performLongClick", null);
14652         }
14653 
14654         boolean handled = false;
14655         boolean performedHapticFeedback = false;
14656 
14657         if (mEditor != null) {
14658             mEditor.mIsBeingLongClicked = true;
14659         }
14660 
14661         if (super.performLongClick()) {
14662             handled = true;
14663             performedHapticFeedback = true;
14664         }
14665 
14666         if (mEditor != null) {
14667             handled |= mEditor.performLongClick(handled);
14668             mEditor.mIsBeingLongClicked = false;
14669         }
14670 
14671         if (handled) {
14672             if (!performedHapticFeedback) {
14673               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
14674             }
14675             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
14676         } else {
14677             MetricsLogger.action(
14678                     mContext,
14679                     MetricsEvent.TEXT_LONGPRESS,
14680                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
14681         }
14682 
14683         return handled;
14684     }
14685 
14686     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)14687     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
14688         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
14689         if (mEditor != null) {
14690             mEditor.onScrollChanged();
14691         }
14692     }
14693 
14694     /**
14695      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
14696      * by the IME or by the spell checker as the user types. This is done by adding
14697      * {@link SuggestionSpan}s to the text.
14698      *
14699      * When suggestions are enabled (default), this list of suggestions will be displayed when the
14700      * user asks for them on these parts of the text. This value depends on the inputType of this
14701      * TextView.
14702      *
14703      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
14704      *
14705      * In addition, the type variation must be one of
14706      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
14707      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
14708      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
14709      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
14710      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
14711      *
14712      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
14713      *
14714      * @return true if the suggestions popup window is enabled, based on the inputType.
14715      */
isSuggestionsEnabled()14716     public boolean isSuggestionsEnabled() {
14717         if (mEditor == null) return false;
14718         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
14719             return false;
14720         }
14721         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
14722 
14723         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
14724         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
14725                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
14726                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
14727                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
14728                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
14729     }
14730 
14731     /**
14732      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
14733      * selection is initiated in this View.
14734      *
14735      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
14736      * Paste, Replace and Share actions, depending on what this View supports.
14737      *
14738      * <p>A custom implementation can add new entries in the default menu in its
14739      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
14740      * method. The default actions can also be removed from the menu using
14741      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
14742      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
14743      * {@link android.R.id#pasteAsPlainText} (starting at API level 23),
14744      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
14745      *
14746      * <p>Returning false from
14747      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
14748      * will prevent the action mode from being started.
14749      *
14750      * <p>Action click events should be handled by the custom implementation of
14751      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
14752      * android.view.MenuItem)}.
14753      *
14754      * <p>Note that text selection mode is not started when a TextView receives focus and the
14755      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
14756      * that case, to allow for quick replacement.
14757      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)14758     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
14759         createEditorIfNeeded();
14760         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
14761     }
14762 
14763     /**
14764      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
14765      *
14766      * @return The current custom selection callback.
14767      */
getCustomSelectionActionModeCallback()14768     public ActionMode.Callback getCustomSelectionActionModeCallback() {
14769         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
14770     }
14771 
14772     /**
14773      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
14774      * insertion is initiated in this View.
14775      * The standard implementation populates the menu with a subset of Select All,
14776      * Paste and Replace actions, depending on what this View supports.
14777      *
14778      * <p>A custom implementation can add new entries in the default menu in its
14779      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
14780      * android.view.Menu)} method. The default actions can also be removed from the menu using
14781      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
14782      * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API
14783      * level 23) or {@link android.R.id#replaceText} ids as parameters.</p>
14784      *
14785      * <p>Returning false from
14786      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
14787      * android.view.Menu)} will prevent the action mode from being started.</p>
14788      *
14789      * <p>Action click events should be handled by the custom implementation of
14790      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
14791      * android.view.MenuItem)}.</p>
14792      *
14793      * <p>Note that text insertion mode is not started when a TextView receives focus and the
14794      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
14795      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)14796     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
14797         createEditorIfNeeded();
14798         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
14799     }
14800 
14801     /**
14802      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
14803      *
14804      * @return The current custom insertion callback.
14805      */
getCustomInsertionActionModeCallback()14806     public ActionMode.Callback getCustomInsertionActionModeCallback() {
14807         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
14808     }
14809 
14810     /**
14811      * Sets the {@link TextClassifier} for this TextView.
14812      */
setTextClassifier(@ullable TextClassifier textClassifier)14813     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
14814         mTextClassifier = textClassifier;
14815     }
14816 
14817     /**
14818      * Returns the {@link TextClassifier} used by this TextView.
14819      * If no TextClassifier has been set, this TextView uses the default set by the
14820      * {@link TextClassificationManager}.
14821      */
14822     @NonNull
getTextClassifier()14823     public TextClassifier getTextClassifier() {
14824         if (mTextClassifier == null) {
14825             final TextClassificationManager tcm = getTextClassificationManagerForUser();
14826             if (tcm != null) {
14827                 return tcm.getTextClassifier();
14828             }
14829             return TextClassifier.NO_OP;
14830         }
14831         return mTextClassifier;
14832     }
14833 
14834     /**
14835      * Returns a session-aware text classifier.
14836      * This method creates one if none already exists or the current one is destroyed.
14837      */
14838     @NonNull
getTextClassificationSession()14839     TextClassifier getTextClassificationSession() {
14840         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
14841             final TextClassificationManager tcm = getTextClassificationManagerForUser();
14842             if (tcm != null) {
14843                 final String widgetType;
14844                 if (isTextEditable()) {
14845                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
14846                 } else if (isTextSelectable()) {
14847                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
14848                 } else {
14849                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
14850                 }
14851                 mTextClassificationContext = new TextClassificationContext.Builder(
14852                         mContext.getPackageName(), widgetType)
14853                         .build();
14854                 if (mTextClassifier != null) {
14855                     mTextClassificationSession = tcm.createTextClassificationSession(
14856                             mTextClassificationContext, mTextClassifier);
14857                 } else {
14858                     mTextClassificationSession = tcm.createTextClassificationSession(
14859                             mTextClassificationContext);
14860                 }
14861             } else {
14862                 mTextClassificationSession = TextClassifier.NO_OP;
14863             }
14864         }
14865         return mTextClassificationSession;
14866     }
14867 
14868     /**
14869      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
14870      * @see #getTextClassificationSession()
14871      */
14872     @Nullable
getTextClassificationContext()14873     TextClassificationContext getTextClassificationContext() {
14874         return mTextClassificationContext;
14875     }
14876 
14877     /**
14878      * Returns true if this TextView uses a no-op TextClassifier.
14879      */
usesNoOpTextClassifier()14880     boolean usesNoOpTextClassifier() {
14881         return getTextClassifier() == TextClassifier.NO_OP;
14882     }
14883 
14884     /**
14885      * Starts an ActionMode for the specified TextLinkSpan.
14886      *
14887      * @return Whether or not we're attempting to start the action mode.
14888      * @hide
14889      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)14890     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
14891         Preconditions.checkNotNull(clickedSpan);
14892 
14893         if (!(mText instanceof Spanned)) {
14894             return false;
14895         }
14896 
14897         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
14898         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
14899 
14900         if (start < 0 || end > mText.length() || start >= end) {
14901             return false;
14902         }
14903 
14904         createEditorIfNeeded();
14905         mEditor.startLinkActionModeAsync(start, end);
14906         return true;
14907     }
14908 
14909     /**
14910      * Handles a click on the specified TextLinkSpan.
14911      *
14912      * @return Whether or not the click is being handled.
14913      * @hide
14914      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)14915     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
14916         Preconditions.checkNotNull(clickedSpan);
14917         if (mText instanceof Spanned) {
14918             final Spanned spanned = (Spanned) mText;
14919             final int start = spanned.getSpanStart(clickedSpan);
14920             final int end = spanned.getSpanEnd(clickedSpan);
14921             if (start >= 0 && end <= mText.length() && start < end) {
14922                 final TextClassification.Request request = new TextClassification.Request.Builder(
14923                         mText, start, end)
14924                         .setDefaultLocales(getTextLocales())
14925                         .build();
14926                 final Supplier<TextClassification> supplier = () ->
14927                         getTextClassificationSession().classifyText(request);
14928                 final Consumer<TextClassification> consumer = classification -> {
14929                     if (classification != null) {
14930                         if (!classification.getActions().isEmpty()) {
14931                             try {
14932                                 classification.getActions().get(0).getActionIntent().send();
14933                             } catch (PendingIntent.CanceledException e) {
14934                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
14935                             }
14936                         } else {
14937                             Log.d(LOG_TAG, "No link action to perform");
14938                         }
14939                     } else {
14940                         // classification == null
14941                         Log.d(LOG_TAG, "Timeout while classifying text");
14942                     }
14943                 };
14944                 CompletableFuture.supplyAsync(supplier)
14945                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
14946                         .thenAccept(consumer);
14947                 return true;
14948             }
14949         }
14950         return false;
14951     }
14952 
14953     /**
14954      * @hide
14955      */
14956     @UnsupportedAppUsage
stopTextActionMode()14957     protected void stopTextActionMode() {
14958         if (mEditor != null) {
14959             mEditor.stopTextActionMode();
14960         }
14961     }
14962 
14963     /** @hide */
hideFloatingToolbar(int durationMs)14964     public void hideFloatingToolbar(int durationMs) {
14965         if (mEditor != null) {
14966             mEditor.hideFloatingToolbar(durationMs);
14967         }
14968     }
14969 
canUndo()14970     boolean canUndo() {
14971         return mEditor != null && mEditor.canUndo();
14972     }
14973 
canRedo()14974     boolean canRedo() {
14975         return mEditor != null && mEditor.canRedo();
14976     }
14977 
canCut()14978     boolean canCut() {
14979         if (hasPasswordTransformationMethod()) {
14980             return false;
14981         }
14982 
14983         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
14984                 && mEditor.mKeyListener != null) {
14985             return true;
14986         }
14987 
14988         return false;
14989     }
14990 
canCopy()14991     boolean canCopy() {
14992         if (hasPasswordTransformationMethod()) {
14993             return false;
14994         }
14995 
14996         if (mText.length() > 0 && hasSelection() && mEditor != null) {
14997             return true;
14998         }
14999 
15000         return false;
15001     }
15002 
canReplace()15003     boolean canReplace() {
15004         if (hasPasswordTransformationMethod()) {
15005             return false;
15006         }
15007 
15008         return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null)
15009                 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions();
15010     }
15011 
canShare()15012     boolean canShare() {
15013         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()
15014                 || !getContext().getResources().getBoolean(
15015                 com.android.internal.R.bool.config_textShareSupported)) {
15016             return false;
15017         }
15018         return canCopy();
15019     }
15020 
isDeviceProvisioned()15021     boolean isDeviceProvisioned() {
15022         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
15023             mDeviceProvisionedState = Settings.Global.getInt(
15024                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
15025                     ? DEVICE_PROVISIONED_YES
15026                     : DEVICE_PROVISIONED_NO;
15027         }
15028         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
15029     }
15030 
15031     @UnsupportedAppUsage
canPaste()15032     boolean canPaste() {
15033         return (mText instanceof Editable
15034                 && mEditor != null && mEditor.mKeyListener != null
15035                 && getSelectionStart() >= 0
15036                 && getSelectionEnd() >= 0
15037                 && getClipboardManagerForUser().hasPrimaryClip());
15038     }
15039 
canPasteAsPlainText()15040     boolean canPasteAsPlainText() {
15041         if (!canPaste()) {
15042             return false;
15043         }
15044 
15045         final ClipDescription description =
15046                 getClipboardManagerForUser().getPrimaryClipDescription();
15047         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
15048         return (isPlainType && description.isStyledText())
15049                 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
15050     }
15051 
canProcessText()15052     boolean canProcessText() {
15053         if (getId() == View.NO_ID) {
15054             return false;
15055         }
15056         return canShare();
15057     }
15058 
canSelectAllText()15059     boolean canSelectAllText() {
15060         return canSelectText() && !hasPasswordTransformationMethod()
15061                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
15062     }
15063 
selectAllText()15064     boolean selectAllText() {
15065         if (mEditor != null) {
15066             // Hide the toolbar before changing the selection to avoid flickering.
15067             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
15068         }
15069         final int length = mText.length();
15070         Selection.setSelection(mSpannable, 0, length);
15071         return length > 0;
15072     }
15073 
paste(boolean withFormatting)15074     private void paste(boolean withFormatting) {
15075         ClipboardManager clipboard = getClipboardManagerForUser();
15076         ClipData clip = clipboard.getPrimaryClip();
15077         if (clip == null) {
15078             return;
15079         }
15080         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD)
15081                 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT)
15082                 .build();
15083         performReceiveContent(payload);
15084         sLastCutCopyOrTextChangedTime = 0;
15085     }
15086 
shareSelectedText()15087     private void shareSelectedText() {
15088         String selectedText = getSelectedText();
15089         if (selectedText != null && !selectedText.isEmpty()) {
15090             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
15091             sharingIntent.setType("text/plain");
15092             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
15093             selectedText = TextUtils.trimToParcelableSize(selectedText);
15094             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
15095             getContext().startActivity(Intent.createChooser(sharingIntent, null));
15096             Selection.setSelection(mSpannable, getSelectionEnd());
15097         }
15098     }
15099 
15100     @CheckResult
setPrimaryClip(ClipData clip)15101     private boolean setPrimaryClip(ClipData clip) {
15102         ClipboardManager clipboard = getClipboardManagerForUser();
15103         try {
15104             clipboard.setPrimaryClip(clip);
15105         } catch (Throwable t) {
15106             return false;
15107         }
15108         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
15109         return true;
15110     }
15111 
15112     /**
15113      * Get the character offset closest to the specified absolute position. A typical use case is to
15114      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
15115      *
15116      * @param x The horizontal absolute position of a point on screen
15117      * @param y The vertical absolute position of a point on screen
15118      * @return the character offset for the character whose position is closest to the specified
15119      *  position. Returns -1 if there is no layout.
15120      */
getOffsetForPosition(float x, float y)15121     public int getOffsetForPosition(float x, float y) {
15122         if (getLayout() == null) return -1;
15123         final int line = getLineAtCoordinate(y);
15124         final int offset = getOffsetAtCoordinate(line, x);
15125         return offset;
15126     }
15127 
convertToLocalHorizontalCoordinate(float x)15128     float convertToLocalHorizontalCoordinate(float x) {
15129         x -= getTotalPaddingLeft();
15130         // Clamp the position to inside of the view.
15131         x = Math.max(0.0f, x);
15132         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
15133         x += getScrollX();
15134         return x;
15135     }
15136 
15137     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getLineAtCoordinate(float y)15138     int getLineAtCoordinate(float y) {
15139         y -= getTotalPaddingTop();
15140         // Clamp the position to inside of the view.
15141         y = Math.max(0.0f, y);
15142         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
15143         y += getScrollY();
15144         return getLayout().getLineForVertical((int) y);
15145     }
15146 
getLineAtCoordinateUnclamped(float y)15147     int getLineAtCoordinateUnclamped(float y) {
15148         y -= getTotalPaddingTop();
15149         y += getScrollY();
15150         return getLayout().getLineForVertical((int) y);
15151     }
15152 
getOffsetAtCoordinate(int line, float x)15153     int getOffsetAtCoordinate(int line, float x) {
15154         x = convertToLocalHorizontalCoordinate(x);
15155         final int offset = getLayout().getOffsetForHorizontal(line, x);
15156         return transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
15157     }
15158 
15159     /**
15160      * Convenient method to convert an offset on the transformed text to the original text.
15161      * @hide
15162      */
transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy)15163     public int transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy) {
15164         if (getTransformationMethod() == null) {
15165             return offset;
15166         }
15167         if (mTransformed instanceof OffsetMapping) {
15168             final OffsetMapping transformedText = (OffsetMapping) mTransformed;
15169             return transformedText.transformedToOriginal(offset, strategy);
15170         }
15171         return offset;
15172     }
15173 
15174     /**
15175      * Convenient method to convert an offset on the original text to the transformed text.
15176      * @hide
15177      */
originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy)15178     public int originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy) {
15179         if (getTransformationMethod() == null) {
15180             return offset;
15181         }
15182         if (mTransformed instanceof OffsetMapping) {
15183             final OffsetMapping transformedText = (OffsetMapping) mTransformed;
15184             return transformedText.originalToTransformed(offset, strategy);
15185         }
15186         return offset;
15187     }
15188     /**
15189      * Handles drag events sent by the system following a call to
15190      * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
15191      * startDragAndDrop()}.
15192      *
15193      * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent}
15194      * implementation.
15195      *
15196      * <p>If this text view is editable, accepts all drag actions (returns true for an
15197      * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all
15198      * subsequent drag events). While the drag is in progress, updates the cursor position
15199      * to follow the touch location. Once a drop event is received, handles content insertion
15200      * via {@link #performReceiveContent}.
15201      *
15202      * @param event The {@link android.view.DragEvent} sent by the system.
15203      * The {@link android.view.DragEvent#getAction()} method returns an action type constant
15204      * defined in DragEvent, indicating the type of drag event represented by this object.
15205      * @return Returns true if this text view is editable and delegates to super otherwise.
15206      * See {@link View#onDragEvent}.
15207      */
15208     @Override
onDragEvent(DragEvent event)15209     public boolean onDragEvent(DragEvent event) {
15210         if (mEditor == null || !mEditor.hasInsertionController()) {
15211             // If this TextView is not editable, defer to the default View implementation. This
15212             // will check for the presence of an OnReceiveContentListener and accept/reject
15213             // drag events depending on whether the listener is/isn't set.
15214             return super.onDragEvent(event);
15215         }
15216         switch (event.getAction()) {
15217             case DragEvent.ACTION_DRAG_STARTED:
15218                 return true;
15219 
15220             case DragEvent.ACTION_DRAG_ENTERED:
15221                 TextView.this.requestFocus();
15222                 return true;
15223 
15224             case DragEvent.ACTION_DRAG_LOCATION:
15225                 if (mText instanceof Spannable) {
15226                     final int offset = getOffsetForPosition(event.getX(), event.getY());
15227                     Selection.setSelection(mSpannable, offset);
15228                 }
15229                 return true;
15230 
15231             case DragEvent.ACTION_DROP:
15232                 if (mEditor != null) mEditor.onDrop(event);
15233                 return true;
15234 
15235             case DragEvent.ACTION_DRAG_ENDED:
15236             case DragEvent.ACTION_DRAG_EXITED:
15237             default:
15238                 return true;
15239         }
15240     }
15241 
isInBatchEditMode()15242     boolean isInBatchEditMode() {
15243         if (mEditor == null) return false;
15244         final Editor.InputMethodState ims = mEditor.mInputMethodState;
15245         if (ims != null) {
15246             return ims.mBatchEditNesting > 0;
15247         }
15248         return mEditor.mInBatchEditControllers;
15249     }
15250 
15251     @Override
onRtlPropertiesChanged(int layoutDirection)15252     public void onRtlPropertiesChanged(int layoutDirection) {
15253         super.onRtlPropertiesChanged(layoutDirection);
15254 
15255         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
15256         if (mTextDir != newTextDir) {
15257             mTextDir = newTextDir;
15258             if (mLayout != null) {
15259                 checkForRelayout();
15260             }
15261         }
15262     }
15263 
15264     /**
15265      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
15266      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
15267      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
15268      * return value may not be the same as the one TextView uses if the View's layout direction is
15269      * not resolved or detached from parent root view.
15270      */
getTextDirectionHeuristic()15271     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
15272         if (hasPasswordTransformationMethod()) {
15273             // passwords fields should be LTR
15274             return TextDirectionHeuristics.LTR;
15275         }
15276 
15277         if (mEditor != null
15278                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
15279                     == EditorInfo.TYPE_CLASS_PHONE) {
15280             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
15281             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
15282             // RTL digits.
15283             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
15284             final String zero = symbols.getDigitStrings()[0];
15285             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
15286             // direction.
15287             final int firstCodepoint = zero.codePointAt(0);
15288             final byte digitDirection = Character.getDirectionality(firstCodepoint);
15289             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
15290                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
15291                 return TextDirectionHeuristics.RTL;
15292             } else {
15293                 return TextDirectionHeuristics.LTR;
15294             }
15295         }
15296 
15297         // Always need to resolve layout direction first
15298         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
15299 
15300         // Now, we can select the heuristic
15301         switch (getTextDirection()) {
15302             default:
15303             case TEXT_DIRECTION_FIRST_STRONG:
15304                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
15305                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
15306             case TEXT_DIRECTION_ANY_RTL:
15307                 return TextDirectionHeuristics.ANYRTL_LTR;
15308             case TEXT_DIRECTION_LTR:
15309                 return TextDirectionHeuristics.LTR;
15310             case TEXT_DIRECTION_RTL:
15311                 return TextDirectionHeuristics.RTL;
15312             case TEXT_DIRECTION_LOCALE:
15313                 return TextDirectionHeuristics.LOCALE;
15314             case TEXT_DIRECTION_FIRST_STRONG_LTR:
15315                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
15316             case TEXT_DIRECTION_FIRST_STRONG_RTL:
15317                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
15318         }
15319     }
15320 
15321     /**
15322      * @hide
15323      */
15324     @Override
onResolveDrawables(int layoutDirection)15325     public void onResolveDrawables(int layoutDirection) {
15326         // No need to resolve twice
15327         if (mLastLayoutDirection == layoutDirection) {
15328             return;
15329         }
15330         mLastLayoutDirection = layoutDirection;
15331 
15332         // Resolve drawables
15333         if (mDrawables != null) {
15334             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
15335                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
15336                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
15337                 applyCompoundDrawableTint();
15338             }
15339         }
15340     }
15341 
15342     /**
15343      * Prepares a drawable for display by propagating layout direction and
15344      * drawable state.
15345      *
15346      * @param dr the drawable to prepare
15347      */
prepareDrawableForDisplay(@ullable Drawable dr)15348     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
15349         if (dr == null) {
15350             return;
15351         }
15352 
15353         dr.setLayoutDirection(getLayoutDirection());
15354 
15355         if (dr.isStateful()) {
15356             dr.setState(getDrawableState());
15357             dr.jumpToCurrentState();
15358         }
15359     }
15360 
15361     /**
15362      * @hide
15363      */
resetResolvedDrawables()15364     protected void resetResolvedDrawables() {
15365         super.resetResolvedDrawables();
15366         mLastLayoutDirection = -1;
15367     }
15368 
15369     /**
15370      * @hide
15371      */
viewClicked(InputMethodManager imm)15372     protected void viewClicked(InputMethodManager imm) {
15373         if (imm != null) {
15374             imm.viewClicked(this);
15375         }
15376     }
15377 
15378     /**
15379      * Deletes the range of text [start, end[.
15380      * @hide
15381      */
15382     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
deleteText_internal(int start, int end)15383     protected void deleteText_internal(int start, int end) {
15384         ((Editable) mText).delete(start, end);
15385     }
15386 
15387     /**
15388      * Replaces the range of text [start, end[ by replacement text
15389      * @hide
15390      */
replaceText_internal(int start, int end, CharSequence text)15391     protected void replaceText_internal(int start, int end, CharSequence text) {
15392         ((Editable) mText).replace(start, end, text);
15393     }
15394 
15395     /**
15396      * Sets a span on the specified range of text
15397      * @hide
15398      */
setSpan_internal(Object span, int start, int end, int flags)15399     protected void setSpan_internal(Object span, int start, int end, int flags) {
15400         ((Editable) mText).setSpan(span, start, end, flags);
15401     }
15402 
15403     /**
15404      * Moves the cursor to the specified offset position in text
15405      * @hide
15406      */
setCursorPosition_internal(int start, int end)15407     protected void setCursorPosition_internal(int start, int end) {
15408         Selection.setSelection(((Editable) mText), start, end);
15409     }
15410 
15411     /**
15412      * An Editor should be created as soon as any of the editable-specific fields (grouped
15413      * inside the Editor object) is assigned to a non-default value.
15414      * This method will create the Editor if needed.
15415      *
15416      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
15417      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
15418      * Editor for backward compatibility, as soon as one of these fields is assigned.
15419      *
15420      * Also note that for performance reasons, the mEditor is created when needed, but not
15421      * reset when no more edit-specific fields are needed.
15422      */
15423     @UnsupportedAppUsage
createEditorIfNeeded()15424     private void createEditorIfNeeded() {
15425         if (mEditor == null) {
15426             mEditor = new Editor(this);
15427         }
15428     }
15429 
15430     /**
15431      * @hide
15432      */
15433     @Override
15434     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getIterableTextForAccessibility()15435     public CharSequence getIterableTextForAccessibility() {
15436         return mText;
15437     }
15438 
ensureIterableTextForAccessibilitySelectable()15439     private void ensureIterableTextForAccessibilitySelectable() {
15440         if (!(mText instanceof Spannable)) {
15441             setText(mText, BufferType.SPANNABLE);
15442         }
15443     }
15444 
15445     /**
15446      * @hide
15447      */
15448     @Override
getIteratorForGranularity(int granularity)15449     public TextSegmentIterator getIteratorForGranularity(int granularity) {
15450         switch (granularity) {
15451             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
15452                 Spannable text = (Spannable) getIterableTextForAccessibility();
15453                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
15454                     AccessibilityIterators.LineTextSegmentIterator iterator =
15455                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
15456                     iterator.initialize(text, getLayout());
15457                     return iterator;
15458                 }
15459             } break;
15460             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
15461                 Spannable text = (Spannable) getIterableTextForAccessibility();
15462                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
15463                     AccessibilityIterators.PageTextSegmentIterator iterator =
15464                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
15465                     iterator.initialize(this);
15466                     return iterator;
15467                 }
15468             } break;
15469         }
15470         return super.getIteratorForGranularity(granularity);
15471     }
15472 
15473     /**
15474      * @hide
15475      */
15476     @Override
getAccessibilitySelectionStart()15477     public int getAccessibilitySelectionStart() {
15478         return getSelectionStart();
15479     }
15480 
15481     /**
15482      * @hide
15483      */
isAccessibilitySelectionExtendable()15484     public boolean isAccessibilitySelectionExtendable() {
15485         return true;
15486     }
15487 
15488     /**
15489      * @hide
15490      */
prepareForExtendedAccessibilitySelection()15491     public void prepareForExtendedAccessibilitySelection() {
15492         requestFocusOnNonEditableSelectableText();
15493     }
15494 
15495     /**
15496      * @hide
15497      */
15498     @Override
getAccessibilitySelectionEnd()15499     public int getAccessibilitySelectionEnd() {
15500         return getSelectionEnd();
15501     }
15502 
15503     /**
15504      * @hide
15505      */
15506     @Override
setAccessibilitySelection(int start, int end)15507     public void setAccessibilitySelection(int start, int end) {
15508         if (getAccessibilitySelectionStart() == start
15509                 && getAccessibilitySelectionEnd() == end) {
15510             return;
15511         }
15512         CharSequence text = getIterableTextForAccessibility();
15513         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
15514             Selection.setSelection((Spannable) text, start, end);
15515         } else {
15516             Selection.removeSelection((Spannable) text);
15517         }
15518         // Hide all selection controllers used for adjusting selection
15519         // since we are doing so explicitlty by other means and these
15520         // controllers interact with how selection behaves.
15521         if (mEditor != null) {
15522             mEditor.hideCursorAndSpanControllers();
15523             mEditor.stopTextActionMode();
15524         }
15525     }
15526 
15527     /** @hide */
15528     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)15529     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
15530         super.encodeProperties(stream);
15531 
15532         TruncateAt ellipsize = getEllipsize();
15533         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
15534         stream.addProperty("text:textSize", getTextSize());
15535         stream.addProperty("text:scaledTextSize", getScaledTextSize());
15536         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
15537         stream.addProperty("text:selectionStart", getSelectionStart());
15538         stream.addProperty("text:selectionEnd", getSelectionEnd());
15539         stream.addProperty("text:curTextColor", mCurTextColor);
15540         stream.addUserProperty("text:text", mText == null ? null : mText.toString());
15541         stream.addProperty("text:gravity", mGravity);
15542     }
15543 
15544     /**
15545      * User interface state that is stored by TextView for implementing
15546      * {@link View#onSaveInstanceState}.
15547      */
15548     public static class SavedState extends BaseSavedState {
15549         int selStart = -1;
15550         int selEnd = -1;
15551         @UnsupportedAppUsage
15552         CharSequence text;
15553         boolean frozenWithFocus;
15554         CharSequence error;
15555         ParcelableParcel editorState;  // Optional state from Editor.
15556 
SavedState(Parcelable superState)15557         SavedState(Parcelable superState) {
15558             super(superState);
15559         }
15560 
15561         @Override
writeToParcel(Parcel out, int flags)15562         public void writeToParcel(Parcel out, int flags) {
15563             super.writeToParcel(out, flags);
15564             out.writeInt(selStart);
15565             out.writeInt(selEnd);
15566             out.writeInt(frozenWithFocus ? 1 : 0);
15567             TextUtils.writeToParcel(text, out, flags);
15568 
15569             if (error == null) {
15570                 out.writeInt(0);
15571             } else {
15572                 out.writeInt(1);
15573                 TextUtils.writeToParcel(error, out, flags);
15574             }
15575 
15576             if (editorState == null) {
15577                 out.writeInt(0);
15578             } else {
15579                 out.writeInt(1);
15580                 editorState.writeToParcel(out, flags);
15581             }
15582         }
15583 
15584         @Override
toString()15585         public String toString() {
15586             String str = "TextView.SavedState{"
15587                     + Integer.toHexString(System.identityHashCode(this))
15588                     + " start=" + selStart + " end=" + selEnd;
15589             if (text != null) {
15590                 str += " text=" + text;
15591             }
15592             return str + "}";
15593         }
15594 
15595         @SuppressWarnings("hiding")
15596         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
15597                 new Parcelable.Creator<SavedState>() {
15598                     public SavedState createFromParcel(Parcel in) {
15599                         return new SavedState(in);
15600                     }
15601 
15602                     public SavedState[] newArray(int size) {
15603                         return new SavedState[size];
15604                     }
15605                 };
15606 
SavedState(Parcel in)15607         private SavedState(Parcel in) {
15608             super(in);
15609             selStart = in.readInt();
15610             selEnd = in.readInt();
15611             frozenWithFocus = (in.readInt() != 0);
15612             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
15613 
15614             if (in.readInt() != 0) {
15615                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
15616             }
15617 
15618             if (in.readInt() != 0) {
15619                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
15620             }
15621         }
15622     }
15623 
15624     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
15625         @NonNull
15626         private char[] mChars;
15627         private int mStart, mLength;
15628 
CharWrapper(@onNull char[] chars, int start, int len)15629         CharWrapper(@NonNull char[] chars, int start, int len) {
15630             mChars = chars;
15631             mStart = start;
15632             mLength = len;
15633         }
15634 
set(@onNull char[] chars, int start, int len)15635         /* package */ void set(@NonNull char[] chars, int start, int len) {
15636             mChars = chars;
15637             mStart = start;
15638             mLength = len;
15639         }
15640 
length()15641         public int length() {
15642             return mLength;
15643         }
15644 
charAt(int off)15645         public char charAt(int off) {
15646             return mChars[off + mStart];
15647         }
15648 
15649         @Override
toString()15650         public String toString() {
15651             return new String(mChars, mStart, mLength);
15652         }
15653 
subSequence(int start, int end)15654         public CharSequence subSequence(int start, int end) {
15655             if (start < 0 || end < 0 || start > mLength || end > mLength) {
15656                 throw new IndexOutOfBoundsException(start + ", " + end);
15657             }
15658 
15659             return new String(mChars, start + mStart, end - start);
15660         }
15661 
getChars(int start, int end, char[] buf, int off)15662         public void getChars(int start, int end, char[] buf, int off) {
15663             if (start < 0 || end < 0 || start > mLength || end > mLength) {
15664                 throw new IndexOutOfBoundsException(start + ", " + end);
15665             }
15666 
15667             System.arraycopy(mChars, start + mStart, buf, off, end - start);
15668         }
15669 
15670         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)15671         public void drawText(BaseCanvas c, int start, int end,
15672                              float x, float y, Paint p) {
15673             c.drawText(mChars, start + mStart, end - start, x, y, p);
15674         }
15675 
15676         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)15677         public void drawTextRun(BaseCanvas c, int start, int end,
15678                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
15679             int count = end - start;
15680             int contextCount = contextEnd - contextStart;
15681             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
15682                     contextCount, x, y, isRtl, p);
15683         }
15684 
measureText(int start, int end, Paint p)15685         public float measureText(int start, int end, Paint p) {
15686             return p.measureText(mChars, start + mStart, end - start);
15687         }
15688 
getTextWidths(int start, int end, float[] widths, Paint p)15689         public int getTextWidths(int start, int end, float[] widths, Paint p) {
15690             return p.getTextWidths(mChars, start + mStart, end - start, widths);
15691         }
15692 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)15693         public float getTextRunAdvances(int start, int end, int contextStart,
15694                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
15695                 Paint p) {
15696             int count = end - start;
15697             int contextCount = contextEnd - contextStart;
15698             return p.getTextRunAdvances(mChars, start + mStart, count,
15699                     contextStart + mStart, contextCount, isRtl, advances,
15700                     advancesIndex);
15701         }
15702 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)15703         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
15704                 int offset, int cursorOpt, Paint p) {
15705             int contextCount = contextEnd - contextStart;
15706             return p.getTextRunCursor(mChars, contextStart + mStart,
15707                     contextCount, isRtl, offset + mStart, cursorOpt);
15708         }
15709     }
15710 
15711     private static final class Marquee {
15712         // TODO: Add an option to configure this
15713         private static final float MARQUEE_DELTA_MAX = 0.07f;
15714         private static final int MARQUEE_DELAY = 1200;
15715         private static final int MARQUEE_DP_PER_SECOND = 30;
15716 
15717         private static final byte MARQUEE_STOPPED = 0x0;
15718         private static final byte MARQUEE_STARTING = 0x1;
15719         private static final byte MARQUEE_RUNNING = 0x2;
15720 
15721         private final WeakReference<TextView> mView;
15722         private final Choreographer mChoreographer;
15723 
15724         private byte mStatus = MARQUEE_STOPPED;
15725         private final float mPixelsPerMs;
15726         private float mMaxScroll;
15727         private float mMaxFadeScroll;
15728         private float mGhostStart;
15729         private float mGhostOffset;
15730         private float mFadeStop;
15731         private int mRepeatLimit;
15732 
15733         private float mScroll;
15734         private long mLastAnimationMs;
15735 
Marquee(TextView v)15736         Marquee(TextView v) {
15737             final float density = v.getContext().getResources().getDisplayMetrics().density;
15738             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
15739             mView = new WeakReference<TextView>(v);
15740             mChoreographer = Choreographer.getInstance();
15741         }
15742 
15743         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
15744             @Override
15745             public void doFrame(long frameTimeNanos) {
15746                 tick();
15747             }
15748         };
15749 
15750         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
15751             @Override
15752             public void doFrame(long frameTimeNanos) {
15753                 mStatus = MARQUEE_RUNNING;
15754                 mLastAnimationMs = mChoreographer.getFrameTime();
15755                 tick();
15756             }
15757         };
15758 
15759         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
15760             @Override
15761             public void doFrame(long frameTimeNanos) {
15762                 if (mStatus == MARQUEE_RUNNING) {
15763                     if (mRepeatLimit >= 0) {
15764                         mRepeatLimit--;
15765                     }
15766                     start(mRepeatLimit);
15767                 }
15768             }
15769         };
15770 
tick()15771         void tick() {
15772             if (mStatus != MARQUEE_RUNNING) {
15773                 return;
15774             }
15775 
15776             mChoreographer.removeFrameCallback(mTickCallback);
15777 
15778             final TextView textView = mView.get();
15779             if (textView != null && textView.isAggregatedVisible()
15780                     && (textView.isFocused() || textView.isSelected())) {
15781                 long currentMs = mChoreographer.getFrameTime();
15782                 long deltaMs = currentMs - mLastAnimationMs;
15783                 mLastAnimationMs = currentMs;
15784                 float deltaPx = deltaMs * mPixelsPerMs;
15785                 mScroll += deltaPx;
15786                 if (mScroll > mMaxScroll) {
15787                     mScroll = mMaxScroll;
15788                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
15789                 } else {
15790                     mChoreographer.postFrameCallback(mTickCallback);
15791                 }
15792                 textView.invalidate();
15793             }
15794         }
15795 
stop()15796         void stop() {
15797             mStatus = MARQUEE_STOPPED;
15798             mChoreographer.removeFrameCallback(mStartCallback);
15799             mChoreographer.removeFrameCallback(mRestartCallback);
15800             mChoreographer.removeFrameCallback(mTickCallback);
15801             resetScroll();
15802         }
15803 
resetScroll()15804         private void resetScroll() {
15805             mScroll = 0.0f;
15806             final TextView textView = mView.get();
15807             if (textView != null) textView.invalidate();
15808         }
15809 
start(int repeatLimit)15810         void start(int repeatLimit) {
15811             if (repeatLimit == 0) {
15812                 stop();
15813                 return;
15814             }
15815             mRepeatLimit = repeatLimit;
15816             final TextView textView = mView.get();
15817             if (textView != null && textView.mLayout != null) {
15818                 mStatus = MARQUEE_STARTING;
15819                 mScroll = 0.0f;
15820                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
15821                         - textView.getCompoundPaddingRight();
15822                 final float lineWidth = textView.mLayout.getLineWidth(0);
15823                 final float gap = textWidth / 3.0f;
15824                 mGhostStart = lineWidth - textWidth + gap;
15825                 mMaxScroll = mGhostStart + textWidth;
15826                 mGhostOffset = lineWidth + gap;
15827                 mFadeStop = lineWidth + textWidth / 6.0f;
15828                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
15829 
15830                 textView.invalidate();
15831                 mChoreographer.postFrameCallback(mStartCallback);
15832             }
15833         }
15834 
getGhostOffset()15835         float getGhostOffset() {
15836             return mGhostOffset;
15837         }
15838 
getScroll()15839         float getScroll() {
15840             return mScroll;
15841         }
15842 
getMaxFadeScroll()15843         float getMaxFadeScroll() {
15844             return mMaxFadeScroll;
15845         }
15846 
shouldDrawLeftFade()15847         boolean shouldDrawLeftFade() {
15848             return mScroll <= mFadeStop;
15849         }
15850 
shouldDrawGhost()15851         boolean shouldDrawGhost() {
15852             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
15853         }
15854 
isRunning()15855         boolean isRunning() {
15856             return mStatus == MARQUEE_RUNNING;
15857         }
15858 
isStopped()15859         boolean isStopped() {
15860             return mStatus == MARQUEE_STOPPED;
15861         }
15862     }
15863 
15864     private class ChangeWatcher implements TextWatcher, SpanWatcher {
15865 
15866         private CharSequence mBeforeText;
15867 
beforeTextChanged(CharSequence buffer, int start, int before, int after)15868         public void beforeTextChanged(CharSequence buffer, int start,
15869                                       int before, int after) {
15870             if (DEBUG_EXTRACT) {
15871                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
15872                         + " before=" + before + " after=" + after + ": " + buffer);
15873             }
15874 
15875             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
15876                 mBeforeText = mTransformed.toString();
15877             }
15878 
15879             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
15880         }
15881 
onTextChanged(CharSequence buffer, int start, int before, int after)15882         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
15883             if (DEBUG_EXTRACT) {
15884                 Log.v(LOG_TAG, "onTextChanged start=" + start
15885                         + " before=" + before + " after=" + after + ": " + buffer);
15886             }
15887             TextView.this.handleTextChanged(buffer, start, before, after);
15888 
15889             if (isVisibleToAccessibility()) {
15890                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
15891                 mBeforeText = null;
15892             }
15893         }
15894 
afterTextChanged(Editable buffer)15895         public void afterTextChanged(Editable buffer) {
15896             if (DEBUG_EXTRACT) {
15897                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
15898             }
15899             TextView.this.sendAfterTextChanged(buffer);
15900 
15901             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
15902                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
15903             }
15904         }
15905 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)15906         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
15907             if (DEBUG_EXTRACT) {
15908                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
15909                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
15910             }
15911             TextView.this.spanChange(buf, what, s, st, e, en);
15912         }
15913 
onSpanAdded(Spannable buf, Object what, int s, int e)15914         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
15915             if (DEBUG_EXTRACT) {
15916                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
15917             }
15918             TextView.this.spanChange(buf, what, -1, s, -1, e);
15919         }
15920 
onSpanRemoved(Spannable buf, Object what, int s, int e)15921         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
15922             if (DEBUG_EXTRACT) {
15923                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
15924             }
15925             TextView.this.spanChange(buf, what, s, -1, e, -1);
15926         }
15927     }
15928 
15929     /** @hide */
15930     @Override
onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)15931     public void onInputConnectionOpenedInternal(@NonNull InputConnection ic,
15932             @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
15933         if (mEditor != null) {
15934             mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic,
15935                     editorInfo);
15936         }
15937     }
15938 
15939     /** @hide */
15940     @Override
onInputConnectionClosedInternal()15941     public void onInputConnectionClosedInternal() {
15942         if (mEditor != null) {
15943             mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo();
15944         }
15945     }
15946 
15947     /**
15948      * Default {@link TextView} implementation for receiving content. Apps wishing to provide
15949      * custom behavior should configure a listener via {@link #setOnReceiveContentListener}.
15950      *
15951      * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in
15952      * content without acting on it).
15953      *
15954      * <p>For editable TextViews the default behavior is to insert text into the view, coercing
15955      * non-text content to text as needed. The MIME types "text/plain" and "text/html" have
15956      * well-defined behavior for this, while other MIME types have reasonable fallback behavior
15957      * (see {@link ClipData.Item#coerceToStyledText}).
15958      *
15959      * @param payload The content to insert and related metadata.
15960      *
15961      * @return The portion of the passed-in content that was not handled (may be all, some, or none
15962      * of the passed-in content).
15963      */
15964     @Nullable
15965     @Override
onReceiveContent(@onNull ContentInfo payload)15966     public ContentInfo onReceiveContent(@NonNull ContentInfo payload) {
15967         if (mEditor != null) {
15968             return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload);
15969         }
15970         return payload;
15971     }
15972 
logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)15973     private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
15974         if (msgFormat == null) {
15975             Log.d(LOG_TAG, location);
15976         } else {
15977             Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs));
15978         }
15979     }
15980 
15981     /**
15982      * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
15983      * the view.
15984      *
15985      * <p>NOTE: When overriding the method, it should not collect a request to translate this
15986      * TextView if it is displaying a password.
15987      *
15988      * @param supportedFormats the supported translation format. The value could be {@link
15989      *                         android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
15990      * @param requestsCollector {@link Consumer} to receiver the {@link ViewTranslationRequest}
15991      *                                         which contains the information to be translated.
15992      */
15993     @Override
onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)15994     public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats,
15995             @NonNull Consumer<ViewTranslationRequest> requestsCollector) {
15996         if (supportedFormats == null || supportedFormats.length == 0) {
15997             if (UiTranslationController.DEBUG) {
15998                 Log.w(LOG_TAG, "Do not provide the support translation formats.");
15999             }
16000             return;
16001         }
16002         ViewTranslationRequest.Builder requestBuilder =
16003                 new ViewTranslationRequest.Builder(getAutofillId());
16004         // Support Text translation
16005         if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) {
16006             if (mText == null || mText.length() == 0) {
16007                 if (UiTranslationController.DEBUG) {
16008                     Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
16009                 }
16010                 return;
16011             }
16012             boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
16013             if (isTextEditable() || isPassword) {
16014                 Log.w(LOG_TAG, "Cannot create translation request. editable = "
16015                         + isTextEditable() + ", isPassword = " + isPassword);
16016                 return;
16017             }
16018             // TODO(b/176488462): apply the view's important for translation
16019             requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
16020                     TranslationRequestValue.forText(mText));
16021             if (!TextUtils.isEmpty(getContentDescription())) {
16022                 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION,
16023                         TranslationRequestValue.forText(getContentDescription()));
16024             }
16025         }
16026         requestsCollector.accept(requestBuilder.build());
16027     }
16028 }
16029