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 import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY;
31 
32 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
33 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
34 
35 import android.R;
36 import android.annotation.CallSuper;
37 import android.annotation.CheckResult;
38 import android.annotation.ColorInt;
39 import android.annotation.DrawableRes;
40 import android.annotation.FlaggedApi;
41 import android.annotation.FloatRange;
42 import android.annotation.IntDef;
43 import android.annotation.IntRange;
44 import android.annotation.NonNull;
45 import android.annotation.Nullable;
46 import android.annotation.Px;
47 import android.annotation.RequiresPermission;
48 import android.annotation.Size;
49 import android.annotation.StringRes;
50 import android.annotation.StyleRes;
51 import android.annotation.TestApi;
52 import android.annotation.XmlRes;
53 import android.app.Activity;
54 import android.app.PendingIntent;
55 import android.app.assist.AssistStructure;
56 import android.app.compat.CompatChanges;
57 import android.compat.annotation.ChangeId;
58 import android.compat.annotation.EnabledSince;
59 import android.compat.annotation.UnsupportedAppUsage;
60 import android.content.ClipData;
61 import android.content.ClipDescription;
62 import android.content.ClipboardManager;
63 import android.content.Context;
64 import android.content.Intent;
65 import android.content.UndoManager;
66 import android.content.pm.PackageManager;
67 import android.content.res.ColorStateList;
68 import android.content.res.CompatibilityInfo;
69 import android.content.res.Configuration;
70 import android.content.res.FontScaleConverterFactory;
71 import android.content.res.Resources;
72 import android.content.res.TypedArray;
73 import android.content.res.XmlResourceParser;
74 import android.graphics.BaseCanvas;
75 import android.graphics.BlendMode;
76 import android.graphics.Canvas;
77 import android.graphics.Color;
78 import android.graphics.Insets;
79 import android.graphics.Matrix;
80 import android.graphics.Paint;
81 import android.graphics.Paint.FontMetricsInt;
82 import android.graphics.Path;
83 import android.graphics.PointF;
84 import android.graphics.PorterDuff;
85 import android.graphics.Rect;
86 import android.graphics.RectF;
87 import android.graphics.Typeface;
88 import android.graphics.drawable.Drawable;
89 import android.graphics.fonts.FontStyle;
90 import android.graphics.fonts.FontVariationAxis;
91 import android.graphics.text.LineBreakConfig;
92 import android.icu.text.DecimalFormatSymbols;
93 import android.os.AsyncTask;
94 import android.os.Build;
95 import android.os.Build.VERSION_CODES;
96 import android.os.Bundle;
97 import android.os.CancellationSignal;
98 import android.os.Handler;
99 import android.os.LocaleList;
100 import android.os.Parcel;
101 import android.os.Parcelable;
102 import android.os.ParcelableParcel;
103 import android.os.Process;
104 import android.os.SystemClock;
105 import android.os.UserHandle;
106 import android.provider.Settings;
107 import android.text.BoringLayout;
108 import android.text.ClientFlags;
109 import android.text.DynamicLayout;
110 import android.text.Editable;
111 import android.text.GetChars;
112 import android.text.GraphemeClusterSegmentFinder;
113 import android.text.GraphicsOperations;
114 import android.text.Highlights;
115 import android.text.InputFilter;
116 import android.text.InputType;
117 import android.text.Layout;
118 import android.text.NoCopySpan;
119 import android.text.ParcelableSpan;
120 import android.text.PrecomputedText;
121 import android.text.SegmentFinder;
122 import android.text.Selection;
123 import android.text.SpanWatcher;
124 import android.text.Spannable;
125 import android.text.SpannableStringBuilder;
126 import android.text.Spanned;
127 import android.text.SpannedString;
128 import android.text.StaticLayout;
129 import android.text.TextDirectionHeuristic;
130 import android.text.TextDirectionHeuristics;
131 import android.text.TextPaint;
132 import android.text.TextUtils;
133 import android.text.TextUtils.TruncateAt;
134 import android.text.TextWatcher;
135 import android.text.WordSegmentFinder;
136 import android.text.method.AllCapsTransformationMethod;
137 import android.text.method.ArrowKeyMovementMethod;
138 import android.text.method.DateKeyListener;
139 import android.text.method.DateTimeKeyListener;
140 import android.text.method.DialerKeyListener;
141 import android.text.method.DigitsKeyListener;
142 import android.text.method.KeyListener;
143 import android.text.method.LinkMovementMethod;
144 import android.text.method.MetaKeyKeyListener;
145 import android.text.method.MovementMethod;
146 import android.text.method.OffsetMapping;
147 import android.text.method.PasswordTransformationMethod;
148 import android.text.method.SingleLineTransformationMethod;
149 import android.text.method.TextKeyListener;
150 import android.text.method.TimeKeyListener;
151 import android.text.method.TransformationMethod;
152 import android.text.method.TransformationMethod2;
153 import android.text.method.WordIterator;
154 import android.text.style.CharacterStyle;
155 import android.text.style.ClickableSpan;
156 import android.text.style.ParagraphStyle;
157 import android.text.style.SpellCheckSpan;
158 import android.text.style.SuggestionSpan;
159 import android.text.style.URLSpan;
160 import android.text.style.UpdateAppearance;
161 import android.text.util.Linkify;
162 import android.util.ArraySet;
163 import android.util.AttributeSet;
164 import android.util.DisplayMetrics;
165 import android.util.IntArray;
166 import android.util.Log;
167 import android.util.SparseIntArray;
168 import android.util.TypedValue;
169 import android.view.AccessibilityIterators.TextSegmentIterator;
170 import android.view.ActionMode;
171 import android.view.Choreographer;
172 import android.view.ContentInfo;
173 import android.view.ContextMenu;
174 import android.view.DragEvent;
175 import android.view.Gravity;
176 import android.view.HapticFeedbackConstants;
177 import android.view.InputDevice;
178 import android.view.KeyCharacterMap;
179 import android.view.KeyEvent;
180 import android.view.MotionEvent;
181 import android.view.PointerIcon;
182 import android.view.View;
183 import android.view.ViewConfiguration;
184 import android.view.ViewDebug;
185 import android.view.ViewGroup;
186 import android.view.ViewGroup.LayoutParams;
187 import android.view.ViewHierarchyEncoder;
188 import android.view.ViewParent;
189 import android.view.ViewRootImpl;
190 import android.view.ViewStructure;
191 import android.view.ViewTreeObserver;
192 import android.view.accessibility.AccessibilityEvent;
193 import android.view.accessibility.AccessibilityManager;
194 import android.view.accessibility.AccessibilityNodeInfo;
195 import android.view.animation.AnimationUtils;
196 import android.view.autofill.AutofillManager;
197 import android.view.autofill.AutofillValue;
198 import android.view.contentcapture.ContentCaptureManager;
199 import android.view.contentcapture.ContentCaptureSession;
200 import android.view.inputmethod.BaseInputConnection;
201 import android.view.inputmethod.CompletionInfo;
202 import android.view.inputmethod.CorrectionInfo;
203 import android.view.inputmethod.CursorAnchorInfo;
204 import android.view.inputmethod.DeleteGesture;
205 import android.view.inputmethod.DeleteRangeGesture;
206 import android.view.inputmethod.EditorBoundsInfo;
207 import android.view.inputmethod.EditorInfo;
208 import android.view.inputmethod.ExtractedText;
209 import android.view.inputmethod.ExtractedTextRequest;
210 import android.view.inputmethod.HandwritingGesture;
211 import android.view.inputmethod.InputConnection;
212 import android.view.inputmethod.InputMethodManager;
213 import android.view.inputmethod.InsertGesture;
214 import android.view.inputmethod.InsertModeGesture;
215 import android.view.inputmethod.JoinOrSplitGesture;
216 import android.view.inputmethod.PreviewableHandwritingGesture;
217 import android.view.inputmethod.RemoveSpaceGesture;
218 import android.view.inputmethod.SelectGesture;
219 import android.view.inputmethod.SelectRangeGesture;
220 import android.view.inputmethod.TextAppearanceInfo;
221 import android.view.inputmethod.TextBoundsInfo;
222 import android.view.inspector.InspectableProperty;
223 import android.view.inspector.InspectableProperty.EnumEntry;
224 import android.view.inspector.InspectableProperty.FlagEntry;
225 import android.view.textclassifier.TextClassification;
226 import android.view.textclassifier.TextClassificationContext;
227 import android.view.textclassifier.TextClassificationManager;
228 import android.view.textclassifier.TextClassifier;
229 import android.view.textclassifier.TextLinks;
230 import android.view.textservice.SpellCheckerSubtype;
231 import android.view.textservice.TextServicesManager;
232 import android.view.translation.TranslationRequestValue;
233 import android.view.translation.TranslationSpec;
234 import android.view.translation.UiTranslationController;
235 import android.view.translation.ViewTranslationCallback;
236 import android.view.translation.ViewTranslationRequest;
237 import android.widget.RemoteViews.RemoteView;
238 
239 import com.android.internal.accessibility.util.AccessibilityUtils;
240 import com.android.internal.annotations.VisibleForTesting;
241 import com.android.internal.graphics.ColorUtils;
242 import com.android.internal.inputmethod.EditableInputConnection;
243 import com.android.internal.logging.MetricsLogger;
244 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
245 import com.android.internal.util.ArrayUtils;
246 import com.android.internal.util.FastMath;
247 import com.android.internal.util.Preconditions;
248 import com.android.text.flags.Flags;
249 
250 import libcore.util.EmptyArray;
251 
252 import org.xmlpull.v1.XmlPullParserException;
253 
254 import java.io.IOException;
255 import java.lang.annotation.Retention;
256 import java.lang.annotation.RetentionPolicy;
257 import java.lang.ref.WeakReference;
258 import java.util.ArrayList;
259 import java.util.Arrays;
260 import java.util.List;
261 import java.util.Locale;
262 import java.util.Objects;
263 import java.util.Set;
264 import java.util.concurrent.CompletableFuture;
265 import java.util.concurrent.TimeUnit;
266 import java.util.function.Consumer;
267 import java.util.function.Supplier;
268 import java.util.regex.Matcher;
269 import java.util.regex.Pattern;
270 
271 /**
272  * A user interface element that displays text to the user.
273  * To provide user-editable text, see {@link EditText}.
274  * <p>
275  * The following code sample shows a typical use, with an XML layout
276  * and code to modify the contents of the text view:
277  * </p>
278 
279  * <pre>
280  * &lt;LinearLayout
281        xmlns:android="http://schemas.android.com/apk/res/android"
282        android:layout_width="match_parent"
283        android:layout_height="match_parent"&gt;
284  *    &lt;TextView
285  *        android:id="@+id/text_view_id"
286  *        android:layout_height="wrap_content"
287  *        android:layout_width="wrap_content"
288  *        android:text="@string/hello" /&gt;
289  * &lt;/LinearLayout&gt;
290  * </pre>
291  * <p>
292  * This code sample demonstrates how to modify the contents of the text view
293  * defined in the previous XML layout:
294  * </p>
295  * <pre>
296  * public class MainActivity extends Activity {
297  *
298  *    protected void onCreate(Bundle savedInstanceState) {
299  *         super.onCreate(savedInstanceState);
300  *         setContentView(R.layout.activity_main);
301  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
302  *         helloTextView.setText(R.string.user_greeting);
303  *     }
304  * }
305  * </pre>
306  * <p>
307  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
308  * </p>
309  * <p>
310  * <b>XML attributes</b>
311  * <p>
312  * See {@link android.R.styleable#TextView TextView Attributes},
313  * {@link android.R.styleable#View View Attributes}
314  *
315  * @attr ref android.R.styleable#TextView_text
316  * @attr ref android.R.styleable#TextView_bufferType
317  * @attr ref android.R.styleable#TextView_hint
318  * @attr ref android.R.styleable#TextView_textColor
319  * @attr ref android.R.styleable#TextView_textColorHighlight
320  * @attr ref android.R.styleable#TextView_textColorHint
321  * @attr ref android.R.styleable#TextView_textAppearance
322  * @attr ref android.R.styleable#TextView_textColorLink
323  * @attr ref android.R.styleable#TextView_textFontWeight
324  * @attr ref android.R.styleable#TextView_textSize
325  * @attr ref android.R.styleable#TextView_textScaleX
326  * @attr ref android.R.styleable#TextView_fontFamily
327  * @attr ref android.R.styleable#TextView_typeface
328  * @attr ref android.R.styleable#TextView_textStyle
329  * @attr ref android.R.styleable#TextView_cursorVisible
330  * @attr ref android.R.styleable#TextView_maxLines
331  * @attr ref android.R.styleable#TextView_maxHeight
332  * @attr ref android.R.styleable#TextView_lines
333  * @attr ref android.R.styleable#TextView_height
334  * @attr ref android.R.styleable#TextView_minLines
335  * @attr ref android.R.styleable#TextView_minHeight
336  * @attr ref android.R.styleable#TextView_maxEms
337  * @attr ref android.R.styleable#TextView_maxWidth
338  * @attr ref android.R.styleable#TextView_ems
339  * @attr ref android.R.styleable#TextView_width
340  * @attr ref android.R.styleable#TextView_minEms
341  * @attr ref android.R.styleable#TextView_minWidth
342  * @attr ref android.R.styleable#TextView_gravity
343  * @attr ref android.R.styleable#TextView_scrollHorizontally
344  * @attr ref android.R.styleable#TextView_password
345  * @attr ref android.R.styleable#TextView_singleLine
346  * @attr ref android.R.styleable#TextView_selectAllOnFocus
347  * @attr ref android.R.styleable#TextView_includeFontPadding
348  * @attr ref android.R.styleable#TextView_maxLength
349  * @attr ref android.R.styleable#TextView_shadowColor
350  * @attr ref android.R.styleable#TextView_shadowDx
351  * @attr ref android.R.styleable#TextView_shadowDy
352  * @attr ref android.R.styleable#TextView_shadowRadius
353  * @attr ref android.R.styleable#TextView_autoLink
354  * @attr ref android.R.styleable#TextView_linksClickable
355  * @attr ref android.R.styleable#TextView_numeric
356  * @attr ref android.R.styleable#TextView_digits
357  * @attr ref android.R.styleable#TextView_phoneNumber
358  * @attr ref android.R.styleable#TextView_inputMethod
359  * @attr ref android.R.styleable#TextView_capitalize
360  * @attr ref android.R.styleable#TextView_autoText
361  * @attr ref android.R.styleable#TextView_editable
362  * @attr ref android.R.styleable#TextView_freezesText
363  * @attr ref android.R.styleable#TextView_ellipsize
364  * @attr ref android.R.styleable#TextView_drawableTop
365  * @attr ref android.R.styleable#TextView_drawableBottom
366  * @attr ref android.R.styleable#TextView_drawableRight
367  * @attr ref android.R.styleable#TextView_drawableLeft
368  * @attr ref android.R.styleable#TextView_drawableStart
369  * @attr ref android.R.styleable#TextView_drawableEnd
370  * @attr ref android.R.styleable#TextView_drawablePadding
371  * @attr ref android.R.styleable#TextView_drawableTint
372  * @attr ref android.R.styleable#TextView_drawableTintMode
373  * @attr ref android.R.styleable#TextView_lineSpacingExtra
374  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
375  * @attr ref android.R.styleable#TextView_justificationMode
376  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
377  * @attr ref android.R.styleable#TextView_inputType
378  * @attr ref android.R.styleable#TextView_imeOptions
379  * @attr ref android.R.styleable#TextView_privateImeOptions
380  * @attr ref android.R.styleable#TextView_imeActionLabel
381  * @attr ref android.R.styleable#TextView_imeActionId
382  * @attr ref android.R.styleable#TextView_editorExtras
383  * @attr ref android.R.styleable#TextView_elegantTextHeight
384  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
385  * @attr ref android.R.styleable#TextView_letterSpacing
386  * @attr ref android.R.styleable#TextView_fontFeatureSettings
387  * @attr ref android.R.styleable#TextView_fontVariationSettings
388  * @attr ref android.R.styleable#TextView_breakStrategy
389  * @attr ref android.R.styleable#TextView_hyphenationFrequency
390  * @attr ref android.R.styleable#TextView_lineBreakStyle
391  * @attr ref android.R.styleable#TextView_lineBreakWordStyle
392  * @attr ref android.R.styleable#TextView_autoSizeTextType
393  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
394  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
395  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
396  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
397  * @attr ref android.R.styleable#TextView_textCursorDrawable
398  * @attr ref android.R.styleable#TextView_textSelectHandle
399  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
400  * @attr ref android.R.styleable#TextView_textSelectHandleRight
401  * @attr ref android.R.styleable#TextView_allowUndo
402  * @attr ref android.R.styleable#TextView_enabled
403  */
404 @RemoteView
405 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
406     static final String LOG_TAG = "TextView";
407     static final boolean DEBUG_EXTRACT = false;
408     static final boolean DEBUG_CURSOR = false;
409 
410     private static final float[] TEMP_POSITION = new float[2];
411 
412     // Enum for the "typeface" XML parameter.
413     // TODO: How can we get this from the XML instead of hardcoding it here?
414     /** @hide */
415     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
416     @Retention(RetentionPolicy.SOURCE)
417     public @interface XMLTypefaceAttr{}
418     private static final int DEFAULT_TYPEFACE = -1;
419     private static final int SANS = 1;
420     private static final int SERIF = 2;
421     private static final int MONOSPACE = 3;
422 
423     // Enum for the "ellipsize" XML parameter.
424     private static final int ELLIPSIZE_NOT_SET = -1;
425     private static final int ELLIPSIZE_NONE = 0;
426     private static final int ELLIPSIZE_START = 1;
427     private static final int ELLIPSIZE_MIDDLE = 2;
428     private static final int ELLIPSIZE_END = 3;
429     private static final int ELLIPSIZE_MARQUEE = 4;
430 
431     // Bitfield for the "numeric" XML parameter.
432     // TODO: How can we get this from the XML instead of hardcoding it here?
433     private static final int SIGNED = 2;
434     private static final int DECIMAL = 4;
435 
436     /**
437      * Draw marquee text with fading edges as usual
438      */
439     private static final int MARQUEE_FADE_NORMAL = 0;
440 
441     /**
442      * Draw marquee text as ellipsize end while inactive instead of with the fade.
443      * (Useful for devices where the fade can be expensive if overdone)
444      */
445     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
446 
447     /**
448      * Draw marquee text with fading edges because it is currently active/animating.
449      */
450     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
451 
452     @UnsupportedAppUsage
453     private static final int LINES = 1;
454     private static final int EMS = LINES;
455     private static final int PIXELS = 2;
456 
457     // Maximum text length for single line input.
458     private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
459     private InputFilter.LengthFilter mSingleLineLengthFilter = null;
460 
461     private static final RectF TEMP_RECTF = new RectF();
462 
463     /** @hide */
464     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
465     private static final int ANIMATED_SCROLL_GAP = 250;
466 
467     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
468     private static final Spanned EMPTY_SPANNED = new SpannedString("");
469 
470     private static final int CHANGE_WATCHER_PRIORITY = 100;
471 
472     /**
473      * The span priority of the {@link OffsetMapping} that is set on the text. It must be
474      * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is
475      * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered
476      * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}.
477      */
478     private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200;
479 
480     // New state used to change background based on whether this TextView is multiline.
481     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
482 
483     // Accessibility action to share selected text.
484     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
485 
486     /**
487      * @hide
488      */
489     // Accessibility action start id for "process text" actions.
490     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
491 
492     /** Accessibility action start id for "smart" actions. @hide */
493     static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000;
494 
495     /**
496      * @hide
497      */
498     @TestApi
499     public static final int PROCESS_TEXT_REQUEST_CODE = 100;
500 
501     /**
502      *  Return code of {@link #doKeyDown}.
503      */
504     private static final int KEY_EVENT_NOT_HANDLED = 0;
505     private static final int KEY_EVENT_HANDLED = -1;
506     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
507     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
508 
509     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
510 
511     // The default value of the line break style.
512     private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE;
513 
514     // The default value of the line break word style.
515     private static final int DEFAULT_LINE_BREAK_WORD_STYLE =
516             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
517 
518     /**
519      * This change ID enables the fallback text line spacing (line height) for BoringLayout.
520      * @hide
521      */
522     @ChangeId
523     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
524     public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id
525 
526     /**
527      * This change ID enables the fallback text line spacing (line height) for StaticLayout.
528      * @hide
529      */
530     @ChangeId
531     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P)
532     public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id
533 
534 
535     /**
536      * This change ID enables the bounding box based layout.
537      * @hide
538      */
539     @ChangeId
540     @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
541     public static final long USE_BOUNDS_FOR_WIDTH = 63938206;  // buganizer id
542 
543     // System wide time for last cut, copy or text changed action.
544     static long sLastCutCopyOrTextChangedTime;
545     private ColorStateList mTextColor;
546     private ColorStateList mHintTextColor;
547     private ColorStateList mLinkTextColor;
548     @ViewDebug.ExportedProperty(category = "text")
549 
550     /**
551      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
552      */
553     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
554     private int mCurTextColor;
555 
556     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
557     private int mCurHintTextColor;
558     private boolean mFreezesText;
559 
560     @UnsupportedAppUsage
561     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
562     @UnsupportedAppUsage
563     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
564 
565     @UnsupportedAppUsage
566     private float mShadowRadius;
567     @UnsupportedAppUsage
568     private float mShadowDx;
569     @UnsupportedAppUsage
570     private float mShadowDy;
571     private int mShadowColor;
572 
573     private int mLastOrientation;
574 
575     private boolean mPreDrawRegistered;
576     private boolean mPreDrawListenerDetached;
577 
578     private TextClassifier mTextClassifier;
579     private TextClassifier mTextClassificationSession;
580     private TextClassificationContext mTextClassificationContext;
581 
582     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
583     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
584     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
585     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
586     // into by the user holding the movement key down) then we shouldn't prevent the focus from
587     // changing.
588     private boolean mPreventDefaultMovement;
589 
590     private TextUtils.TruncateAt mEllipsize;
591 
592     // A flag to indicate the cursor was hidden by IME.
593     private boolean mImeIsConsumingInput;
594 
595     // Whether cursor is visible without regard to {@link mImeConsumesInput}.
596     // {@code true} is the default value.
597     private boolean mCursorVisibleFromAttr = true;
598 
599     static class Drawables {
600         static final int LEFT = 0;
601         static final int TOP = 1;
602         static final int RIGHT = 2;
603         static final int BOTTOM = 3;
604 
605         static final int DRAWABLE_NONE = -1;
606         static final int DRAWABLE_RIGHT = 0;
607         static final int DRAWABLE_LEFT = 1;
608 
609         final Rect mCompoundRect = new Rect();
610 
611         final Drawable[] mShowing = new Drawable[4];
612 
613         ColorStateList mTintList;
614         BlendMode mBlendMode;
615         boolean mHasTint;
616         boolean mHasTintMode;
617 
618         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
619         Drawable mDrawableLeftInitial, mDrawableRightInitial;
620 
621         boolean mIsRtlCompatibilityMode;
622         boolean mOverride;
623 
624         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
625                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
626 
627         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
628                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
629 
630         int mDrawablePadding;
631 
632         int mDrawableSaved = DRAWABLE_NONE;
633 
634         public Drawables(Context context) {
635             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
636             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
637                     || !context.getApplicationInfo().hasRtlSupport();
638             mOverride = false;
639         }
640 
641         /**
642          * @return {@code true} if this object contains metadata that needs to
643          *         be retained, {@code false} otherwise
644          */
645         public boolean hasMetadata() {
646             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
647         }
648 
649         /**
650          * Updates the list of displayed drawables to account for the current
651          * layout direction.
652          *
653          * @param layoutDirection the current layout direction
654          * @return {@code true} if the displayed drawables changed
655          */
656         public boolean resolveWithLayoutDirection(int layoutDirection) {
657             final Drawable previousLeft = mShowing[Drawables.LEFT];
658             final Drawable previousRight = mShowing[Drawables.RIGHT];
659 
660             // First reset "left" and "right" drawables to their initial values
661             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
662             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
663 
664             if (mIsRtlCompatibilityMode) {
665                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
666                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
667                     mShowing[Drawables.LEFT] = mDrawableStart;
668                     mDrawableSizeLeft = mDrawableSizeStart;
669                     mDrawableHeightLeft = mDrawableHeightStart;
670                 }
671                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
672                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
673                     mShowing[Drawables.RIGHT] = mDrawableEnd;
674                     mDrawableSizeRight = mDrawableSizeEnd;
675                     mDrawableHeightRight = mDrawableHeightEnd;
676                 }
677             } else {
678                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
679                 // drawable if and only if they have been defined
680                 switch(layoutDirection) {
681                     case LAYOUT_DIRECTION_RTL:
682                         if (mOverride) {
683                             mShowing[Drawables.RIGHT] = mDrawableStart;
684                             mDrawableSizeRight = mDrawableSizeStart;
685                             mDrawableHeightRight = mDrawableHeightStart;
686 
687                             mShowing[Drawables.LEFT] = mDrawableEnd;
688                             mDrawableSizeLeft = mDrawableSizeEnd;
689                             mDrawableHeightLeft = mDrawableHeightEnd;
690                         }
691                         break;
692 
693                     case LAYOUT_DIRECTION_LTR:
694                     default:
695                         if (mOverride) {
696                             mShowing[Drawables.LEFT] = mDrawableStart;
697                             mDrawableSizeLeft = mDrawableSizeStart;
698                             mDrawableHeightLeft = mDrawableHeightStart;
699 
700                             mShowing[Drawables.RIGHT] = mDrawableEnd;
701                             mDrawableSizeRight = mDrawableSizeEnd;
702                             mDrawableHeightRight = mDrawableHeightEnd;
703                         }
704                         break;
705                 }
706             }
707 
708             applyErrorDrawableIfNeeded(layoutDirection);
709 
710             return mShowing[Drawables.LEFT] != previousLeft
711                     || mShowing[Drawables.RIGHT] != previousRight;
712         }
713 
714         public void setErrorDrawable(Drawable dr, TextView tv) {
715             if (mDrawableError != dr && mDrawableError != null) {
716                 mDrawableError.setCallback(null);
717             }
718             mDrawableError = dr;
719 
720             if (mDrawableError != null) {
721                 final Rect compoundRect = mCompoundRect;
722                 final int[] state = tv.getDrawableState();
723 
724                 mDrawableError.setState(state);
725                 mDrawableError.copyBounds(compoundRect);
726                 mDrawableError.setCallback(tv);
727                 mDrawableSizeError = compoundRect.width();
728                 mDrawableHeightError = compoundRect.height();
729             } else {
730                 mDrawableSizeError = mDrawableHeightError = 0;
731             }
732         }
733 
734         private void applyErrorDrawableIfNeeded(int layoutDirection) {
735             // first restore the initial state if needed
736             switch (mDrawableSaved) {
737                 case DRAWABLE_LEFT:
738                     mShowing[Drawables.LEFT] = mDrawableTemp;
739                     mDrawableSizeLeft = mDrawableSizeTemp;
740                     mDrawableHeightLeft = mDrawableHeightTemp;
741                     break;
742                 case DRAWABLE_RIGHT:
743                     mShowing[Drawables.RIGHT] = mDrawableTemp;
744                     mDrawableSizeRight = mDrawableSizeTemp;
745                     mDrawableHeightRight = mDrawableHeightTemp;
746                     break;
747                 case DRAWABLE_NONE:
748                 default:
749             }
750             // then, if needed, assign the Error drawable to the correct location
751             if (mDrawableError != null) {
752                 switch(layoutDirection) {
753                     case LAYOUT_DIRECTION_RTL:
754                         mDrawableSaved = DRAWABLE_LEFT;
755 
756                         mDrawableTemp = mShowing[Drawables.LEFT];
757                         mDrawableSizeTemp = mDrawableSizeLeft;
758                         mDrawableHeightTemp = mDrawableHeightLeft;
759 
760                         mShowing[Drawables.LEFT] = mDrawableError;
761                         mDrawableSizeLeft = mDrawableSizeError;
762                         mDrawableHeightLeft = mDrawableHeightError;
763                         break;
764                     case LAYOUT_DIRECTION_LTR:
765                     default:
766                         mDrawableSaved = DRAWABLE_RIGHT;
767 
768                         mDrawableTemp = mShowing[Drawables.RIGHT];
769                         mDrawableSizeTemp = mDrawableSizeRight;
770                         mDrawableHeightTemp = mDrawableHeightRight;
771 
772                         mShowing[Drawables.RIGHT] = mDrawableError;
773                         mDrawableSizeRight = mDrawableSizeError;
774                         mDrawableHeightRight = mDrawableHeightError;
775                         break;
776                 }
777             }
778         }
779     }
780 
781     @UnsupportedAppUsage
782     Drawables mDrawables;
783 
784     @UnsupportedAppUsage
785     private CharWrapper mCharWrapper;
786 
787     @UnsupportedAppUsage(trackingBug = 124050217)
788     private Marquee mMarquee;
789     @UnsupportedAppUsage
790     private boolean mRestartMarquee;
791 
792     private int mMarqueeRepeatLimit = 3;
793 
794     private int mLastLayoutDirection = -1;
795 
796     /**
797      * On some devices the fading edges add a performance penalty if used
798      * extensively in the same layout. This mode indicates how the marquee
799      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
800      */
801     @UnsupportedAppUsage
802     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
803 
804     /**
805      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
806      * the layout that should be used when the mode switches.
807      */
808     @UnsupportedAppUsage
809     private Layout mSavedMarqueeModeLayout;
810 
811     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
812     @ViewDebug.ExportedProperty(category = "text")
813     @UnsupportedAppUsage
814     private @Nullable CharSequence mText;
815     private @Nullable Spannable mSpannable;
816     private @Nullable PrecomputedText mPrecomputed;
817 
818     @UnsupportedAppUsage
819     private CharSequence mTransformed;
820     @UnsupportedAppUsage
821     private BufferType mBufferType = BufferType.NORMAL;
822 
823     private CharSequence mHint;
824     @UnsupportedAppUsage
825     private Layout mHintLayout;
826     private boolean mHideHint;
827 
828     private MovementMethod mMovement;
829 
830     private TransformationMethod mTransformation;
831     @UnsupportedAppUsage
832     private boolean mAllowTransformationLengthChange;
833     @UnsupportedAppUsage
834     private ChangeWatcher mChangeWatcher;
835 
836     @UnsupportedAppUsage(trackingBug = 123769451)
837     private ArrayList<TextWatcher> mListeners;
838 
839     // display attributes
840     @UnsupportedAppUsage
841     private final TextPaint mTextPaint;
842     @UnsupportedAppUsage
843     private boolean mUserSetTextScaleX;
844     @UnsupportedAppUsage
845     private Layout mLayout;
846     private boolean mLocalesChanged = false;
847     private int mTextSizeUnit = -1;
848     private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
849     private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
850 
851     // This is used to reflect the current user preference for changing font weight and making text
852     // more bold.
853     private int mFontWeightAdjustment;
854     private Typeface mOriginalTypeface;
855 
856     // True if setKeyListener() has been explicitly called
857     private boolean mListenerChanged = false;
858     // True if internationalized input should be used for numbers and date and time.
859     private final boolean mUseInternationalizedInput;
860 
861     // Fallback fonts that end up getting used should be allowed to affect line spacing.
862     private static final int FALLBACK_LINE_SPACING_NONE = 0;
863     private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1;
864     private static final int FALLBACK_LINE_SPACING_ALL = 2;
865 
866     private int mUseFallbackLineSpacing;
867     // True if the view text can be padded for compat reasons, when the view is translated.
868     private final boolean mUseTextPaddingForUiTranslation;
869 
870     private boolean mUseBoundsForWidth;
871     private boolean mShiftDrawingOffsetForStartOverhang;
872     @Nullable private Paint.FontMetrics mMinimumFontMetrics;
873     @Nullable private Paint.FontMetrics mLocalePreferredFontMetrics;
874     private boolean mUseLocalePreferredLineHeightForMinimum;
875 
876     @ViewDebug.ExportedProperty(category = "text")
877     @UnsupportedAppUsage
878     private int mGravity = Gravity.TOP | Gravity.START;
879     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
880     private boolean mHorizontallyScrolling;
881 
882     private int mAutoLinkMask;
883     private boolean mLinksClickable = true;
884 
885     @UnsupportedAppUsage
886     private float mSpacingMult = 1.0f;
887     @UnsupportedAppUsage
888     private float mSpacingAdd = 0.0f;
889 
890     /**
891      * Remembers what line height was set to originally, before we broke it down into raw pixels.
892      *
893      * <p>This is stored as a complex dimension with both value and unit packed into one field!
894      * {@see TypedValue}
895      */
896     private int mLineHeightComplexDimen;
897 
898     private int mBreakStrategy;
899     private int mHyphenationFrequency;
900     private int mJustificationMode;
901 
902     @UnsupportedAppUsage
903     private int mMaximum = Integer.MAX_VALUE;
904     @UnsupportedAppUsage
905     private int mMaxMode = LINES;
906     @UnsupportedAppUsage
907     private int mMinimum = 0;
908     @UnsupportedAppUsage
909     private int mMinMode = LINES;
910 
911     @UnsupportedAppUsage
912     private int mOldMaximum = mMaximum;
913     @UnsupportedAppUsage
914     private int mOldMaxMode = mMaxMode;
915 
916     @UnsupportedAppUsage
917     private int mMaxWidth = Integer.MAX_VALUE;
918     @UnsupportedAppUsage
919     private int mMaxWidthMode = PIXELS;
920     @UnsupportedAppUsage
921     private int mMinWidth = 0;
922     @UnsupportedAppUsage
923     private int mMinWidthMode = PIXELS;
924 
925     @UnsupportedAppUsage
926     private boolean mSingleLine;
927     @UnsupportedAppUsage
928     private int mDesiredHeightAtMeasure = -1;
929     @UnsupportedAppUsage
930     private boolean mIncludePad = true;
931     private int mDeferScroll = -1;
932 
933     // tmp primitives, so we don't alloc them on each draw
934     private Rect mTempRect;
935     private long mLastScroll;
936     private Scroller mScroller;
937     private TextPaint mTempTextPaint;
938 
939     private Object mTempCursor;
940 
941     @UnsupportedAppUsage
942     private BoringLayout.Metrics mBoring;
943     @UnsupportedAppUsage
944     private BoringLayout.Metrics mHintBoring;
945     @UnsupportedAppUsage
946     private BoringLayout mSavedLayout;
947     @UnsupportedAppUsage
948     private BoringLayout mSavedHintLayout;
949 
950     @UnsupportedAppUsage
951     private TextDirectionHeuristic mTextDir;
952 
953     private InputFilter[] mFilters = NO_FILTERS;
954 
955     /**
956      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
957      * the same as {@link Process#myUserHandle()}.
958      *
959      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
960      * other apps may need to set this so that the system can use right user's resources and
961      * services such as input methods and spell checkers.</p>
962      *
963      * @see #setTextOperationUser(UserHandle)
964      */
965     @Nullable
966     private UserHandle mTextOperationUser;
967 
968     private volatile Locale mCurrentSpellCheckerLocaleCache;
969 
970     // It is possible to have a selection even when mEditor is null (programmatically set, like when
971     // a link is pressed). These highlight-related fields do not go in mEditor.
972     @UnsupportedAppUsage
973     int mHighlightColor = 0x6633B5E5;
974     private Path mHighlightPath;
975     @UnsupportedAppUsage
976     private final Paint mHighlightPaint;
977     @UnsupportedAppUsage
978     private boolean mHighlightPathBogus = true;
979 
980     private List<Path> mHighlightPaths;
981     private List<Paint> mHighlightPaints;
982     private Highlights mHighlights;
983     private int[] mSearchResultHighlights = null;
984     private Paint mSearchResultHighlightPaint = null;
985     private Paint mFocusedSearchResultHighlightPaint = null;
986     private int mFocusedSearchResultHighlightColor = 0xFFFF9632;
987     private int mSearchResultHighlightColor = 0xFFFFFF00;
988 
989     private int mFocusedSearchResultIndex = -1;
990     private int mGesturePreviewHighlightStart = -1;
991     private int mGesturePreviewHighlightEnd = -1;
992     private Paint mGesturePreviewHighlightPaint;
993     private final List<Path> mPathRecyclePool = new ArrayList<>();
994     private boolean mHighlightPathsBogus = true;
995 
996     // Although these fields are specific to editable text, they are not added to Editor because
997     // they are defined by the TextView's style and are theme-dependent.
998     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
999     int mCursorDrawableRes;
1000     private Drawable mCursorDrawable;
1001     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
1002     // by removing it, but we would break apps targeting <= P that use it by reflection.
1003     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1004     int mTextSelectHandleLeftRes;
1005     private Drawable mTextSelectHandleLeft;
1006     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
1007     // by removing it, but we would break apps targeting <= P that use it by reflection.
1008     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1009     int mTextSelectHandleRightRes;
1010     private Drawable mTextSelectHandleRight;
1011     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
1012     // by removing it, but we would break apps targeting <= P that use it by reflection.
1013     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1014     int mTextSelectHandleRes;
1015     private Drawable mTextSelectHandle;
1016     int mTextEditSuggestionItemLayout;
1017     int mTextEditSuggestionContainerLayout;
1018     int mTextEditSuggestionHighlightStyle;
1019 
1020     private static final int NO_POINTER_ID = -1;
1021     /**
1022      * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among
1023      * TextView and the handle views which are rendered on popup windows.
1024      */
1025     private int mPrimePointerId = NO_POINTER_ID;
1026 
1027     /**
1028      * Whether the prime pointer is from the event delivered to selection handle or insertion
1029      * handle.
1030      */
1031     private boolean mIsPrimePointerFromHandleView;
1032 
1033     /**
1034      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
1035      * See {@link #createEditorIfNeeded()}.
1036      */
1037     @UnsupportedAppUsage
1038     private Editor mEditor;
1039 
1040     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
1041     private static final int DEVICE_PROVISIONED_NO = 1;
1042     private static final int DEVICE_PROVISIONED_YES = 2;
1043 
1044     /**
1045      * Some special options such as sharing selected text should only be shown if the device
1046      * is provisioned. Only check the provisioned state once for a given view instance.
1047      */
1048     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
1049 
1050     /**
1051      * The last input source on this TextView.
1052      *
1053      * Use the SOURCE_TOUCHSCREEN as the default value for backward compatibility. There could be a
1054      * non UI event originated ActionMode initiation, e.g. API call, a11y events, etc.
1055      */
1056     private int mLastInputSource = InputDevice.SOURCE_TOUCHSCREEN;
1057 
1058     /**
1059      * The TextView does not auto-size text (default).
1060      */
1061     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
1062 
1063     /**
1064      * The TextView scales text size both horizontally and vertically to fit within the
1065      * container.
1066      */
1067     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
1068 
1069     /** @hide */
1070     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
1071             AUTO_SIZE_TEXT_TYPE_NONE,
1072             AUTO_SIZE_TEXT_TYPE_UNIFORM
1073     })
1074     @Retention(RetentionPolicy.SOURCE)
1075     public @interface AutoSizeTextType {}
1076     // Default minimum size for auto-sizing text in scaled pixels.
1077     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
1078     // Default maximum size for auto-sizing text in scaled pixels.
1079     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
1080     // Default value for the step size in pixels.
1081     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
1082     // Use this to specify that any of the auto-size configuration int values have not been set.
1083     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
1084     // Auto-size text type.
1085     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1086     // Specify if auto-size text is needed.
1087     private boolean mNeedsAutoSizeText = false;
1088     // Step size for auto-sizing in pixels.
1089     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1090     // Minimum text size for auto-sizing in pixels.
1091     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1092     // Maximum text size for auto-sizing in pixels.
1093     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1094     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
1095     // when auto-sizing text.
1096     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
1097     // Specifies whether auto-size should use the provided auto size steps set or if it should
1098     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
1099     // mAutoSizeStepGranularityInPx.
1100     private boolean mHasPresetAutoSizeValues = false;
1101 
1102     // Autofill-related attributes
1103     //
1104     // Indicates whether the text was set statically or dynamically, so it can be used to
1105     // sanitize autofill requests.
1106     private boolean mTextSetFromXmlOrResourceId = false;
1107     // Resource id used to set the text.
1108     private @StringRes int mTextId = Resources.ID_NULL;
1109     // Resource id used to set the hint.
1110     private @StringRes int mHintId = Resources.ID_NULL;
1111     //
1112     // End of autofill-related attributes
1113 
1114     private Pattern mWhitespacePattern;
1115 
1116     /**
1117      * Kick-start the font cache for the zygote process (to pay the cost of
1118      * initializing freetype for our default font only once).
1119      * @hide
1120      */
1121     public static void preloadFontCache() {
1122         if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
1123             return;
1124         }
1125         Paint p = new Paint();
1126         p.setAntiAlias(true);
1127         // Ensure that the Typeface is loaded here.
1128         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
1129         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
1130         // since Paint.measureText can not be called without Typeface static initializer.
1131         p.setTypeface(Typeface.DEFAULT);
1132         // We don't care about the result, just the side-effect of measuring.
1133         p.measureText("H");
1134     }
1135 
1136     /**
1137      * Interface definition for a callback to be invoked when an action is
1138      * performed on the editor.
1139      */
1140     public interface OnEditorActionListener {
1141         /**
1142          * Called when an action is being performed.
1143          *
1144          * @param v The view that was clicked.
1145          * @param actionId Identifier of the action.  This will be either the
1146          * identifier you supplied, or {@link EditorInfo#IME_NULL
1147          * EditorInfo.IME_NULL} if being called due to the enter key
1148          * being pressed. Starting from Android 14, the action identifier will
1149          * also be included when triggered by an enter key if the input is
1150          * constrained to a single line.
1151          * @param event If triggered by an enter key, this is the event;
1152          * otherwise, this is null.
1153          * @return Return true if you have consumed the action, else false.
1154          */
1155         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
1156     }
1157 
1158     public TextView(Context context) {
1159         this(context, null);
1160     }
1161 
1162     public TextView(Context context, @Nullable AttributeSet attrs) {
1163         this(context, attrs, com.android.internal.R.attr.textViewStyle);
1164     }
1165 
1166     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
1167         this(context, attrs, defStyleAttr, 0);
1168     }
1169 
1170     @SuppressWarnings("deprecation")
1171     public TextView(
1172             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1173         super(context, attrs, defStyleAttr, defStyleRes);
1174 
1175         // TextView is important by default, unless app developer overrode attribute.
1176         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
1177             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
1178         }
1179         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
1180             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
1181         }
1182 
1183         setTextInternal("");
1184 
1185         final Resources res = getResources();
1186         final CompatibilityInfo compat = res.getCompatibilityInfo();
1187 
1188         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
1189         mTextPaint.density = res.getDisplayMetrics().density;
1190         mTextPaint.setCompatibilityScaling(compat.applicationScale);
1191 
1192         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1193         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
1194 
1195         mMovement = getDefaultMovementMethod();
1196 
1197         mTransformation = null;
1198 
1199         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
1200         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
1201         attributes.mTextSize = 15;
1202         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1203         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1204         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1205         mLastOrientation = getResources().getConfiguration().orientation;
1206 
1207         final Resources.Theme theme = context.getTheme();
1208 
1209         /*
1210          * Look the appearance up without checking first if it exists because
1211          * almost every TextView has one and it greatly simplifies the logic
1212          * to be able to parse the appearance first and then let specific tags
1213          * for this View override it.
1214          */
1215         TypedArray a = theme.obtainStyledAttributes(attrs,
1216                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1217         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1218                 attrs, a, defStyleAttr, defStyleRes);
1219         TypedArray appearance = null;
1220         int ap = a.getResourceId(
1221                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1222         a.recycle();
1223         if (ap != -1) {
1224             appearance = theme.obtainStyledAttributes(
1225                     ap, com.android.internal.R.styleable.TextAppearance);
1226             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1227                     null, appearance, 0, ap);
1228         }
1229         if (appearance != null) {
1230             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1231             attributes.mFontFamilyExplicit = false;
1232             appearance.recycle();
1233         }
1234 
1235         boolean editable = getDefaultEditable();
1236         CharSequence inputMethod = null;
1237         int numeric = 0;
1238         CharSequence digits = null;
1239         boolean phone = false;
1240         boolean autotext = false;
1241         int autocap = -1;
1242         int buffertype = 0;
1243         boolean selectallonfocus = false;
1244         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1245                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1246         ColorStateList drawableTint = null;
1247         BlendMode drawableTintMode = null;
1248         int drawablePadding = 0;
1249         int ellipsize = ELLIPSIZE_NOT_SET;
1250         boolean singleLine = false;
1251         int maxlength = -1;
1252         CharSequence text = "";
1253         CharSequence hint = null;
1254         boolean password = false;
1255         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1256         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1257         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1258         int inputType = EditorInfo.TYPE_NULL;
1259         a = theme.obtainStyledAttributes(
1260                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1261         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1262                 defStyleAttr, defStyleRes);
1263         int firstBaselineToTopHeight = -1;
1264         int lastBaselineToBottomHeight = -1;
1265         float lineHeight = -1f;
1266         int lineHeightUnit = -1;
1267         boolean hasUseBoundForWidthValue = false;
1268 
1269         readTextAppearance(context, a, attributes, true /* styleArray */);
1270 
1271         int n = a.getIndexCount();
1272 
1273         // Must set id in a temporary variable because it will be reset by setText()
1274         boolean textIsSetFromXml = false;
1275         for (int i = 0; i < n; i++) {
1276             int attr = a.getIndex(i);
1277 
1278             switch (attr) {
1279                 case com.android.internal.R.styleable.TextView_editable:
1280                     editable = a.getBoolean(attr, editable);
1281                     break;
1282 
1283                 case com.android.internal.R.styleable.TextView_inputMethod:
1284                     inputMethod = a.getText(attr);
1285                     break;
1286 
1287                 case com.android.internal.R.styleable.TextView_numeric:
1288                     numeric = a.getInt(attr, numeric);
1289                     break;
1290 
1291                 case com.android.internal.R.styleable.TextView_digits:
1292                     digits = a.getText(attr);
1293                     break;
1294 
1295                 case com.android.internal.R.styleable.TextView_phoneNumber:
1296                     phone = a.getBoolean(attr, phone);
1297                     break;
1298 
1299                 case com.android.internal.R.styleable.TextView_autoText:
1300                     autotext = a.getBoolean(attr, autotext);
1301                     break;
1302 
1303                 case com.android.internal.R.styleable.TextView_capitalize:
1304                     autocap = a.getInt(attr, autocap);
1305                     break;
1306 
1307                 case com.android.internal.R.styleable.TextView_bufferType:
1308                     buffertype = a.getInt(attr, buffertype);
1309                     break;
1310 
1311                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1312                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1313                     break;
1314 
1315                 case com.android.internal.R.styleable.TextView_autoLink:
1316                     mAutoLinkMask = a.getInt(attr, 0);
1317                     break;
1318 
1319                 case com.android.internal.R.styleable.TextView_linksClickable:
1320                     mLinksClickable = a.getBoolean(attr, true);
1321                     break;
1322 
1323                 case com.android.internal.R.styleable.TextView_drawableLeft:
1324                     drawableLeft = a.getDrawable(attr);
1325                     break;
1326 
1327                 case com.android.internal.R.styleable.TextView_drawableTop:
1328                     drawableTop = a.getDrawable(attr);
1329                     break;
1330 
1331                 case com.android.internal.R.styleable.TextView_drawableRight:
1332                     drawableRight = a.getDrawable(attr);
1333                     break;
1334 
1335                 case com.android.internal.R.styleable.TextView_drawableBottom:
1336                     drawableBottom = a.getDrawable(attr);
1337                     break;
1338 
1339                 case com.android.internal.R.styleable.TextView_drawableStart:
1340                     drawableStart = a.getDrawable(attr);
1341                     break;
1342 
1343                 case com.android.internal.R.styleable.TextView_drawableEnd:
1344                     drawableEnd = a.getDrawable(attr);
1345                     break;
1346 
1347                 case com.android.internal.R.styleable.TextView_drawableTint:
1348                     drawableTint = a.getColorStateList(attr);
1349                     break;
1350 
1351                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1352                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1353                             drawableTintMode);
1354                     break;
1355 
1356                 case com.android.internal.R.styleable.TextView_drawablePadding:
1357                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1358                     break;
1359 
1360                 case com.android.internal.R.styleable.TextView_maxLines:
1361                     setMaxLines(a.getInt(attr, -1));
1362                     break;
1363 
1364                 case com.android.internal.R.styleable.TextView_maxHeight:
1365                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1366                     break;
1367 
1368                 case com.android.internal.R.styleable.TextView_lines:
1369                     setLines(a.getInt(attr, -1));
1370                     break;
1371 
1372                 case com.android.internal.R.styleable.TextView_height:
1373                     setHeight(a.getDimensionPixelSize(attr, -1));
1374                     break;
1375 
1376                 case com.android.internal.R.styleable.TextView_minLines:
1377                     setMinLines(a.getInt(attr, -1));
1378                     break;
1379 
1380                 case com.android.internal.R.styleable.TextView_minHeight:
1381                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1382                     break;
1383 
1384                 case com.android.internal.R.styleable.TextView_maxEms:
1385                     setMaxEms(a.getInt(attr, -1));
1386                     break;
1387 
1388                 case com.android.internal.R.styleable.TextView_maxWidth:
1389                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1390                     break;
1391 
1392                 case com.android.internal.R.styleable.TextView_ems:
1393                     setEms(a.getInt(attr, -1));
1394                     break;
1395 
1396                 case com.android.internal.R.styleable.TextView_width:
1397                     setWidth(a.getDimensionPixelSize(attr, -1));
1398                     break;
1399 
1400                 case com.android.internal.R.styleable.TextView_minEms:
1401                     setMinEms(a.getInt(attr, -1));
1402                     break;
1403 
1404                 case com.android.internal.R.styleable.TextView_minWidth:
1405                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1406                     break;
1407 
1408                 case com.android.internal.R.styleable.TextView_gravity:
1409                     setGravity(a.getInt(attr, -1));
1410                     break;
1411 
1412                 case com.android.internal.R.styleable.TextView_hint:
1413                     mHintId = a.getResourceId(attr, Resources.ID_NULL);
1414                     hint = a.getText(attr);
1415                     break;
1416 
1417                 case com.android.internal.R.styleable.TextView_text:
1418                     textIsSetFromXml = true;
1419                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1420                     text = a.getText(attr);
1421                     break;
1422 
1423                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1424                     if (a.getBoolean(attr, false)) {
1425                         setHorizontallyScrolling(true);
1426                     }
1427                     break;
1428 
1429                 case com.android.internal.R.styleable.TextView_singleLine:
1430                     singleLine = a.getBoolean(attr, singleLine);
1431                     break;
1432 
1433                 case com.android.internal.R.styleable.TextView_ellipsize:
1434                     ellipsize = a.getInt(attr, ellipsize);
1435                     break;
1436 
1437                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1438                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1439                     break;
1440 
1441                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1442                     if (!a.getBoolean(attr, true)) {
1443                         setIncludeFontPadding(false);
1444                     }
1445                     break;
1446 
1447                 case com.android.internal.R.styleable.TextView_cursorVisible:
1448                     if (!a.getBoolean(attr, true)) {
1449                         setCursorVisible(false);
1450                     }
1451                     break;
1452 
1453                 case com.android.internal.R.styleable.TextView_maxLength:
1454                     maxlength = a.getInt(attr, -1);
1455                     break;
1456 
1457                 case com.android.internal.R.styleable.TextView_textScaleX:
1458                     setTextScaleX(a.getFloat(attr, 1.0f));
1459                     break;
1460 
1461                 case com.android.internal.R.styleable.TextView_freezesText:
1462                     mFreezesText = a.getBoolean(attr, false);
1463                     break;
1464 
1465                 case com.android.internal.R.styleable.TextView_enabled:
1466                     setEnabled(a.getBoolean(attr, isEnabled()));
1467                     break;
1468 
1469                 case com.android.internal.R.styleable.TextView_password:
1470                     password = a.getBoolean(attr, password);
1471                     break;
1472 
1473                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1474                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1475                     break;
1476 
1477                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1478                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1479                     break;
1480 
1481                 case com.android.internal.R.styleable.TextView_inputType:
1482                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1483                     break;
1484 
1485                 case com.android.internal.R.styleable.TextView_allowUndo:
1486                     createEditorIfNeeded();
1487                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1488                     break;
1489 
1490                 case com.android.internal.R.styleable.TextView_imeOptions:
1491                     createEditorIfNeeded();
1492                     mEditor.createInputContentTypeIfNeeded();
1493                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1494                             mEditor.mInputContentType.imeOptions);
1495                     break;
1496 
1497                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1498                     createEditorIfNeeded();
1499                     mEditor.createInputContentTypeIfNeeded();
1500                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1501                     break;
1502 
1503                 case com.android.internal.R.styleable.TextView_imeActionId:
1504                     createEditorIfNeeded();
1505                     mEditor.createInputContentTypeIfNeeded();
1506                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1507                             mEditor.mInputContentType.imeActionId);
1508                     break;
1509 
1510                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1511                     setPrivateImeOptions(a.getString(attr));
1512                     break;
1513 
1514                 case com.android.internal.R.styleable.TextView_editorExtras:
1515                     try {
1516                         setInputExtras(a.getResourceId(attr, 0));
1517                     } catch (XmlPullParserException e) {
1518                         Log.w(LOG_TAG, "Failure reading input extras", e);
1519                     } catch (IOException e) {
1520                         Log.w(LOG_TAG, "Failure reading input extras", e);
1521                     }
1522                     break;
1523 
1524                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1525                     mCursorDrawableRes = a.getResourceId(attr, 0);
1526                     break;
1527 
1528                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1529                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1530                     break;
1531 
1532                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1533                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1534                     break;
1535 
1536                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1537                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1538                     break;
1539 
1540                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1541                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1542                     break;
1543 
1544                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1545                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1546                     break;
1547 
1548                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1549                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1550                     break;
1551 
1552                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1553                     setTextIsSelectable(a.getBoolean(attr, false));
1554                     break;
1555 
1556                 case com.android.internal.R.styleable.TextView_breakStrategy:
1557                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1558                     break;
1559 
1560                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1561                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1562                     break;
1563 
1564                 case com.android.internal.R.styleable.TextView_lineBreakStyle:
1565                     mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE);
1566                     break;
1567 
1568                 case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
1569                     mLineBreakWordStyle = a.getInt(attr,
1570                             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
1571                     break;
1572 
1573                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1574                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1575                     break;
1576 
1577                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1578                     autoSizeStepGranularityInPx = a.getDimension(attr,
1579                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1580                     break;
1581 
1582                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1583                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1584                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1585                     break;
1586 
1587                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1588                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1589                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1590                     break;
1591 
1592                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1593                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1594                     if (autoSizeStepSizeArrayResId > 0) {
1595                         final TypedArray autoSizePresetTextSizes = a.getResources()
1596                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1597                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1598                         autoSizePresetTextSizes.recycle();
1599                     }
1600                     break;
1601                 case com.android.internal.R.styleable.TextView_justificationMode:
1602                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1603                     break;
1604 
1605                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1606                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1607                     break;
1608 
1609                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1610                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1611                     break;
1612 
1613                 case com.android.internal.R.styleable.TextView_lineHeight:
1614                     TypedValue peekValue = a.peekValue(attr);
1615                     if (peekValue != null && peekValue.type == TypedValue.TYPE_DIMENSION) {
1616                         lineHeightUnit = peekValue.getComplexUnit();
1617                         lineHeight = TypedValue.complexToFloat(peekValue.data);
1618                     } else {
1619                         lineHeight = a.getDimensionPixelSize(attr, -1);
1620                     }
1621                     break;
1622                 case com.android.internal.R.styleable.TextView_useBoundsForWidth:
1623                     mUseBoundsForWidth = a.getBoolean(attr, false);
1624                     hasUseBoundForWidthValue = true;
1625                     break;
1626                 case com.android.internal.R.styleable
1627                         .TextView_shiftDrawingOffsetForStartOverhang:
1628                     mShiftDrawingOffsetForStartOverhang = a.getBoolean(attr, false);
1629                     break;
1630                 case com.android.internal.R.styleable
1631                         .TextView_useLocalePreferredLineHeightForMinimum:
1632                     mUseLocalePreferredLineHeightForMinimum = a.getBoolean(attr, false);
1633                     break;
1634             }
1635         }
1636 
1637         a.recycle();
1638 
1639         BufferType bufferType = BufferType.EDITABLE;
1640 
1641         final int variation =
1642                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1643         final boolean passwordInputType = variation
1644                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1645         final boolean webPasswordInputType = variation
1646                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1647         final boolean numberPasswordInputType = variation
1648                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1649 
1650         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1651         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1652         if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
1653             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL;
1654         } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) {
1655             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
1656         } else {
1657             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
1658         }
1659 
1660         if (!hasUseBoundForWidthValue) {
1661             if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
1662                 mUseBoundsForWidth = ClientFlags.useBoundsForWidth();
1663             } else {
1664                 mUseBoundsForWidth = false;
1665             }
1666         }
1667 
1668         // TODO(b/179693024): Use a ChangeId instead.
1669         mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R;
1670 
1671         if (inputMethod != null) {
1672             Class<?> c;
1673 
1674             try {
1675                 c = Class.forName(inputMethod.toString());
1676             } catch (ClassNotFoundException ex) {
1677                 throw new RuntimeException(ex);
1678             }
1679 
1680             try {
1681                 createEditorIfNeeded();
1682                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1683             } catch (InstantiationException ex) {
1684                 throw new RuntimeException(ex);
1685             } catch (IllegalAccessException ex) {
1686                 throw new RuntimeException(ex);
1687             }
1688             try {
1689                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1690                         ? inputType
1691                         : mEditor.mKeyListener.getInputType();
1692             } catch (IncompatibleClassChangeError e) {
1693                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1694             }
1695         } else if (digits != null) {
1696             createEditorIfNeeded();
1697             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1698             // If no input type was specified, we will default to generic
1699             // text, since we can't tell the IME about the set of digits
1700             // that was selected.
1701             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1702                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1703         } else if (inputType != EditorInfo.TYPE_NULL) {
1704             setInputType(inputType, true);
1705             // If set, the input type overrides what was set using the deprecated singleLine flag.
1706             singleLine = !isMultilineInputType(inputType);
1707         } else if (phone) {
1708             createEditorIfNeeded();
1709             mEditor.mKeyListener = DialerKeyListener.getInstance();
1710             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1711         } else if (numeric != 0) {
1712             createEditorIfNeeded();
1713             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1714                     null,  // locale
1715                     (numeric & SIGNED) != 0,
1716                     (numeric & DECIMAL) != 0);
1717             inputType = mEditor.mKeyListener.getInputType();
1718             mEditor.mInputType = inputType;
1719         } else if (autotext || autocap != -1) {
1720             TextKeyListener.Capitalize cap;
1721 
1722             inputType = EditorInfo.TYPE_CLASS_TEXT;
1723 
1724             switch (autocap) {
1725                 case 1:
1726                     cap = TextKeyListener.Capitalize.SENTENCES;
1727                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1728                     break;
1729 
1730                 case 2:
1731                     cap = TextKeyListener.Capitalize.WORDS;
1732                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1733                     break;
1734 
1735                 case 3:
1736                     cap = TextKeyListener.Capitalize.CHARACTERS;
1737                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1738                     break;
1739 
1740                 default:
1741                     cap = TextKeyListener.Capitalize.NONE;
1742                     break;
1743             }
1744 
1745             createEditorIfNeeded();
1746             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1747             mEditor.mInputType = inputType;
1748         } else if (editable) {
1749             createEditorIfNeeded();
1750             mEditor.mKeyListener = TextKeyListener.getInstance();
1751             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1752         } else if (isTextSelectable()) {
1753             // Prevent text changes from keyboard.
1754             if (mEditor != null) {
1755                 mEditor.mKeyListener = null;
1756                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1757             }
1758             bufferType = BufferType.SPANNABLE;
1759             // So that selection can be changed using arrow keys and touch is handled.
1760             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1761         } else {
1762             if (mEditor != null) mEditor.mKeyListener = null;
1763 
1764             switch (buffertype) {
1765                 case 0:
1766                     bufferType = BufferType.NORMAL;
1767                     break;
1768                 case 1:
1769                     bufferType = BufferType.SPANNABLE;
1770                     break;
1771                 case 2:
1772                     bufferType = BufferType.EDITABLE;
1773                     break;
1774             }
1775         }
1776 
1777         if (mEditor != null) {
1778             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1779                     numberPasswordInputType);
1780         }
1781 
1782         if (selectallonfocus) {
1783             createEditorIfNeeded();
1784             mEditor.mSelectAllOnFocus = true;
1785 
1786             if (bufferType == BufferType.NORMAL) {
1787                 bufferType = BufferType.SPANNABLE;
1788             }
1789         }
1790 
1791         // Set up the tint (if needed) before setting the drawables so that it
1792         // gets applied correctly.
1793         if (drawableTint != null || drawableTintMode != null) {
1794             if (mDrawables == null) {
1795                 mDrawables = new Drawables(context);
1796             }
1797             if (drawableTint != null) {
1798                 mDrawables.mTintList = drawableTint;
1799                 mDrawables.mHasTint = true;
1800             }
1801             if (drawableTintMode != null) {
1802                 mDrawables.mBlendMode = drawableTintMode;
1803                 mDrawables.mHasTintMode = true;
1804             }
1805         }
1806 
1807         // This call will save the initial left/right drawables
1808         setCompoundDrawablesWithIntrinsicBounds(
1809                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1810         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1811         setCompoundDrawablePadding(drawablePadding);
1812 
1813         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1814         // of lines of height are unchanged for multi-line TextViews.
1815         setInputTypeSingleLine(singleLine);
1816         applySingleLine(singleLine, singleLine, singleLine,
1817                 // Does not apply automated max length filter since length filter will be resolved
1818                 // later in this function.
1819                 false
1820         );
1821 
1822         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1823             ellipsize = ELLIPSIZE_END;
1824         }
1825 
1826         switch (ellipsize) {
1827             case ELLIPSIZE_START:
1828                 setEllipsize(TextUtils.TruncateAt.START);
1829                 break;
1830             case ELLIPSIZE_MIDDLE:
1831                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1832                 break;
1833             case ELLIPSIZE_END:
1834                 setEllipsize(TextUtils.TruncateAt.END);
1835                 break;
1836             case ELLIPSIZE_MARQUEE:
1837                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1838                     setHorizontalFadingEdgeEnabled(true);
1839                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1840                 } else {
1841                     setHorizontalFadingEdgeEnabled(false);
1842                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1843                 }
1844                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1845                 break;
1846         }
1847 
1848         final boolean isPassword = password || passwordInputType || webPasswordInputType
1849                 || numberPasswordInputType;
1850         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1851                 && (mEditor.mInputType
1852                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1853                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1854         if (isMonospaceEnforced) {
1855             attributes.mTypefaceIndex = MONOSPACE;
1856         }
1857 
1858         mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment;
1859         applyTextAppearance(attributes);
1860 
1861         if (isPassword) {
1862             setTransformationMethod(PasswordTransformationMethod.getInstance());
1863         }
1864 
1865         // For addressing b/145128646
1866         // For the performance reason, we limit characters for single line text field.
1867         if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) {
1868             mSingleLineLengthFilter = new InputFilter.LengthFilter(
1869                 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
1870         }
1871 
1872         if (mSingleLineLengthFilter != null) {
1873             setFilters(new InputFilter[] { mSingleLineLengthFilter });
1874         } else if (maxlength >= 0) {
1875             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1876         } else {
1877             setFilters(NO_FILTERS);
1878         }
1879 
1880         setText(text, bufferType);
1881         if (mText == null) {
1882             mText = "";
1883         }
1884         if (mTransformed == null) {
1885             mTransformed = "";
1886         }
1887 
1888         if (textIsSetFromXml) {
1889             mTextSetFromXmlOrResourceId = true;
1890         }
1891 
1892         if (hint != null) setHint(hint);
1893 
1894         /*
1895          * Views are not normally clickable unless specified to be.
1896          * However, TextViews that have input or movement methods *are*
1897          * clickable by default. By setting clickable here, we implicitly set focusable as well
1898          * if not overridden by the developer.
1899          */
1900         a = context.obtainStyledAttributes(
1901                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1902         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1903         boolean clickable = canInputOrMove || isClickable();
1904         boolean longClickable = canInputOrMove || isLongClickable();
1905         int focusable = getFocusable();
1906         boolean isAutoHandwritingEnabled = true;
1907 
1908         n = a.getIndexCount();
1909         for (int i = 0; i < n; i++) {
1910             int attr = a.getIndex(i);
1911 
1912             switch (attr) {
1913                 case com.android.internal.R.styleable.View_focusable:
1914                     TypedValue val = new TypedValue();
1915                     if (a.getValue(attr, val)) {
1916                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1917                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1918                                 : val.data;
1919                     }
1920                     break;
1921 
1922                 case com.android.internal.R.styleable.View_clickable:
1923                     clickable = a.getBoolean(attr, clickable);
1924                     break;
1925 
1926                 case com.android.internal.R.styleable.View_longClickable:
1927                     longClickable = a.getBoolean(attr, longClickable);
1928                     break;
1929 
1930                 case com.android.internal.R.styleable.View_autoHandwritingEnabled:
1931                     isAutoHandwritingEnabled = a.getBoolean(attr, true);
1932                     break;
1933             }
1934         }
1935         a.recycle();
1936 
1937         // Some apps were relying on the undefined behavior of focusable winning over
1938         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1939         // when starting with EditText and setting only focusable=false). To keep those apps from
1940         // breaking, re-apply the focusable attribute here.
1941         if (focusable != getFocusable()) {
1942             setFocusable(focusable);
1943         }
1944         setClickable(clickable);
1945         setLongClickable(longClickable);
1946         setAutoHandwritingEnabled(isAutoHandwritingEnabled);
1947 
1948         if (mEditor != null) mEditor.prepareCursorControllers();
1949 
1950         // If not explicitly specified this view is important for accessibility.
1951         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1952             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1953         }
1954 
1955         if (supportsAutoSizeText()) {
1956             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1957                 // If uniform auto-size has been specified but preset values have not been set then
1958                 // replace the auto-size configuration values that have not been specified with the
1959                 // defaults.
1960                 if (!mHasPresetAutoSizeValues) {
1961                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1962 
1963                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1964                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1965                                 TypedValue.COMPLEX_UNIT_SP,
1966                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1967                                 displayMetrics);
1968                     }
1969 
1970                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1971                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1972                                 TypedValue.COMPLEX_UNIT_SP,
1973                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1974                                 displayMetrics);
1975                     }
1976 
1977                     if (autoSizeStepGranularityInPx
1978                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1979                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1980                     }
1981 
1982                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1983                             autoSizeMaxTextSizeInPx,
1984                             autoSizeStepGranularityInPx);
1985                 }
1986 
1987                 setupAutoSizeText();
1988             }
1989         } else {
1990             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1991         }
1992 
1993         if (firstBaselineToTopHeight >= 0) {
1994             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1995         }
1996         if (lastBaselineToBottomHeight >= 0) {
1997             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1998         }
1999         if (lineHeight >= 0) {
2000             if (lineHeightUnit == -1) {
2001                 setLineHeightPx(lineHeight);
2002             } else {
2003                 setLineHeight(lineHeightUnit, lineHeight);
2004             }
2005         }
2006     }
2007 
2008     // Update mText and mPrecomputed
2009     private void setTextInternal(@Nullable CharSequence text) {
2010         mText = text;
2011         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
2012         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
2013     }
2014 
2015     /**
2016      * Specify whether this widget should automatically scale the text to try to perfectly fit
2017      * within the layout bounds by using the default auto-size configuration.
2018      *
2019      * @param autoSizeTextType the type of auto-size. Must be one of
2020      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
2021      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
2022      *
2023      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
2024      *
2025      * @attr ref android.R.styleable#TextView_autoSizeTextType
2026      *
2027      * @see #getAutoSizeTextType()
2028      */
2029     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
2030         if (supportsAutoSizeText()) {
2031             switch (autoSizeTextType) {
2032                 case AUTO_SIZE_TEXT_TYPE_NONE:
2033                     clearAutoSizeConfiguration();
2034                     break;
2035                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
2036                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2037                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
2038                             TypedValue.COMPLEX_UNIT_SP,
2039                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
2040                             displayMetrics);
2041                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
2042                             TypedValue.COMPLEX_UNIT_SP,
2043                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
2044                             displayMetrics);
2045 
2046                     validateAndSetAutoSizeTextTypeUniformConfiguration(
2047                             autoSizeMinTextSizeInPx,
2048                             autoSizeMaxTextSizeInPx,
2049                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
2050                     if (setupAutoSizeText()) {
2051                         autoSizeText();
2052                         invalidate();
2053                     }
2054                     break;
2055                 default:
2056                     throw new IllegalArgumentException(
2057                             "Unknown auto-size text type: " + autoSizeTextType);
2058             }
2059         }
2060     }
2061 
2062     /**
2063      * Specify whether this widget should automatically scale the text to try to perfectly fit
2064      * within the layout bounds. If all the configuration params are valid the type of auto-size is
2065      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
2066      *
2067      * @param autoSizeMinTextSize the minimum text size available for auto-size
2068      * @param autoSizeMaxTextSize the maximum text size available for auto-size
2069      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
2070      *                                the minimum and maximum text size in order to build the set of
2071      *                                text sizes the system uses to choose from when auto-sizing
2072      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
2073      *             possible dimension units
2074      *
2075      * @throws IllegalArgumentException if any of the configuration params are invalid.
2076      *
2077      * @attr ref android.R.styleable#TextView_autoSizeTextType
2078      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2079      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2080      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
2081      *
2082      * @see #setAutoSizeTextTypeWithDefaults(int)
2083      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2084      * @see #getAutoSizeMinTextSize()
2085      * @see #getAutoSizeMaxTextSize()
2086      * @see #getAutoSizeStepGranularity()
2087      * @see #getAutoSizeTextAvailableSizes()
2088      */
2089     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
2090             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
2091         if (supportsAutoSizeText()) {
2092             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2093             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
2094                     unit, autoSizeMinTextSize, displayMetrics);
2095             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
2096                     unit, autoSizeMaxTextSize, displayMetrics);
2097             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
2098                     unit, autoSizeStepGranularity, displayMetrics);
2099 
2100             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
2101                     autoSizeMaxTextSizeInPx,
2102                     autoSizeStepGranularityInPx);
2103 
2104             if (setupAutoSizeText()) {
2105                 autoSizeText();
2106                 invalidate();
2107             }
2108         }
2109     }
2110 
2111     /**
2112      * Specify whether this widget should automatically scale the text to try to perfectly fit
2113      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
2114      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
2115      *
2116      * @param presetSizes an {@code int} array of sizes in pixels
2117      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
2118      *             the possible dimension units
2119      *
2120      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
2121      *
2122      * @attr ref android.R.styleable#TextView_autoSizeTextType
2123      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
2124      *
2125      * @see #setAutoSizeTextTypeWithDefaults(int)
2126      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2127      * @see #getAutoSizeMinTextSize()
2128      * @see #getAutoSizeMaxTextSize()
2129      * @see #getAutoSizeTextAvailableSizes()
2130      */
2131     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
2132         if (supportsAutoSizeText()) {
2133             final int presetSizesLength = presetSizes.length;
2134             if (presetSizesLength > 0) {
2135                 int[] presetSizesInPx = new int[presetSizesLength];
2136 
2137                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
2138                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
2139                 } else {
2140                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2141                     // Convert all to sizes to pixels.
2142                     for (int i = 0; i < presetSizesLength; i++) {
2143                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
2144                             presetSizes[i], displayMetrics));
2145                     }
2146                 }
2147 
2148                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
2149                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
2150                     throw new IllegalArgumentException("None of the preset sizes is valid: "
2151                             + Arrays.toString(presetSizes));
2152                 }
2153             } else {
2154                 mHasPresetAutoSizeValues = false;
2155             }
2156 
2157             if (setupAutoSizeText()) {
2158                 autoSizeText();
2159                 invalidate();
2160             }
2161         }
2162     }
2163 
2164     /**
2165      * Returns the type of auto-size set for this widget.
2166      *
2167      * @return an {@code int} corresponding to one of the auto-size types:
2168      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
2169      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
2170      *
2171      * @attr ref android.R.styleable#TextView_autoSizeTextType
2172      *
2173      * @see #setAutoSizeTextTypeWithDefaults(int)
2174      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2175      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2176      */
2177     @InspectableProperty(enumMapping = {
2178             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
2179             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
2180     })
2181     @AutoSizeTextType
2182     public int getAutoSizeTextType() {
2183         return mAutoSizeTextType;
2184     }
2185 
2186     /**
2187      * @return the current auto-size step granularity in pixels.
2188      *
2189      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
2190      *
2191      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2192      */
2193     @InspectableProperty
2194     public int getAutoSizeStepGranularity() {
2195         return Math.round(mAutoSizeStepGranularityInPx);
2196     }
2197 
2198     /**
2199      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
2200      *         if auto-size has not been configured this function returns {@code -1}.
2201      *
2202      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2203      *
2204      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2205      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2206      */
2207     @InspectableProperty
2208     public int getAutoSizeMinTextSize() {
2209         return Math.round(mAutoSizeMinTextSizeInPx);
2210     }
2211 
2212     /**
2213      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
2214      *         if auto-size has not been configured this function returns {@code -1}.
2215      *
2216      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2217      *
2218      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2219      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2220      */
2221     @InspectableProperty
2222     public int getAutoSizeMaxTextSize() {
2223         return Math.round(mAutoSizeMaxTextSizeInPx);
2224     }
2225 
2226     /**
2227      * @return the current auto-size {@code int} sizes array (in pixels).
2228      *
2229      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2230      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2231      */
2232     public int[] getAutoSizeTextAvailableSizes() {
2233         return mAutoSizeTextSizesInPx;
2234     }
2235 
2236     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
2237         final int textSizesLength = textSizes.length();
2238         final int[] parsedSizes = new int[textSizesLength];
2239 
2240         if (textSizesLength > 0) {
2241             for (int i = 0; i < textSizesLength; i++) {
2242                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
2243             }
2244             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
2245             setupAutoSizeUniformPresetSizesConfiguration();
2246         }
2247     }
2248 
2249     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
2250         final int sizesLength = mAutoSizeTextSizesInPx.length;
2251         mHasPresetAutoSizeValues = sizesLength > 0;
2252         if (mHasPresetAutoSizeValues) {
2253             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2254             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
2255             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
2256             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2257         }
2258         return mHasPresetAutoSizeValues;
2259     }
2260 
2261     /**
2262      * If all params are valid then save the auto-size configuration.
2263      *
2264      * @throws IllegalArgumentException if any of the params are invalid
2265      */
2266     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
2267             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
2268         // First validate.
2269         if (autoSizeMinTextSizeInPx <= 0) {
2270             throw new IllegalArgumentException("Minimum auto-size text size ("
2271                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
2272         }
2273 
2274         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2275             throw new IllegalArgumentException("Maximum auto-size text size ("
2276                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2277                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2278         }
2279 
2280         if (autoSizeStepGranularityInPx <= 0) {
2281             throw new IllegalArgumentException("The auto-size step granularity ("
2282                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2283         }
2284 
2285         // All good, persist the configuration.
2286         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2287         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2288         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2289         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2290         mHasPresetAutoSizeValues = false;
2291     }
2292 
2293     private void clearAutoSizeConfiguration() {
2294         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2295         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2296         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2297         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2298         mAutoSizeTextSizesInPx = EmptyArray.INT;
2299         mNeedsAutoSizeText = false;
2300     }
2301 
2302     // Returns distinct sorted positive values.
2303     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2304         final int presetValuesLength = presetValues.length;
2305         if (presetValuesLength == 0) {
2306             return presetValues;
2307         }
2308         Arrays.sort(presetValues);
2309 
2310         final IntArray uniqueValidSizes = new IntArray();
2311         for (int i = 0; i < presetValuesLength; i++) {
2312             final int currentPresetValue = presetValues[i];
2313 
2314             if (currentPresetValue > 0
2315                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2316                 uniqueValidSizes.add(currentPresetValue);
2317             }
2318         }
2319 
2320         return presetValuesLength == uniqueValidSizes.size()
2321             ? presetValues
2322             : uniqueValidSizes.toArray();
2323     }
2324 
2325     private boolean setupAutoSizeText() {
2326         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2327             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2328             // not have a predefined set of sizes or if the current sizes array is empty.
2329             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2330                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2331                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2332                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2333                 for (int i = 0; i < autoSizeValuesLength; i++) {
2334                     autoSizeTextSizesInPx[i] = Math.round(
2335                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2336                 }
2337                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2338             }
2339 
2340             mNeedsAutoSizeText = true;
2341         } else {
2342             mNeedsAutoSizeText = false;
2343         }
2344 
2345         return mNeedsAutoSizeText;
2346     }
2347 
2348     private int[] parseDimensionArray(TypedArray dimens) {
2349         if (dimens == null) {
2350             return null;
2351         }
2352         int[] result = new int[dimens.length()];
2353         for (int i = 0; i < result.length; i++) {
2354             result[i] = dimens.getDimensionPixelSize(i, 0);
2355         }
2356         return result;
2357     }
2358 
2359     /**
2360      * @hide
2361      */
2362     @TestApi
2363     @Override
2364     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
2365         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2366             if (resultCode == Activity.RESULT_OK && data != null) {
2367                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2368                 if (result != null) {
2369                     if (isTextEditable()) {
2370                         ClipData clip = ClipData.newPlainText("", result);
2371                         ContentInfo payload =
2372                                 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build();
2373                         performReceiveContent(payload);
2374                         if (mEditor != null) {
2375                             mEditor.refreshTextActionMode();
2376                         }
2377                     } else {
2378                         if (result.length() > 0) {
2379                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2380                                 .show();
2381                         }
2382                     }
2383                 }
2384             } else if (mSpannable != null) {
2385                 // Reset the selection.
2386                 Selection.setSelection(mSpannable, getSelectionEnd());
2387             }
2388         }
2389     }
2390 
2391     /**
2392      * Sets the Typeface taking into account the given attributes.
2393      *
2394      * @param typeface a typeface
2395      * @param familyName family name string, e.g. "serif"
2396      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2397      * @param style a typeface style
2398      * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED}
2399      *               if not specified.
2400      */
2401     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2402             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2403             @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
2404                     int weight) {
2405         if (typeface == null && familyName != null) {
2406             // Lookup normal Typeface from system font map.
2407             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2408             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2409         } else if (typeface != null) {
2410             resolveStyleAndSetTypeface(typeface, style, weight);
2411         } else {  // both typeface and familyName is null.
2412             switch (typefaceIndex) {
2413                 case SANS:
2414                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2415                     break;
2416                 case SERIF:
2417                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2418                     break;
2419                 case MONOSPACE:
2420                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2421                     break;
2422                 case DEFAULT_TYPEFACE:
2423                 default:
2424                     resolveStyleAndSetTypeface(null, style, weight);
2425                     break;
2426             }
2427         }
2428     }
2429 
2430     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2431             @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
2432                     int weight) {
2433         if (weight >= 0) {
2434             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2435             final boolean italic = (style & Typeface.ITALIC) != 0;
2436             setTypeface(Typeface.create(typeface, weight, italic));
2437         } else {
2438             setTypeface(typeface, style);
2439         }
2440     }
2441 
2442     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2443         boolean hasRelativeDrawables = (start != null) || (end != null);
2444         if (hasRelativeDrawables) {
2445             Drawables dr = mDrawables;
2446             if (dr == null) {
2447                 mDrawables = dr = new Drawables(getContext());
2448             }
2449             mDrawables.mOverride = true;
2450             final Rect compoundRect = dr.mCompoundRect;
2451             int[] state = getDrawableState();
2452             if (start != null) {
2453                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2454                 start.setState(state);
2455                 start.copyBounds(compoundRect);
2456                 start.setCallback(this);
2457 
2458                 dr.mDrawableStart = start;
2459                 dr.mDrawableSizeStart = compoundRect.width();
2460                 dr.mDrawableHeightStart = compoundRect.height();
2461             } else {
2462                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2463             }
2464             if (end != null) {
2465                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2466                 end.setState(state);
2467                 end.copyBounds(compoundRect);
2468                 end.setCallback(this);
2469 
2470                 dr.mDrawableEnd = end;
2471                 dr.mDrawableSizeEnd = compoundRect.width();
2472                 dr.mDrawableHeightEnd = compoundRect.height();
2473             } else {
2474                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2475             }
2476             resetResolvedDrawables();
2477             resolveDrawables();
2478             applyCompoundDrawableTint();
2479         }
2480     }
2481 
2482     @android.view.RemotableViewMethod
2483     @Override
2484     public void setEnabled(boolean enabled) {
2485         if (enabled == isEnabled()) {
2486             return;
2487         }
2488 
2489         if (!enabled) {
2490             // Hide the soft input if the currently active TextView is disabled
2491             InputMethodManager imm = getInputMethodManager();
2492             if (imm != null) {
2493                 imm.hideSoftInputFromView(this, 0);
2494             }
2495         }
2496 
2497         super.setEnabled(enabled);
2498 
2499         if (enabled) {
2500             // Make sure IME is updated with current editor info.
2501             InputMethodManager imm = getInputMethodManager();
2502             if (imm != null) imm.restartInput(this);
2503         }
2504 
2505         // Will change text color
2506         if (mEditor != null) {
2507             mEditor.invalidateTextDisplayList();
2508             mEditor.prepareCursorControllers();
2509 
2510             // start or stop the cursor blinking as appropriate
2511             mEditor.makeBlink();
2512         }
2513     }
2514 
2515     /**
2516      * Sets the typeface and style in which the text should be displayed,
2517      * and turns on the fake bold and italic bits in the Paint if the
2518      * Typeface that you provided does not have all the bits in the
2519      * style that you specified.
2520      *
2521      * @attr ref android.R.styleable#TextView_typeface
2522      * @attr ref android.R.styleable#TextView_textStyle
2523      */
2524     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2525         if (style > 0) {
2526             if (tf == null) {
2527                 tf = Typeface.defaultFromStyle(style);
2528             } else {
2529                 tf = Typeface.create(tf, style);
2530             }
2531 
2532             setTypeface(tf);
2533             // now compute what (if any) algorithmic styling is needed
2534             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2535             int need = style & ~typefaceStyle;
2536             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2537             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2538         } else {
2539             mTextPaint.setFakeBoldText(false);
2540             mTextPaint.setTextSkewX(0);
2541             setTypeface(tf);
2542         }
2543     }
2544 
2545     /**
2546      * Subclasses override this to specify that they have a KeyListener
2547      * by default even if not specifically called for in the XML options.
2548      */
2549     protected boolean getDefaultEditable() {
2550         return false;
2551     }
2552 
2553     /**
2554      * Subclasses override this to specify a default movement method.
2555      */
2556     protected MovementMethod getDefaultMovementMethod() {
2557         return null;
2558     }
2559 
2560     /**
2561      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2562      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2563      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2564      * the return value from this method to Spannable or Editable, respectively.
2565      *
2566      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2567      * should make your own copy first.</p>
2568      *
2569      * @return The text displayed by the text view.
2570      * @attr ref android.R.styleable#TextView_text
2571      */
2572     @ViewDebug.CapturedViewProperty
2573     @InspectableProperty
2574     public CharSequence getText() {
2575         if (mUseTextPaddingForUiTranslation) {
2576             ViewTranslationCallback callback = getViewTranslationCallback();
2577             if (callback != null && callback instanceof TextViewTranslationCallback) {
2578                 TextViewTranslationCallback defaultCallback =
2579                         (TextViewTranslationCallback) callback;
2580                 if (defaultCallback.isTextPaddingEnabled()
2581                         && defaultCallback.isShowingTranslation()) {
2582                     return defaultCallback.getPaddedText(mText, mTransformed);
2583                 }
2584             }
2585         }
2586         return mText;
2587     }
2588 
2589     /**
2590      * Returns the length, in characters, of the text managed by this TextView
2591      * @return The length of the text managed by the TextView in characters.
2592      */
2593     public int length() {
2594         return mText.length();
2595     }
2596 
2597     /**
2598      * Return the text that TextView is displaying as an Editable object. If the text is not
2599      * editable, null is returned.
2600      *
2601      * @see #getText
2602      */
2603     public Editable getEditableText() {
2604         return (mText instanceof Editable) ? (Editable) mText : null;
2605     }
2606 
2607     /**
2608      * @hide
2609      */
2610     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2611     public CharSequence getTransformed() {
2612         return mTransformed;
2613     }
2614 
2615     /**
2616      * Gets the vertical distance between lines of text, in pixels.
2617      * Note that markup within the text can cause individual lines
2618      * to be taller or shorter than this height, and the layout may
2619      * contain additional first-or last-line padding.
2620      * @return The height of one standard line in pixels.
2621      */
2622     @InspectableProperty
2623     public int getLineHeight() {
2624         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2625     }
2626 
2627     /**
2628      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2629      * This value can be null if the text or width has recently changed.
2630      * @return The Layout that is currently being used to display the text.
2631      */
2632     public final Layout getLayout() {
2633         return mLayout;
2634     }
2635 
2636     /**
2637      * @return the {@link android.text.Layout} that is currently being used to
2638      * display the hint text. This can be null.
2639      */
2640     @UnsupportedAppUsage
2641     final Layout getHintLayout() {
2642         return mHintLayout;
2643     }
2644 
2645     /**
2646      * Retrieve the {@link android.content.UndoManager} that is currently associated
2647      * with this TextView.  By default there is no associated UndoManager, so null
2648      * is returned.  One can be associated with the TextView through
2649      * {@link #setUndoManager(android.content.UndoManager, String)}
2650      *
2651      * @hide
2652      */
2653     public final UndoManager getUndoManager() {
2654         // TODO: Consider supporting a global undo manager.
2655         throw new UnsupportedOperationException("not implemented");
2656     }
2657 
2658 
2659     /**
2660      * @hide
2661      */
2662     @VisibleForTesting
2663     public final Editor getEditorForTesting() {
2664         return mEditor;
2665     }
2666 
2667     /**
2668      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2669      * done, all edit operations on the TextView will result in appropriate
2670      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2671      * stack.
2672      *
2673      * @param undoManager The {@link android.content.UndoManager} to associate with
2674      * this TextView, or null to clear any existing association.
2675      * @param tag String tag identifying this particular TextView owner in the
2676      * UndoManager.  This is used to keep the correct association with the
2677      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2678      *
2679      * @hide
2680      */
2681     public final void setUndoManager(UndoManager undoManager, String tag) {
2682         // TODO: Consider supporting a global undo manager. An implementation will need to:
2683         // * createEditorIfNeeded()
2684         // * Promote to BufferType.EDITABLE if needed.
2685         // * Update the UndoManager and UndoOwner.
2686         // Likewise it will need to be able to restore the default UndoManager.
2687         throw new UnsupportedOperationException("not implemented");
2688     }
2689 
2690     /**
2691      * Gets the current {@link KeyListener} for the TextView.
2692      * This will frequently be null for non-EditText TextViews.
2693      * @return the current key listener for this TextView.
2694      *
2695      * @attr ref android.R.styleable#TextView_numeric
2696      * @attr ref android.R.styleable#TextView_digits
2697      * @attr ref android.R.styleable#TextView_phoneNumber
2698      * @attr ref android.R.styleable#TextView_inputMethod
2699      * @attr ref android.R.styleable#TextView_capitalize
2700      * @attr ref android.R.styleable#TextView_autoText
2701      */
2702     public final KeyListener getKeyListener() {
2703         return mEditor == null ? null : mEditor.mKeyListener;
2704     }
2705 
2706     /**
2707      * Sets the key listener to be used with this TextView.  This can be null
2708      * to disallow user input.  Note that this method has significant and
2709      * subtle interactions with soft keyboards and other input method:
2710      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2711      * for important details.  Calling this method will replace the current
2712      * content type of the text view with the content type returned by the
2713      * key listener.
2714      * <p>
2715      * Be warned that if you want a TextView with a key listener or movement
2716      * method not to be focusable, or if you want a TextView without a
2717      * key listener or movement method to be focusable, you must call
2718      * {@link #setFocusable} again after calling this to get the focusability
2719      * back the way you want it.
2720      *
2721      * @attr ref android.R.styleable#TextView_numeric
2722      * @attr ref android.R.styleable#TextView_digits
2723      * @attr ref android.R.styleable#TextView_phoneNumber
2724      * @attr ref android.R.styleable#TextView_inputMethod
2725      * @attr ref android.R.styleable#TextView_capitalize
2726      * @attr ref android.R.styleable#TextView_autoText
2727      */
2728     public void setKeyListener(KeyListener input) {
2729         mListenerChanged = true;
2730         setKeyListenerOnly(input);
2731         fixFocusableAndClickableSettings();
2732 
2733         if (input != null) {
2734             createEditorIfNeeded();
2735             setInputTypeFromEditor();
2736         } else {
2737             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2738         }
2739 
2740         InputMethodManager imm = getInputMethodManager();
2741         if (imm != null) imm.restartInput(this);
2742     }
2743 
2744     private void setInputTypeFromEditor() {
2745         try {
2746             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2747         } catch (IncompatibleClassChangeError e) {
2748             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2749         }
2750         // Change inputType, without affecting transformation.
2751         // No need to applySingleLine since mSingleLine is unchanged.
2752         setInputTypeSingleLine(mSingleLine);
2753     }
2754 
2755     private void setKeyListenerOnly(KeyListener input) {
2756         if (mEditor == null && input == null) return; // null is the default value
2757 
2758         createEditorIfNeeded();
2759         if (mEditor.mKeyListener != input) {
2760             mEditor.mKeyListener = input;
2761             if (input != null && !(mText instanceof Editable)) {
2762                 setText(mText);
2763             }
2764 
2765             setFilters((Editable) mText, mFilters);
2766         }
2767     }
2768 
2769     /**
2770      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2771      * which provides positioning, scrolling, and text selection functionality.
2772      * This will frequently be null for non-EditText TextViews.
2773      * @return the movement method being used for this TextView.
2774      * @see android.text.method.MovementMethod
2775      */
2776     public final MovementMethod getMovementMethod() {
2777         return mMovement;
2778     }
2779 
2780     /**
2781      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2782      * for this TextView. This can be null to disallow using the arrow keys to move the
2783      * cursor or scroll the view.
2784      * <p>
2785      * Be warned that if you want a TextView with a key listener or movement
2786      * method not to be focusable, or if you want a TextView without a
2787      * key listener or movement method to be focusable, you must call
2788      * {@link #setFocusable} again after calling this to get the focusability
2789      * back the way you want it.
2790      */
2791     public final void setMovementMethod(MovementMethod movement) {
2792         if (mMovement != movement) {
2793             mMovement = movement;
2794 
2795             if (movement != null && mSpannable == null) {
2796                 setText(mText);
2797             }
2798 
2799             fixFocusableAndClickableSettings();
2800 
2801             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2802             // mMovement
2803             if (mEditor != null) mEditor.prepareCursorControllers();
2804         }
2805     }
2806 
2807     private void fixFocusableAndClickableSettings() {
2808         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2809             setFocusable(FOCUSABLE);
2810             setClickable(true);
2811             setLongClickable(true);
2812         } else {
2813             setFocusable(FOCUSABLE_AUTO);
2814             setClickable(false);
2815             setLongClickable(false);
2816         }
2817     }
2818 
2819     /**
2820      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2821      * This is frequently null, except for single-line and password fields.
2822      * @return the current transformation method for this TextView.
2823      *
2824      * @attr ref android.R.styleable#TextView_password
2825      * @attr ref android.R.styleable#TextView_singleLine
2826      */
2827     public final TransformationMethod getTransformationMethod() {
2828         return mTransformation;
2829     }
2830 
2831     /**
2832      * Sets the transformation that is applied to the text that this
2833      * TextView is displaying.
2834      *
2835      * @attr ref android.R.styleable#TextView_password
2836      * @attr ref android.R.styleable#TextView_singleLine
2837      */
2838     public final void setTransformationMethod(TransformationMethod method) {
2839         if (mEditor != null) {
2840             mEditor.setTransformationMethod(method);
2841         } else {
2842             setTransformationMethodInternal(method, /* updateText */ true);
2843         }
2844     }
2845 
2846     /**
2847      * Set the transformation that is applied to the text that this TextView is displaying,
2848      * optionally call the setText.
2849      * @param method the new transformation method to be set.
2850      * @param updateText whether the call {@link #setText} which will update the TextView to display
2851      *                   the new content. This method is helpful when updating
2852      *                   {@link TransformationMethod} inside {@link #setText}. It should only be
2853      *                   false if text will be updated immediately after this call, otherwise the
2854      *                   TextView will enter an inconsistent state.
2855      */
2856     void setTransformationMethodInternal(@Nullable TransformationMethod method,
2857             boolean updateText) {
2858         if (method == mTransformation) {
2859             // Avoid the setText() below if the transformation is
2860             // the same.
2861             return;
2862         }
2863         if (mTransformation != null) {
2864             if (mSpannable != null) {
2865                 mSpannable.removeSpan(mTransformation);
2866             }
2867         }
2868 
2869         mTransformation = method;
2870 
2871         if (method instanceof TransformationMethod2) {
2872             TransformationMethod2 method2 = (TransformationMethod2) method;
2873             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2874             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2875         } else {
2876             mAllowTransformationLengthChange = false;
2877         }
2878 
2879         if (updateText) {
2880             if (Flags.insertModeNotUpdateSelection()) {
2881                 // Update the transformation text.
2882                 if (mTransformation == null) {
2883                     mTransformed = mText;
2884                 } else {
2885                     mTransformed = mTransformation.getTransformation(mText, this);
2886                 }
2887                 if (mTransformed == null) {
2888                     // Should not happen if the transformation method follows the non-null
2889                     // postcondition.
2890                     mTransformed = "";
2891                 }
2892                 final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
2893 
2894                 // If the mText is a Spannable and the new TransformationMethod needs to listen to
2895                 // its updates, apply the watcher on it.
2896                 if (mTransformation != null && mText instanceof Spannable
2897                         && (!mAllowTransformationLengthChange || isOffsetMapping)) {
2898                     Spannable sp = (Spannable) mText;
2899                     final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
2900                     sp.setSpan(mTransformation, 0, mText.length(),
2901                             Spanned.SPAN_INCLUSIVE_INCLUSIVE
2902                                     | (priority << Spanned.SPAN_PRIORITY_SHIFT));
2903                 }
2904                 if (mLayout != null) {
2905                     nullLayouts();
2906                     requestLayout();
2907                     invalidate();
2908                 }
2909             } else {
2910                 setText(mText);
2911             }
2912         }
2913 
2914         if (hasPasswordTransformationMethod()) {
2915             notifyViewAccessibilityStateChangedIfNeeded(
2916                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2917         }
2918 
2919         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2920         // getTextDirectionHeuristic, needs reset
2921         mTextDir = getTextDirectionHeuristic();
2922     }
2923 
2924     /**
2925      * Returns the top padding of the view, plus space for the top
2926      * Drawable if any.
2927      */
2928     public int getCompoundPaddingTop() {
2929         final Drawables dr = mDrawables;
2930         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2931             return mPaddingTop;
2932         } else {
2933             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2934         }
2935     }
2936 
2937     /**
2938      * Returns the bottom padding of the view, plus space for the bottom
2939      * Drawable if any.
2940      */
2941     public int getCompoundPaddingBottom() {
2942         final Drawables dr = mDrawables;
2943         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2944             return mPaddingBottom;
2945         } else {
2946             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2947         }
2948     }
2949 
2950     /**
2951      * Returns the left padding of the view, plus space for the left
2952      * Drawable if any.
2953      */
2954     public int getCompoundPaddingLeft() {
2955         final Drawables dr = mDrawables;
2956         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2957             return mPaddingLeft;
2958         } else {
2959             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2960         }
2961     }
2962 
2963     /**
2964      * Returns the right padding of the view, plus space for the right
2965      * Drawable if any.
2966      */
2967     public int getCompoundPaddingRight() {
2968         final Drawables dr = mDrawables;
2969         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2970             return mPaddingRight;
2971         } else {
2972             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2973         }
2974     }
2975 
2976     /**
2977      * Returns the start padding of the view, plus space for the start
2978      * Drawable if any.
2979      */
2980     public int getCompoundPaddingStart() {
2981         resolveDrawables();
2982         switch(getLayoutDirection()) {
2983             default:
2984             case LAYOUT_DIRECTION_LTR:
2985                 return getCompoundPaddingLeft();
2986             case LAYOUT_DIRECTION_RTL:
2987                 return getCompoundPaddingRight();
2988         }
2989     }
2990 
2991     /**
2992      * Returns the end padding of the view, plus space for the end
2993      * Drawable if any.
2994      */
2995     public int getCompoundPaddingEnd() {
2996         resolveDrawables();
2997         switch(getLayoutDirection()) {
2998             default:
2999             case LAYOUT_DIRECTION_LTR:
3000                 return getCompoundPaddingRight();
3001             case LAYOUT_DIRECTION_RTL:
3002                 return getCompoundPaddingLeft();
3003         }
3004     }
3005 
3006     /**
3007      * Returns the extended top padding of the view, including both the
3008      * top Drawable if any and any extra space to keep more than maxLines
3009      * of text from showing.  It is only valid to call this after measuring.
3010      */
3011     public int getExtendedPaddingTop() {
3012         if (mMaxMode != LINES) {
3013             return getCompoundPaddingTop();
3014         }
3015 
3016         if (mLayout == null) {
3017             assumeLayout();
3018         }
3019 
3020         if (mLayout.getLineCount() <= mMaximum) {
3021             return getCompoundPaddingTop();
3022         }
3023 
3024         int top = getCompoundPaddingTop();
3025         int bottom = getCompoundPaddingBottom();
3026         int viewht = getHeight() - top - bottom;
3027         int layoutht = mLayout.getLineTop(mMaximum);
3028 
3029         if (layoutht >= viewht) {
3030             return top;
3031         }
3032 
3033         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3034         if (gravity == Gravity.TOP) {
3035             return top;
3036         } else if (gravity == Gravity.BOTTOM) {
3037             return top + viewht - layoutht;
3038         } else { // (gravity == Gravity.CENTER_VERTICAL)
3039             return top + (viewht - layoutht) / 2;
3040         }
3041     }
3042 
3043     /**
3044      * Returns the extended bottom padding of the view, including both the
3045      * bottom Drawable if any and any extra space to keep more than maxLines
3046      * of text from showing.  It is only valid to call this after measuring.
3047      */
3048     public int getExtendedPaddingBottom() {
3049         if (mMaxMode != LINES) {
3050             return getCompoundPaddingBottom();
3051         }
3052 
3053         if (mLayout == null) {
3054             assumeLayout();
3055         }
3056 
3057         if (mLayout.getLineCount() <= mMaximum) {
3058             return getCompoundPaddingBottom();
3059         }
3060 
3061         int top = getCompoundPaddingTop();
3062         int bottom = getCompoundPaddingBottom();
3063         int viewht = getHeight() - top - bottom;
3064         int layoutht = mLayout.getLineTop(mMaximum);
3065 
3066         if (layoutht >= viewht) {
3067             return bottom;
3068         }
3069 
3070         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3071         if (gravity == Gravity.TOP) {
3072             return bottom + viewht - layoutht;
3073         } else if (gravity == Gravity.BOTTOM) {
3074             return bottom;
3075         } else { // (gravity == Gravity.CENTER_VERTICAL)
3076             return bottom + (viewht - layoutht) / 2;
3077         }
3078     }
3079 
3080     /**
3081      * Returns the total left padding of the view, including the left
3082      * Drawable if any.
3083      */
3084     public int getTotalPaddingLeft() {
3085         return getCompoundPaddingLeft();
3086     }
3087 
3088     /**
3089      * Returns the total right padding of the view, including the right
3090      * Drawable if any.
3091      */
3092     public int getTotalPaddingRight() {
3093         return getCompoundPaddingRight();
3094     }
3095 
3096     /**
3097      * Returns the total start padding of the view, including the start
3098      * Drawable if any.
3099      */
3100     public int getTotalPaddingStart() {
3101         return getCompoundPaddingStart();
3102     }
3103 
3104     /**
3105      * Returns the total end padding of the view, including the end
3106      * Drawable if any.
3107      */
3108     public int getTotalPaddingEnd() {
3109         return getCompoundPaddingEnd();
3110     }
3111 
3112     /**
3113      * Returns the total top padding of the view, including the top
3114      * Drawable if any, the extra space to keep more than maxLines
3115      * from showing, and the vertical offset for gravity, if any.
3116      */
3117     public int getTotalPaddingTop() {
3118         return getExtendedPaddingTop() + getVerticalOffset(true);
3119     }
3120 
3121     /**
3122      * Returns the total bottom padding of the view, including the bottom
3123      * Drawable if any, the extra space to keep more than maxLines
3124      * from showing, and the vertical offset for gravity, if any.
3125      */
3126     public int getTotalPaddingBottom() {
3127         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
3128     }
3129 
3130     /**
3131      * Sets the Drawables (if any) to appear to the left of, above, to the
3132      * right of, and below the text. Use {@code null} if you do not want a
3133      * Drawable there. The Drawables must already have had
3134      * {@link Drawable#setBounds} called.
3135      * <p>
3136      * Calling this method will overwrite any Drawables previously set using
3137      * {@link #setCompoundDrawablesRelative} or related methods.
3138      *
3139      * @attr ref android.R.styleable#TextView_drawableLeft
3140      * @attr ref android.R.styleable#TextView_drawableTop
3141      * @attr ref android.R.styleable#TextView_drawableRight
3142      * @attr ref android.R.styleable#TextView_drawableBottom
3143      */
3144     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
3145             @Nullable Drawable right, @Nullable Drawable bottom) {
3146         Drawables dr = mDrawables;
3147 
3148         // We're switching to absolute, discard relative.
3149         if (dr != null) {
3150             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3151             dr.mDrawableStart = null;
3152             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
3153             dr.mDrawableEnd = null;
3154             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3155             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3156         }
3157 
3158         final boolean drawables = left != null || top != null || right != null || bottom != null;
3159         if (!drawables) {
3160             // Clearing drawables...  can we free the data structure?
3161             if (dr != null) {
3162                 if (!dr.hasMetadata()) {
3163                     mDrawables = null;
3164                 } else {
3165                     // We need to retain the last set padding, so just clear
3166                     // out all of the fields in the existing structure.
3167                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
3168                         if (dr.mShowing[i] != null) {
3169                             dr.mShowing[i].setCallback(null);
3170                         }
3171                         dr.mShowing[i] = null;
3172                     }
3173                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3174                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3175                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3176                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3177                 }
3178             }
3179         } else {
3180             if (dr == null) {
3181                 mDrawables = dr = new Drawables(getContext());
3182             }
3183 
3184             mDrawables.mOverride = false;
3185 
3186             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
3187                 dr.mShowing[Drawables.LEFT].setCallback(null);
3188             }
3189             dr.mShowing[Drawables.LEFT] = left;
3190 
3191             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3192                 dr.mShowing[Drawables.TOP].setCallback(null);
3193             }
3194             dr.mShowing[Drawables.TOP] = top;
3195 
3196             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
3197                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3198             }
3199             dr.mShowing[Drawables.RIGHT] = right;
3200 
3201             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3202                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3203             }
3204             dr.mShowing[Drawables.BOTTOM] = bottom;
3205 
3206             final Rect compoundRect = dr.mCompoundRect;
3207             int[] state;
3208 
3209             state = getDrawableState();
3210 
3211             if (left != null) {
3212                 left.setState(state);
3213                 left.copyBounds(compoundRect);
3214                 left.setCallback(this);
3215                 dr.mDrawableSizeLeft = compoundRect.width();
3216                 dr.mDrawableHeightLeft = compoundRect.height();
3217             } else {
3218                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3219             }
3220 
3221             if (right != null) {
3222                 right.setState(state);
3223                 right.copyBounds(compoundRect);
3224                 right.setCallback(this);
3225                 dr.mDrawableSizeRight = compoundRect.width();
3226                 dr.mDrawableHeightRight = compoundRect.height();
3227             } else {
3228                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3229             }
3230 
3231             if (top != null) {
3232                 top.setState(state);
3233                 top.copyBounds(compoundRect);
3234                 top.setCallback(this);
3235                 dr.mDrawableSizeTop = compoundRect.height();
3236                 dr.mDrawableWidthTop = compoundRect.width();
3237             } else {
3238                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3239             }
3240 
3241             if (bottom != null) {
3242                 bottom.setState(state);
3243                 bottom.copyBounds(compoundRect);
3244                 bottom.setCallback(this);
3245                 dr.mDrawableSizeBottom = compoundRect.height();
3246                 dr.mDrawableWidthBottom = compoundRect.width();
3247             } else {
3248                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3249             }
3250         }
3251 
3252         // Save initial left/right drawables
3253         if (dr != null) {
3254             dr.mDrawableLeftInitial = left;
3255             dr.mDrawableRightInitial = right;
3256         }
3257 
3258         resetResolvedDrawables();
3259         resolveDrawables();
3260         applyCompoundDrawableTint();
3261         invalidate();
3262         requestLayout();
3263     }
3264 
3265     /**
3266      * Sets the Drawables (if any) to appear to the left of, above, to the
3267      * right of, and below the text. Use 0 if you do not want a Drawable there.
3268      * The Drawables' bounds will be set to their intrinsic bounds.
3269      * <p>
3270      * Calling this method will overwrite any Drawables previously set using
3271      * {@link #setCompoundDrawablesRelative} or related methods.
3272      *
3273      * @param left Resource identifier of the left Drawable.
3274      * @param top Resource identifier of the top Drawable.
3275      * @param right Resource identifier of the right Drawable.
3276      * @param bottom Resource identifier of the bottom Drawable.
3277      *
3278      * @attr ref android.R.styleable#TextView_drawableLeft
3279      * @attr ref android.R.styleable#TextView_drawableTop
3280      * @attr ref android.R.styleable#TextView_drawableRight
3281      * @attr ref android.R.styleable#TextView_drawableBottom
3282      */
3283     @android.view.RemotableViewMethod
3284     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
3285             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
3286         final Context context = getContext();
3287         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
3288                 top != 0 ? context.getDrawable(top) : null,
3289                 right != 0 ? context.getDrawable(right) : null,
3290                 bottom != 0 ? context.getDrawable(bottom) : null);
3291     }
3292 
3293     /**
3294      * Sets the Drawables (if any) to appear to the left of, above, to the
3295      * right of, and below the text. Use {@code null} if you do not want a
3296      * Drawable there. The Drawables' bounds will be set to their intrinsic
3297      * bounds.
3298      * <p>
3299      * Calling this method will overwrite any Drawables previously set using
3300      * {@link #setCompoundDrawablesRelative} or related methods.
3301      *
3302      * @attr ref android.R.styleable#TextView_drawableLeft
3303      * @attr ref android.R.styleable#TextView_drawableTop
3304      * @attr ref android.R.styleable#TextView_drawableRight
3305      * @attr ref android.R.styleable#TextView_drawableBottom
3306      */
3307     @android.view.RemotableViewMethod
3308     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
3309             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
3310 
3311         if (left != null) {
3312             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
3313         }
3314         if (right != null) {
3315             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
3316         }
3317         if (top != null) {
3318             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3319         }
3320         if (bottom != null) {
3321             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3322         }
3323         setCompoundDrawables(left, top, right, bottom);
3324     }
3325 
3326     /**
3327      * Sets the Drawables (if any) to appear to the start of, above, to the end
3328      * of, and below the text. Use {@code null} if you do not want a Drawable
3329      * there. The Drawables must already have had {@link Drawable#setBounds}
3330      * called.
3331      * <p>
3332      * Calling this method will overwrite any Drawables previously set using
3333      * {@link #setCompoundDrawables} or related methods.
3334      *
3335      * @attr ref android.R.styleable#TextView_drawableStart
3336      * @attr ref android.R.styleable#TextView_drawableTop
3337      * @attr ref android.R.styleable#TextView_drawableEnd
3338      * @attr ref android.R.styleable#TextView_drawableBottom
3339      */
3340     @android.view.RemotableViewMethod
3341     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
3342             @Nullable Drawable end, @Nullable Drawable bottom) {
3343         Drawables dr = mDrawables;
3344 
3345         // We're switching to relative, discard absolute.
3346         if (dr != null) {
3347             if (dr.mShowing[Drawables.LEFT] != null) {
3348                 dr.mShowing[Drawables.LEFT].setCallback(null);
3349             }
3350             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3351             if (dr.mShowing[Drawables.RIGHT] != null) {
3352                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3353             }
3354             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3355             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3356             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3357         }
3358 
3359         final boolean drawables = start != null || top != null
3360                 || end != null || bottom != null;
3361 
3362         if (!drawables) {
3363             // Clearing drawables...  can we free the data structure?
3364             if (dr != null) {
3365                 if (!dr.hasMetadata()) {
3366                     mDrawables = null;
3367                 } else {
3368                     // We need to retain the last set padding, so just clear
3369                     // out all of the fields in the existing structure.
3370                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3371                     dr.mDrawableStart = null;
3372                     if (dr.mShowing[Drawables.TOP] != null) {
3373                         dr.mShowing[Drawables.TOP].setCallback(null);
3374                     }
3375                     dr.mShowing[Drawables.TOP] = null;
3376                     if (dr.mDrawableEnd != null) {
3377                         dr.mDrawableEnd.setCallback(null);
3378                     }
3379                     dr.mDrawableEnd = null;
3380                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3381                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3382                     }
3383                     dr.mShowing[Drawables.BOTTOM] = null;
3384                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3385                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3386                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3387                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3388                 }
3389             }
3390         } else {
3391             if (dr == null) {
3392                 mDrawables = dr = new Drawables(getContext());
3393             }
3394 
3395             mDrawables.mOverride = true;
3396 
3397             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3398                 dr.mDrawableStart.setCallback(null);
3399             }
3400             dr.mDrawableStart = start;
3401 
3402             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3403                 dr.mShowing[Drawables.TOP].setCallback(null);
3404             }
3405             dr.mShowing[Drawables.TOP] = top;
3406 
3407             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3408                 dr.mDrawableEnd.setCallback(null);
3409             }
3410             dr.mDrawableEnd = end;
3411 
3412             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3413                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3414             }
3415             dr.mShowing[Drawables.BOTTOM] = bottom;
3416 
3417             final Rect compoundRect = dr.mCompoundRect;
3418             int[] state;
3419 
3420             state = getDrawableState();
3421 
3422             if (start != null) {
3423                 start.setState(state);
3424                 start.copyBounds(compoundRect);
3425                 start.setCallback(this);
3426                 dr.mDrawableSizeStart = compoundRect.width();
3427                 dr.mDrawableHeightStart = compoundRect.height();
3428             } else {
3429                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3430             }
3431 
3432             if (end != null) {
3433                 end.setState(state);
3434                 end.copyBounds(compoundRect);
3435                 end.setCallback(this);
3436                 dr.mDrawableSizeEnd = compoundRect.width();
3437                 dr.mDrawableHeightEnd = compoundRect.height();
3438             } else {
3439                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3440             }
3441 
3442             if (top != null) {
3443                 top.setState(state);
3444                 top.copyBounds(compoundRect);
3445                 top.setCallback(this);
3446                 dr.mDrawableSizeTop = compoundRect.height();
3447                 dr.mDrawableWidthTop = compoundRect.width();
3448             } else {
3449                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3450             }
3451 
3452             if (bottom != null) {
3453                 bottom.setState(state);
3454                 bottom.copyBounds(compoundRect);
3455                 bottom.setCallback(this);
3456                 dr.mDrawableSizeBottom = compoundRect.height();
3457                 dr.mDrawableWidthBottom = compoundRect.width();
3458             } else {
3459                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3460             }
3461         }
3462 
3463         resetResolvedDrawables();
3464         resolveDrawables();
3465         invalidate();
3466         requestLayout();
3467     }
3468 
3469     /**
3470      * Sets the Drawables (if any) to appear to the start of, above, to the end
3471      * of, and below the text. Use 0 if you do not want a Drawable there. The
3472      * Drawables' bounds will be set to their intrinsic bounds.
3473      * <p>
3474      * Calling this method will overwrite any Drawables previously set using
3475      * {@link #setCompoundDrawables} or related methods.
3476      *
3477      * @param start Resource identifier of the start Drawable.
3478      * @param top Resource identifier of the top Drawable.
3479      * @param end Resource identifier of the end Drawable.
3480      * @param bottom Resource identifier of the bottom Drawable.
3481      *
3482      * @attr ref android.R.styleable#TextView_drawableStart
3483      * @attr ref android.R.styleable#TextView_drawableTop
3484      * @attr ref android.R.styleable#TextView_drawableEnd
3485      * @attr ref android.R.styleable#TextView_drawableBottom
3486      */
3487     @android.view.RemotableViewMethod
3488     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3489             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3490         final Context context = getContext();
3491         setCompoundDrawablesRelativeWithIntrinsicBounds(
3492                 start != 0 ? context.getDrawable(start) : null,
3493                 top != 0 ? context.getDrawable(top) : null,
3494                 end != 0 ? context.getDrawable(end) : null,
3495                 bottom != 0 ? context.getDrawable(bottom) : null);
3496     }
3497 
3498     /**
3499      * Sets the Drawables (if any) to appear to the start of, above, to the end
3500      * of, and below the text. Use {@code null} if you do not want a Drawable
3501      * there. The Drawables' bounds will be set to their intrinsic bounds.
3502      * <p>
3503      * Calling this method will overwrite any Drawables previously set using
3504      * {@link #setCompoundDrawables} or related methods.
3505      *
3506      * @attr ref android.R.styleable#TextView_drawableStart
3507      * @attr ref android.R.styleable#TextView_drawableTop
3508      * @attr ref android.R.styleable#TextView_drawableEnd
3509      * @attr ref android.R.styleable#TextView_drawableBottom
3510      */
3511     @android.view.RemotableViewMethod
3512     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3513             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3514 
3515         if (start != null) {
3516             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3517         }
3518         if (end != null) {
3519             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3520         }
3521         if (top != null) {
3522             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3523         }
3524         if (bottom != null) {
3525             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3526         }
3527         setCompoundDrawablesRelative(start, top, end, bottom);
3528     }
3529 
3530     /**
3531      * Returns drawables for the left, top, right, and bottom borders.
3532      *
3533      * @attr ref android.R.styleable#TextView_drawableLeft
3534      * @attr ref android.R.styleable#TextView_drawableTop
3535      * @attr ref android.R.styleable#TextView_drawableRight
3536      * @attr ref android.R.styleable#TextView_drawableBottom
3537      */
3538     @NonNull
3539     public Drawable[] getCompoundDrawables() {
3540         final Drawables dr = mDrawables;
3541         if (dr != null) {
3542             return dr.mShowing.clone();
3543         } else {
3544             return new Drawable[] { null, null, null, null };
3545         }
3546     }
3547 
3548     /**
3549      * Returns drawables for the start, top, end, and bottom borders.
3550      *
3551      * @attr ref android.R.styleable#TextView_drawableStart
3552      * @attr ref android.R.styleable#TextView_drawableTop
3553      * @attr ref android.R.styleable#TextView_drawableEnd
3554      * @attr ref android.R.styleable#TextView_drawableBottom
3555      */
3556     @NonNull
3557     public Drawable[] getCompoundDrawablesRelative() {
3558         final Drawables dr = mDrawables;
3559         if (dr != null) {
3560             return new Drawable[] {
3561                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3562                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3563             };
3564         } else {
3565             return new Drawable[] { null, null, null, null };
3566         }
3567     }
3568 
3569     /**
3570      * Sets the size of the padding between the compound drawables and
3571      * the text.
3572      *
3573      * @attr ref android.R.styleable#TextView_drawablePadding
3574      */
3575     @android.view.RemotableViewMethod
3576     public void setCompoundDrawablePadding(int pad) {
3577         Drawables dr = mDrawables;
3578         if (pad == 0) {
3579             if (dr != null) {
3580                 dr.mDrawablePadding = pad;
3581             }
3582         } else {
3583             if (dr == null) {
3584                 mDrawables = dr = new Drawables(getContext());
3585             }
3586             dr.mDrawablePadding = pad;
3587         }
3588 
3589         invalidate();
3590         requestLayout();
3591     }
3592 
3593     /**
3594      * Returns the padding between the compound drawables and the text.
3595      *
3596      * @attr ref android.R.styleable#TextView_drawablePadding
3597      */
3598     @InspectableProperty(name = "drawablePadding")
3599     public int getCompoundDrawablePadding() {
3600         final Drawables dr = mDrawables;
3601         return dr != null ? dr.mDrawablePadding : 0;
3602     }
3603 
3604     /**
3605      * Applies a tint to the compound drawables. Does not modify the
3606      * current tint mode, which is {@link BlendMode#SRC_IN} by default.
3607      * <p>
3608      * Subsequent calls to
3609      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3610      * and related methods will automatically mutate the drawables and apply
3611      * the specified tint and tint mode using
3612      * {@link Drawable#setTintList(ColorStateList)}.
3613      *
3614      * @param tint the tint to apply, may be {@code null} to clear tint
3615      *
3616      * @attr ref android.R.styleable#TextView_drawableTint
3617      * @see #getCompoundDrawableTintList()
3618      * @see Drawable#setTintList(ColorStateList)
3619      */
3620     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3621         if (mDrawables == null) {
3622             mDrawables = new Drawables(getContext());
3623         }
3624         mDrawables.mTintList = tint;
3625         mDrawables.mHasTint = true;
3626 
3627         applyCompoundDrawableTint();
3628     }
3629 
3630     /**
3631      * @return the tint applied to the compound drawables
3632      * @attr ref android.R.styleable#TextView_drawableTint
3633      * @see #setCompoundDrawableTintList(ColorStateList)
3634      */
3635     @InspectableProperty(name = "drawableTint")
3636     public ColorStateList getCompoundDrawableTintList() {
3637         return mDrawables != null ? mDrawables.mTintList : null;
3638     }
3639 
3640     /**
3641      * Specifies the blending mode used to apply the tint specified by
3642      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3643      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3644      *
3645      * @param tintMode the blending mode used to apply the tint, may be
3646      *                 {@code null} to clear tint
3647      * @attr ref android.R.styleable#TextView_drawableTintMode
3648      * @see #setCompoundDrawableTintList(ColorStateList)
3649      * @see Drawable#setTintMode(PorterDuff.Mode)
3650      */
3651     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3652         setCompoundDrawableTintBlendMode(tintMode != null
3653                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3654     }
3655 
3656     /**
3657      * Specifies the blending mode used to apply the tint specified by
3658      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3659      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3660      *
3661      * @param blendMode the blending mode used to apply the tint, may be
3662      *                 {@code null} to clear tint
3663      * @attr ref android.R.styleable#TextView_drawableTintMode
3664      * @see #setCompoundDrawableTintList(ColorStateList)
3665      * @see Drawable#setTintBlendMode(BlendMode)
3666      */
3667     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3668         if (mDrawables == null) {
3669             mDrawables = new Drawables(getContext());
3670         }
3671         mDrawables.mBlendMode = blendMode;
3672         mDrawables.mHasTintMode = true;
3673 
3674         applyCompoundDrawableTint();
3675     }
3676 
3677     /**
3678      * Returns the blending mode used to apply the tint to the compound
3679      * drawables, if specified.
3680      *
3681      * @return the blending mode used to apply the tint to the compound
3682      *         drawables
3683      * @attr ref android.R.styleable#TextView_drawableTintMode
3684      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3685      *
3686      */
3687     @InspectableProperty(name = "drawableTintMode")
3688     public PorterDuff.Mode getCompoundDrawableTintMode() {
3689         BlendMode mode = getCompoundDrawableTintBlendMode();
3690         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3691     }
3692 
3693     /**
3694      * Returns the blending mode used to apply the tint to the compound
3695      * drawables, if specified.
3696      *
3697      * @return the blending mode used to apply the tint to the compound
3698      *         drawables
3699      * @attr ref android.R.styleable#TextView_drawableTintMode
3700      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3701      */
3702     @InspectableProperty(name = "drawableBlendMode",
3703             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
3704     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3705         return mDrawables != null ? mDrawables.mBlendMode : null;
3706     }
3707 
3708     private void applyCompoundDrawableTint() {
3709         if (mDrawables == null) {
3710             return;
3711         }
3712 
3713         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3714             final ColorStateList tintList = mDrawables.mTintList;
3715             final BlendMode blendMode = mDrawables.mBlendMode;
3716             final boolean hasTint = mDrawables.mHasTint;
3717             final boolean hasTintMode = mDrawables.mHasTintMode;
3718             final int[] state = getDrawableState();
3719 
3720             for (Drawable dr : mDrawables.mShowing) {
3721                 if (dr == null) {
3722                     continue;
3723                 }
3724 
3725                 if (dr == mDrawables.mDrawableError) {
3726                     // From a developer's perspective, the error drawable isn't
3727                     // a compound drawable. Don't apply the generic compound
3728                     // drawable tint to it.
3729                     continue;
3730                 }
3731 
3732                 dr.mutate();
3733 
3734                 if (hasTint) {
3735                     dr.setTintList(tintList);
3736                 }
3737 
3738                 if (hasTintMode) {
3739                     dr.setTintBlendMode(blendMode);
3740                 }
3741 
3742                 // The drawable (or one of its children) may not have been
3743                 // stateful before applying the tint, so let's try again.
3744                 if (dr.isStateful()) {
3745                     dr.setState(state);
3746                 }
3747             }
3748         }
3749     }
3750 
3751     /**
3752      * @inheritDoc
3753      *
3754      * @see #setFirstBaselineToTopHeight(int)
3755      * @see #setLastBaselineToBottomHeight(int)
3756      */
3757     @Override
3758     public void setPadding(int left, int top, int right, int bottom) {
3759         if (left != mPaddingLeft
3760                 || right != mPaddingRight
3761                 || top != mPaddingTop
3762                 ||  bottom != mPaddingBottom) {
3763             nullLayouts();
3764         }
3765 
3766         // the super call will requestLayout()
3767         super.setPadding(left, top, right, bottom);
3768         invalidate();
3769     }
3770 
3771     /**
3772      * @inheritDoc
3773      *
3774      * @see #setFirstBaselineToTopHeight(int)
3775      * @see #setLastBaselineToBottomHeight(int)
3776      */
3777     @Override
3778     public void setPaddingRelative(int start, int top, int end, int bottom) {
3779         if (start != getPaddingStart()
3780                 || end != getPaddingEnd()
3781                 || top != mPaddingTop
3782                 || bottom != mPaddingBottom) {
3783             nullLayouts();
3784         }
3785 
3786         // the super call will requestLayout()
3787         super.setPaddingRelative(start, top, end, bottom);
3788         invalidate();
3789     }
3790 
3791     /**
3792      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3793      * the distance between the top of the TextView and first line's baseline.
3794      * <p>
3795      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3796      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3797      *
3798      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3799      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3800      * Moreover since this function sets the top padding, if the height of the TextView is less than
3801      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3802      * down and bottom will be clipped.
3803      *
3804      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3805      *      in pixels
3806      *
3807      * @see #getFirstBaselineToTopHeight()
3808      * @see #setLastBaselineToBottomHeight(int)
3809      * @see #setPadding(int, int, int, int)
3810      * @see #setPaddingRelative(int, int, int, int)
3811      *
3812      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3813      */
3814     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3815         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3816 
3817         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3818         final int fontMetricsTop;
3819         if (getIncludeFontPadding()) {
3820             fontMetricsTop = fontMetrics.top;
3821         } else {
3822             fontMetricsTop = fontMetrics.ascent;
3823         }
3824 
3825         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3826         // in settings). At the moment, we don't.
3827 
3828         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3829             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3830             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3831         }
3832     }
3833 
3834     /**
3835      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3836      * the distance between the bottom of the TextView and the last line's baseline.
3837      * <p>
3838      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3839      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3840      *
3841      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3842      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3843      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3844      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3845      * clipped.
3846      *
3847      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3848      *      in pixels
3849      *
3850      * @see #getLastBaselineToBottomHeight()
3851      * @see #setFirstBaselineToTopHeight(int)
3852      * @see #setPadding(int, int, int, int)
3853      * @see #setPaddingRelative(int, int, int, int)
3854      *
3855      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3856      */
3857     public void setLastBaselineToBottomHeight(
3858             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3859         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3860 
3861         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3862         final int fontMetricsBottom;
3863         if (getIncludeFontPadding()) {
3864             fontMetricsBottom = fontMetrics.bottom;
3865         } else {
3866             fontMetricsBottom = fontMetrics.descent;
3867         }
3868 
3869         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3870         // in settings). At the moment, we don't.
3871 
3872         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3873             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3874             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3875         }
3876     }
3877 
3878     /**
3879      * Returns the distance between the first text baseline and the top of this TextView.
3880      *
3881      * @see #setFirstBaselineToTopHeight(int)
3882      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3883      */
3884     @InspectableProperty
3885     public int getFirstBaselineToTopHeight() {
3886         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3887     }
3888 
3889     /**
3890      * Returns the distance between the last text baseline and the bottom of this TextView.
3891      *
3892      * @see #setLastBaselineToBottomHeight(int)
3893      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3894      */
3895     @InspectableProperty
3896     public int getLastBaselineToBottomHeight() {
3897         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3898     }
3899 
3900     /**
3901      * Gets the autolink mask of the text.
3902      *
3903      * See {@link Linkify#ALL} and peers for possible values.
3904      *
3905      * @attr ref android.R.styleable#TextView_autoLink
3906      */
3907     @InspectableProperty(name = "autoLink", flagMapping = {
3908             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3909             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3910             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3911             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3912     })
3913     public final int getAutoLinkMask() {
3914         return mAutoLinkMask;
3915     }
3916 
3917     /**
3918      * Sets the Drawable corresponding to the selection handle used for
3919      * positioning the cursor within text. The Drawable defaults to the value
3920      * of the textSelectHandle 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 #setTextSelectHandle(int)
3925      * @attr ref android.R.styleable#TextView_textSelectHandle
3926      */
3927     @android.view.RemotableViewMethod
3928     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3929         Preconditions.checkNotNull(textSelectHandle,
3930                 "The text select handle should not be null.");
3931         mTextSelectHandle = textSelectHandle;
3932         mTextSelectHandleRes = 0;
3933         if (mEditor != null) {
3934             mEditor.loadHandleDrawables(true /* overwrite */);
3935         }
3936     }
3937 
3938     /**
3939      * Sets the Drawable corresponding to the selection handle used for
3940      * positioning the cursor within text. The Drawable defaults to the value
3941      * of the textSelectHandle attribute.
3942      * Note that any change applied to the handle Drawable will not be visible
3943      * until the handle is hidden and then drawn again.
3944      *
3945      * @see #setTextSelectHandle(Drawable)
3946      * @attr ref android.R.styleable#TextView_textSelectHandle
3947      */
3948     @android.view.RemotableViewMethod
3949     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3950         Preconditions.checkArgument(textSelectHandle != 0,
3951                 "The text select handle should be a valid drawable resource id.");
3952         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3953     }
3954 
3955     /**
3956      * Returns the Drawable corresponding to the selection handle used
3957      * for positioning the cursor within text.
3958      * Note that any change applied to the handle Drawable will not be visible
3959      * until the handle is hidden and then drawn again.
3960      *
3961      * @return the text select handle drawable
3962      *
3963      * @see #setTextSelectHandle(Drawable)
3964      * @see #setTextSelectHandle(int)
3965      * @attr ref android.R.styleable#TextView_textSelectHandle
3966      */
3967     @Nullable public Drawable getTextSelectHandle() {
3968         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3969             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3970         }
3971         return mTextSelectHandle;
3972     }
3973 
3974     /**
3975      * Sets the Drawable corresponding to the left handle used
3976      * for selecting text. The Drawable defaults to the value of the
3977      * textSelectHandleLeft 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 #setTextSelectHandleLeft(int)
3982      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3983      */
3984     @android.view.RemotableViewMethod
3985     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3986         Preconditions.checkNotNull(textSelectHandleLeft,
3987                 "The left text select handle should not be null.");
3988         mTextSelectHandleLeft = textSelectHandleLeft;
3989         mTextSelectHandleLeftRes = 0;
3990         if (mEditor != null) {
3991             mEditor.loadHandleDrawables(true /* overwrite */);
3992         }
3993     }
3994 
3995     /**
3996      * Sets the Drawable corresponding to the left handle used
3997      * for selecting text. The Drawable defaults to the value of the
3998      * textSelectHandleLeft attribute.
3999      * Note that any change applied to the handle Drawable will not be visible
4000      * until the handle is hidden and then drawn again.
4001      *
4002      * @see #setTextSelectHandleLeft(Drawable)
4003      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
4004      */
4005     @android.view.RemotableViewMethod
4006     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
4007         Preconditions.checkArgument(textSelectHandleLeft != 0,
4008                 "The text select left handle should be a valid drawable resource id.");
4009         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
4010     }
4011 
4012     /**
4013      * Returns the Drawable corresponding to the left handle used
4014      * for selecting text.
4015      * Note that any change applied to the handle Drawable will not be visible
4016      * until the handle is hidden and then drawn again.
4017      *
4018      * @return the left text selection handle drawable
4019      *
4020      * @see #setTextSelectHandleLeft(Drawable)
4021      * @see #setTextSelectHandleLeft(int)
4022      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
4023      */
4024     @Nullable public Drawable getTextSelectHandleLeft() {
4025         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
4026             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
4027         }
4028         return mTextSelectHandleLeft;
4029     }
4030 
4031     /**
4032      * Sets the Drawable corresponding to the right handle used
4033      * for selecting text. The Drawable defaults to the value of the
4034      * textSelectHandleRight attribute.
4035      * Note that any change applied to the handle Drawable will not be visible
4036      * until the handle is hidden and then drawn again.
4037      *
4038      * @see #setTextSelectHandleRight(int)
4039      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4040      */
4041     @android.view.RemotableViewMethod
4042     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
4043         Preconditions.checkNotNull(textSelectHandleRight,
4044                 "The right text select handle should not be null.");
4045         mTextSelectHandleRight = textSelectHandleRight;
4046         mTextSelectHandleRightRes = 0;
4047         if (mEditor != null) {
4048             mEditor.loadHandleDrawables(true /* overwrite */);
4049         }
4050     }
4051 
4052     /**
4053      * Sets the Drawable corresponding to the right handle used
4054      * for selecting text. The Drawable defaults to the value of the
4055      * textSelectHandleRight attribute.
4056      * Note that any change applied to the handle Drawable will not be visible
4057      * until the handle is hidden and then drawn again.
4058      *
4059      * @see #setTextSelectHandleRight(Drawable)
4060      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4061      */
4062     @android.view.RemotableViewMethod
4063     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
4064         Preconditions.checkArgument(textSelectHandleRight != 0,
4065                 "The text select right handle should be a valid drawable resource id.");
4066         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
4067     }
4068 
4069     /**
4070      * Returns the Drawable corresponding to the right handle used
4071      * for selecting text.
4072      * Note that any change applied to the handle Drawable will not be visible
4073      * until the handle is hidden and then drawn again.
4074      *
4075      * @return the right text selection handle drawable
4076      *
4077      * @see #setTextSelectHandleRight(Drawable)
4078      * @see #setTextSelectHandleRight(int)
4079      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4080      */
4081     @Nullable public Drawable getTextSelectHandleRight() {
4082         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
4083             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
4084         }
4085         return mTextSelectHandleRight;
4086     }
4087 
4088     /**
4089      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
4090      * value of the textCursorDrawable attribute.
4091      * Note that any change applied to the cursor Drawable will not be visible
4092      * until the cursor is hidden and then drawn again.
4093      *
4094      * @see #setTextCursorDrawable(int)
4095      * @attr ref android.R.styleable#TextView_textCursorDrawable
4096      */
4097     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
4098         mCursorDrawable = textCursorDrawable;
4099         mCursorDrawableRes = 0;
4100         if (mEditor != null) {
4101             mEditor.loadCursorDrawable();
4102         }
4103     }
4104 
4105     /**
4106      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
4107      * value of the textCursorDrawable attribute.
4108      * Note that any change applied to the cursor Drawable will not be visible
4109      * until the cursor is hidden and then drawn again.
4110      *
4111      * @see #setTextCursorDrawable(Drawable)
4112      * @attr ref android.R.styleable#TextView_textCursorDrawable
4113      */
4114     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
4115         setTextCursorDrawable(
4116                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
4117     }
4118 
4119     /**
4120      * Returns the Drawable corresponding to the text cursor.
4121      * Note that any change applied to the cursor Drawable will not be visible
4122      * until the cursor is hidden and then drawn again.
4123      *
4124      * @return the text cursor drawable
4125      *
4126      * @see #setTextCursorDrawable(Drawable)
4127      * @see #setTextCursorDrawable(int)
4128      * @attr ref android.R.styleable#TextView_textCursorDrawable
4129      */
4130     @Nullable public Drawable getTextCursorDrawable() {
4131         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
4132             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
4133         }
4134         return mCursorDrawable;
4135     }
4136 
4137     /**
4138      * Sets the text appearance from the specified style resource.
4139      * <p>
4140      * Use a framework-defined {@code TextAppearance} style like
4141      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
4142      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
4143      * set of attributes that can be used in a custom style.
4144      *
4145      * @param resId the resource identifier of the style to apply
4146      * @attr ref android.R.styleable#TextView_textAppearance
4147      */
4148     @SuppressWarnings("deprecation")
4149     public void setTextAppearance(@StyleRes int resId) {
4150         setTextAppearance(mContext, resId);
4151     }
4152 
4153     /**
4154      * Sets the text color, size, style, hint color, and highlight color
4155      * from the specified TextAppearance resource.
4156      *
4157      * @deprecated Use {@link #setTextAppearance(int)} instead.
4158      */
4159     @Deprecated
4160     public void setTextAppearance(Context context, @StyleRes int resId) {
4161         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
4162         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
4163         readTextAppearance(context, ta, attributes, false /* styleArray */);
4164         ta.recycle();
4165         applyTextAppearance(attributes);
4166     }
4167 
4168     /**
4169      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
4170      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
4171      */
4172     private static class TextAppearanceAttributes {
4173         int mTextColorHighlight = 0;
4174         int mSearchResultHighlightColor = 0;
4175         int mFocusedSearchResultHighlightColor = 0;
4176         ColorStateList mTextColor = null;
4177         ColorStateList mTextColorHint = null;
4178         ColorStateList mTextColorLink = null;
4179         int mTextSize = -1;
4180         int mTextSizeUnit = -1;
4181         LocaleList mTextLocales = null;
4182         String mFontFamily = null;
4183         Typeface mFontTypeface = null;
4184         boolean mFontFamilyExplicit = false;
4185         int mTypefaceIndex = -1;
4186         int mTextStyle = 0;
4187         int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
4188         boolean mAllCaps = false;
4189         int mShadowColor = 0;
4190         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
4191         boolean mHasElegant = false;
4192         boolean mElegant = false;
4193         boolean mHasFallbackLineSpacing = false;
4194         boolean mFallbackLineSpacing = false;
4195         boolean mHasLetterSpacing = false;
4196         float mLetterSpacing = 0;
4197         String mFontFeatureSettings = null;
4198         String mFontVariationSettings = null;
4199         boolean mHasLineBreakStyle = false;
4200         boolean mHasLineBreakWordStyle = false;
4201         int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
4202         int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
4203 
4204         @Override
4205         public String toString() {
4206             return "TextAppearanceAttributes {\n"
4207                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
4208                     + "    mSearchResultHighlightColor: " + mSearchResultHighlightColor + "\n"
4209                     + "    mFocusedSearchResultHighlightColor: "
4210                     + mFocusedSearchResultHighlightColor + "\n"
4211                     + "    mTextColor:" + mTextColor + "\n"
4212                     + "    mTextColorHint:" + mTextColorHint + "\n"
4213                     + "    mTextColorLink:" + mTextColorLink + "\n"
4214                     + "    mTextSize:" + mTextSize + "\n"
4215                     + "    mTextSizeUnit:" + mTextSizeUnit + "\n"
4216                     + "    mTextLocales:" + mTextLocales + "\n"
4217                     + "    mFontFamily:" + mFontFamily + "\n"
4218                     + "    mFontTypeface:" + mFontTypeface + "\n"
4219                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
4220                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
4221                     + "    mTextStyle:" + mTextStyle + "\n"
4222                     + "    mFontWeight:" + mFontWeight + "\n"
4223                     + "    mAllCaps:" + mAllCaps + "\n"
4224                     + "    mShadowColor:" + mShadowColor + "\n"
4225                     + "    mShadowDx:" + mShadowDx + "\n"
4226                     + "    mShadowDy:" + mShadowDy + "\n"
4227                     + "    mShadowRadius:" + mShadowRadius + "\n"
4228                     + "    mHasElegant:" + mHasElegant + "\n"
4229                     + "    mElegant:" + mElegant + "\n"
4230                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
4231                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
4232                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
4233                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
4234                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
4235                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
4236                     + "    mHasLineBreakStyle:" + mHasLineBreakStyle + "\n"
4237                     + "    mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n"
4238                     + "    mLineBreakStyle:" + mLineBreakStyle + "\n"
4239                     + "    mLineBreakWordStyle:" + mLineBreakWordStyle + "\n"
4240                     + "}";
4241         }
4242     }
4243 
4244     // Maps styleable attributes that exist both in TextView style and TextAppearance.
4245     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
4246     static {
4247         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
4248                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
4249         sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor,
4250                 com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor);
4251         sAppearanceValues.put(
4252                 com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor,
4253                 com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor);
4254         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
4255                 com.android.internal.R.styleable.TextAppearance_textColor);
4256         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
4257                 com.android.internal.R.styleable.TextAppearance_textColorHint);
4258         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
4259                 com.android.internal.R.styleable.TextAppearance_textColorLink);
4260         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
4261                 com.android.internal.R.styleable.TextAppearance_textSize);
4262         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
4263                 com.android.internal.R.styleable.TextAppearance_textLocale);
4264         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
4265                 com.android.internal.R.styleable.TextAppearance_typeface);
4266         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
4267                 com.android.internal.R.styleable.TextAppearance_fontFamily);
4268         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
4269                 com.android.internal.R.styleable.TextAppearance_textStyle);
4270         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
4271                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
4272         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
4273                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
4274         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
4275                 com.android.internal.R.styleable.TextAppearance_shadowColor);
4276         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
4277                 com.android.internal.R.styleable.TextAppearance_shadowDx);
4278         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
4279                 com.android.internal.R.styleable.TextAppearance_shadowDy);
4280         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
4281                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
4282         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
4283                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
4284         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
4285                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
4286         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
4287                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
4288         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
4289                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
4290         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
4291                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
4292         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle,
4293                 com.android.internal.R.styleable.TextAppearance_lineBreakStyle);
4294         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle,
4295                 com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle);
4296     }
4297 
4298     /**
4299      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
4300      * set. If the TypedArray contains a value that was already set in the given attributes, that
4301      * will be overridden.
4302      *
4303      * @param context The Context to be used
4304      * @param appearance The TypedArray to read properties from
4305      * @param attributes the TextAppearanceAttributes to fill in
4306      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
4307      *                   what attribute indexes will be used to read the properties.
4308      */
4309     private void readTextAppearance(Context context, TypedArray appearance,
4310             TextAppearanceAttributes attributes, boolean styleArray) {
4311         final int n = appearance.getIndexCount();
4312         for (int i = 0; i < n; i++) {
4313             final int attr = appearance.getIndex(i);
4314             int index = attr;
4315             // Translate style array index ids to TextAppearance ids.
4316             if (styleArray) {
4317                 index = sAppearanceValues.get(attr, -1);
4318                 if (index == -1) {
4319                     // This value is not part of a Text Appearance and should be ignored.
4320                     continue;
4321                 }
4322             }
4323             switch (index) {
4324                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
4325                     attributes.mTextColorHighlight =
4326                             appearance.getColor(attr, attributes.mTextColorHighlight);
4327                     break;
4328                 case com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor:
4329                     attributes.mSearchResultHighlightColor =
4330                             appearance.getColor(attr, attributes.mSearchResultHighlightColor);
4331                     break;
4332                 case com.android.internal.R.styleable
4333                         .TextAppearance_focusedSearchResultHighlightColor:
4334                     attributes.mFocusedSearchResultHighlightColor =
4335                             appearance.getColor(attr,
4336                                     attributes.mFocusedSearchResultHighlightColor);
4337                     break;
4338                 case com.android.internal.R.styleable.TextAppearance_textColor:
4339                     attributes.mTextColor = appearance.getColorStateList(attr);
4340                     break;
4341                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
4342                     attributes.mTextColorHint = appearance.getColorStateList(attr);
4343                     break;
4344                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
4345                     attributes.mTextColorLink = appearance.getColorStateList(attr);
4346                     break;
4347                 case com.android.internal.R.styleable.TextAppearance_textSize:
4348                     attributes.mTextSize =
4349                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
4350                     attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit();
4351                     break;
4352                 case com.android.internal.R.styleable.TextAppearance_textLocale:
4353                     final String localeString = appearance.getString(attr);
4354                     if (localeString != null) {
4355                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
4356                         if (!localeList.isEmpty()) {
4357                             attributes.mTextLocales = localeList;
4358                         }
4359                     }
4360                     break;
4361                 case com.android.internal.R.styleable.TextAppearance_typeface:
4362                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
4363                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4364                         attributes.mFontFamily = null;
4365                     }
4366                     break;
4367                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
4368                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
4369                         try {
4370                             attributes.mFontTypeface = appearance.getFont(attr);
4371                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
4372                             // Expected if it is not a font resource.
4373                         }
4374                     }
4375                     if (attributes.mFontTypeface == null) {
4376                         attributes.mFontFamily = appearance.getString(attr);
4377                     }
4378                     attributes.mFontFamilyExplicit = true;
4379                     break;
4380                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4381                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4382                     break;
4383                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4384                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4385                     break;
4386                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4387                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4388                     break;
4389                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4390                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4391                     break;
4392                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4393                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4394                     break;
4395                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4396                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4397                     break;
4398                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4399                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4400                     break;
4401                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4402                     attributes.mHasElegant = true;
4403                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4404                     break;
4405                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4406                     attributes.mHasFallbackLineSpacing = true;
4407                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4408                             attributes.mFallbackLineSpacing);
4409                     break;
4410                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4411                     attributes.mHasLetterSpacing = true;
4412                     attributes.mLetterSpacing =
4413                             appearance.getFloat(attr, attributes.mLetterSpacing);
4414                     break;
4415                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4416                     attributes.mFontFeatureSettings = appearance.getString(attr);
4417                     break;
4418                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4419                     attributes.mFontVariationSettings = appearance.getString(attr);
4420                     break;
4421                 case com.android.internal.R.styleable.TextAppearance_lineBreakStyle:
4422                     attributes.mHasLineBreakStyle = true;
4423                     attributes.mLineBreakStyle =
4424                             appearance.getInt(attr, attributes.mLineBreakStyle);
4425                     break;
4426                 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
4427                     attributes.mHasLineBreakWordStyle = true;
4428                     attributes.mLineBreakWordStyle =
4429                             appearance.getInt(attr, attributes.mLineBreakWordStyle);
4430                     break;
4431                 default:
4432             }
4433         }
4434     }
4435 
4436     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4437         if (attributes.mTextColor != null) {
4438             setTextColor(attributes.mTextColor);
4439         }
4440 
4441         if (attributes.mTextColorHint != null) {
4442             setHintTextColor(attributes.mTextColorHint);
4443         }
4444 
4445         if (attributes.mTextColorLink != null) {
4446             setLinkTextColor(attributes.mTextColorLink);
4447         }
4448 
4449         if (attributes.mTextColorHighlight != 0) {
4450             setHighlightColor(attributes.mTextColorHighlight);
4451         }
4452 
4453         if (attributes.mSearchResultHighlightColor != 0) {
4454             setSearchResultHighlightColor(attributes.mSearchResultHighlightColor);
4455         }
4456 
4457         if (attributes.mFocusedSearchResultHighlightColor != 0) {
4458             setFocusedSearchResultHighlightColor(attributes.mFocusedSearchResultHighlightColor);
4459         }
4460 
4461         if (attributes.mTextSize != -1) {
4462             mTextSizeUnit = attributes.mTextSizeUnit;
4463             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4464         }
4465 
4466         if (attributes.mTextLocales != null) {
4467             setTextLocales(attributes.mTextLocales);
4468         }
4469 
4470         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4471             attributes.mFontFamily = null;
4472         }
4473         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4474                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4475 
4476         if (attributes.mShadowColor != 0) {
4477             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4478                     attributes.mShadowColor);
4479         }
4480 
4481         if (attributes.mAllCaps) {
4482             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4483         }
4484 
4485         if (attributes.mHasElegant) {
4486             setElegantTextHeight(attributes.mElegant);
4487         }
4488 
4489         if (attributes.mHasFallbackLineSpacing) {
4490             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4491         }
4492 
4493         if (attributes.mHasLetterSpacing) {
4494             setLetterSpacing(attributes.mLetterSpacing);
4495         }
4496 
4497         if (attributes.mFontFeatureSettings != null) {
4498             setFontFeatureSettings(attributes.mFontFeatureSettings);
4499         }
4500 
4501         if (attributes.mFontVariationSettings != null) {
4502             setFontVariationSettings(attributes.mFontVariationSettings);
4503         }
4504 
4505         if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) {
4506             updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle,
4507                     attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle,
4508                     attributes.mLineBreakWordStyle);
4509         }
4510     }
4511 
4512     /**
4513      * Updates the LineBreakConfig from the TextAppearance.
4514      *
4515      * This method updates the given line configuration from the TextAppearance. This method will
4516      * request new layout if line break config has been changed.
4517      *
4518      * @param isLineBreakStyleSpecified true if the line break style is specified.
4519      * @param isLineBreakWordStyleSpecified true if the line break word style is specified.
4520      * @param lineBreakStyle the value of the line break style in the TextAppearance.
4521      * @param lineBreakWordStyle the value of the line break word style in the TextAppearance.
4522      */
4523     private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified,
4524             boolean isLineBreakWordStyleSpecified,
4525             @LineBreakConfig.LineBreakStyle int lineBreakStyle,
4526             @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
4527         boolean updated = false;
4528         if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) {
4529             mLineBreakStyle = lineBreakStyle;
4530             updated = true;
4531         }
4532         if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) {
4533             mLineBreakWordStyle = lineBreakWordStyle;
4534             updated = true;
4535         }
4536         if (updated && mLayout != null) {
4537             nullLayouts();
4538             requestLayout();
4539             invalidate();
4540         }
4541     }
4542     /**
4543      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4544      * the first member of {@link #getTextLocales()}.
4545      * @return the default primary {@link Locale} of the text in this TextView.
4546      */
4547     @NonNull
4548     public Locale getTextLocale() {
4549         return mTextPaint.getTextLocale();
4550     }
4551 
4552     /**
4553      * Get the default {@link LocaleList} of the text in this TextView.
4554      * @return the default {@link LocaleList} of the text in this TextView.
4555      */
4556     @NonNull @Size(min = 1)
4557     public LocaleList getTextLocales() {
4558         return mTextPaint.getTextLocales();
4559     }
4560 
4561     private void changeListenerLocaleTo(@Nullable Locale locale) {
4562         if (mListenerChanged) {
4563             // If a listener has been explicitly set, don't change it. We may break something.
4564             return;
4565         }
4566         // The following null check is not absolutely necessary since all calling points of
4567         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4568         // here in case others would want to call this method in the future.
4569         if (mEditor != null) {
4570             KeyListener listener = mEditor.mKeyListener;
4571             if (listener instanceof DigitsKeyListener) {
4572                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4573             } else if (listener instanceof DateKeyListener) {
4574                 listener = DateKeyListener.getInstance(locale);
4575             } else if (listener instanceof TimeKeyListener) {
4576                 listener = TimeKeyListener.getInstance(locale);
4577             } else if (listener instanceof DateTimeKeyListener) {
4578                 listener = DateTimeKeyListener.getInstance(locale);
4579             } else {
4580                 return;
4581             }
4582             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4583             setKeyListenerOnly(listener);
4584             setInputTypeFromEditor();
4585             if (wasPasswordType) {
4586                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4587                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4588                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4589                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4590                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4591                 }
4592             }
4593         }
4594     }
4595 
4596     /**
4597      * Set the default {@link Locale} of the text in this TextView to a one-member
4598      * {@link LocaleList} containing just the given Locale.
4599      *
4600      * @param locale the {@link Locale} for drawing text, must not be null.
4601      *
4602      * @see #setTextLocales
4603      */
4604     public void setTextLocale(@NonNull Locale locale) {
4605         mLocalesChanged = true;
4606         mTextPaint.setTextLocale(locale);
4607         if (mLayout != null) {
4608             nullLayouts();
4609             requestLayout();
4610             invalidate();
4611         }
4612     }
4613 
4614     /**
4615      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4616      *
4617      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4618      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4619      * other aspects of text display, including line breaking.
4620      *
4621      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4622      *
4623      * @see Paint#setTextLocales
4624      */
4625     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4626         mLocalesChanged = true;
4627         mTextPaint.setTextLocales(locales);
4628         if (mLayout != null) {
4629             nullLayouts();
4630             requestLayout();
4631             invalidate();
4632         }
4633     }
4634 
4635     @Override
4636     protected void onConfigurationChanged(Configuration newConfig) {
4637         super.onConfigurationChanged(newConfig);
4638         if (!mLocalesChanged) {
4639             mTextPaint.setTextLocales(LocaleList.getDefault());
4640             if (mLayout != null) {
4641                 nullLayouts();
4642                 requestLayout();
4643                 invalidate();
4644             }
4645         }
4646         if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) {
4647             mFontWeightAdjustment = newConfig.fontWeightAdjustment;
4648             setTypeface(getTypeface());
4649         }
4650 
4651         InputMethodManager imm = getInputMethodManager();
4652         // if orientation changed and this TextView is currently served.
4653         if (mLastOrientation != newConfig.orientation
4654                 && imm != null && imm.hasActiveInputConnection(this)) {
4655             // EditorInfo.internalImeOptions are out of date.
4656             imm.restartInput(this);
4657         }
4658         mLastOrientation = newConfig.orientation;
4659     }
4660 
4661     /**
4662      * @return the size (in pixels) of the default text size in this TextView.
4663      */
4664     @InspectableProperty
4665     @ViewDebug.ExportedProperty(category = "text")
4666     public float getTextSize() {
4667         return mTextPaint.getTextSize();
4668     }
4669 
4670     /**
4671      * @return the size (in scaled pixels) of the default text size in this TextView.
4672      * @hide
4673      */
4674     @ViewDebug.ExportedProperty(category = "text")
4675     public float getScaledTextSize() {
4676         return mTextPaint.getTextSize() / mTextPaint.density;
4677     }
4678 
4679     /** @hide */
4680     @ViewDebug.ExportedProperty(category = "text", mapping = {
4681             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4682             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4683             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4684             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4685     })
4686     public int getTypefaceStyle() {
4687         Typeface typeface = mTextPaint.getTypeface();
4688         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4689     }
4690 
4691     /**
4692      * Set the default text size to the given value, interpreted as "scaled
4693      * pixel" units.  This size is adjusted based on the current density and
4694      * user font size preference.
4695      *
4696      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4697      *
4698      * @param size The scaled pixel size.
4699      *
4700      * @attr ref android.R.styleable#TextView_textSize
4701      */
4702     @android.view.RemotableViewMethod
4703     public void setTextSize(float size) {
4704         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4705     }
4706 
4707     /**
4708      * Set the default text size to a given unit and value. See {@link
4709      * TypedValue} for the possible dimension units.
4710      *
4711      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4712      *
4713      * @param unit The desired dimension unit.
4714      * @param size The desired size in the given units.
4715      *
4716      * @attr ref android.R.styleable#TextView_textSize
4717      */
4718     public void setTextSize(int unit, float size) {
4719         if (!isAutoSizeEnabled()) {
4720             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4721         }
4722     }
4723 
4724     @NonNull
4725     private DisplayMetrics getDisplayMetricsOrSystem() {
4726         Context c = getContext();
4727         Resources r;
4728 
4729         if (c == null) {
4730             r = Resources.getSystem();
4731         } else {
4732             r = c.getResources();
4733         }
4734 
4735         return r.getDisplayMetrics();
4736     }
4737 
4738     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4739         mTextSizeUnit = unit;
4740         setRawTextSize(TypedValue.applyDimension(unit, size, getDisplayMetricsOrSystem()),
4741                 shouldRequestLayout);
4742     }
4743 
4744     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
4745     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4746         if (size != mTextPaint.getTextSize()) {
4747             mTextPaint.setTextSize(size);
4748 
4749             maybeRecalculateLineHeight();
4750             if (shouldRequestLayout && mLayout != null) {
4751                 // Do not auto-size right after setting the text size.
4752                 mNeedsAutoSizeText = false;
4753                 nullLayouts();
4754                 requestLayout();
4755                 invalidate();
4756             }
4757         }
4758     }
4759 
4760     /**
4761      * Gets the text size unit defined by the developer. It may be specified in resources or be
4762      * passed as the unit argument of {@link #setTextSize(int, float)} at runtime.
4763      *
4764      * @return the dimension type of the text size unit originally defined.
4765      * @see TypedValue#TYPE_DIMENSION
4766      */
4767     public int getTextSizeUnit() {
4768         return mTextSizeUnit;
4769     }
4770 
4771     /**
4772      * Gets the extent by which text should be stretched horizontally.
4773      * This will usually be 1.0.
4774      * @return The horizontal scale factor.
4775      */
4776     @InspectableProperty
4777     public float getTextScaleX() {
4778         return mTextPaint.getTextScaleX();
4779     }
4780 
4781     /**
4782      * Sets the horizontal scale factor for text. The default value
4783      * is 1.0. Values greater than 1.0 stretch the text wider.
4784      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4785      * @param size The horizontal scale factor.
4786      * @attr ref android.R.styleable#TextView_textScaleX
4787      */
4788     @android.view.RemotableViewMethod
4789     public void setTextScaleX(float size) {
4790         if (size != mTextPaint.getTextScaleX()) {
4791             mUserSetTextScaleX = true;
4792             mTextPaint.setTextScaleX(size);
4793 
4794             if (mLayout != null) {
4795                 nullLayouts();
4796                 requestLayout();
4797                 invalidate();
4798             }
4799         }
4800     }
4801 
4802     /**
4803      * Sets the typeface and style in which the text should be displayed.
4804      * Note that not all Typeface families actually have bold and italic
4805      * variants, so you may need to use
4806      * {@link #setTypeface(Typeface, int)} to get the appearance
4807      * that you actually want.
4808      *
4809      * @see #getTypeface()
4810      *
4811      * @attr ref android.R.styleable#TextView_fontFamily
4812      * @attr ref android.R.styleable#TextView_typeface
4813      * @attr ref android.R.styleable#TextView_textStyle
4814      */
4815     public void setTypeface(@Nullable Typeface tf) {
4816         mOriginalTypeface = tf;
4817         if (mFontWeightAdjustment != 0
4818                 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
4819             if (tf == null) {
4820                 if (Flags.fixNullTypefaceBolding()) {
4821                     tf = Typeface.DEFAULT_BOLD;
4822                 } else {
4823                     tf = Typeface.DEFAULT;
4824                 }
4825             } else {
4826                 int newWeight = Math.min(
4827                         Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
4828                         FontStyle.FONT_WEIGHT_MAX);
4829                 int typefaceStyle = tf != null ? tf.getStyle() : 0;
4830                 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0;
4831                 tf = Typeface.create(tf, newWeight, italic);
4832             }
4833         }
4834         if (mTextPaint.getTypeface() != tf) {
4835             mTextPaint.setTypeface(tf);
4836 
4837             if (mLayout != null) {
4838                 nullLayouts();
4839                 requestLayout();
4840                 invalidate();
4841             }
4842         }
4843     }
4844 
4845     /**
4846      * Gets the current {@link Typeface} that is used to style the text.
4847      * @return The current Typeface.
4848      *
4849      * @see #setTypeface(Typeface)
4850      *
4851      * @attr ref android.R.styleable#TextView_fontFamily
4852      * @attr ref android.R.styleable#TextView_typeface
4853      * @attr ref android.R.styleable#TextView_textStyle
4854      */
4855     @InspectableProperty
4856     public Typeface getTypeface() {
4857         return mOriginalTypeface;
4858     }
4859 
4860     /**
4861      * Set the TextView's elegant height metrics flag. This setting selects font
4862      * variants that have not been compacted to fit Latin-based vertical
4863      * metrics, and also increases top and bottom bounds to provide more space.
4864      *
4865      * @param elegant set the paint's elegant metrics flag.
4866      *
4867      * @see #isElegantTextHeight()
4868      * @see Paint#isElegantTextHeight()
4869      *
4870      * @attr ref android.R.styleable#TextView_elegantTextHeight
4871      */
4872     public void setElegantTextHeight(boolean elegant) {
4873         if (elegant != mTextPaint.isElegantTextHeight()) {
4874             mTextPaint.setElegantTextHeight(elegant);
4875             if (mLayout != null) {
4876                 nullLayouts();
4877                 requestLayout();
4878                 invalidate();
4879             }
4880         }
4881     }
4882 
4883     /**
4884      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4885      * displaying the text (which is needed to avoid text from consecutive lines running into
4886      * each other). If set, fallback fonts that end up getting used can increase the ascent
4887      * and descent of the lines that they are used on.
4888      * <p/>
4889      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4890      * is typically much taller or deeper than Latin text.
4891      *
4892      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4893      *
4894      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4895      *
4896      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4897      */
4898     public void setFallbackLineSpacing(boolean enabled) {
4899         int fallbackStrategy;
4900         if (enabled) {
4901             if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
4902                 fallbackStrategy = FALLBACK_LINE_SPACING_ALL;
4903             } else {
4904                 fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
4905             }
4906         } else {
4907             fallbackStrategy = FALLBACK_LINE_SPACING_NONE;
4908         }
4909         if (mUseFallbackLineSpacing != fallbackStrategy) {
4910             mUseFallbackLineSpacing = fallbackStrategy;
4911             if (mLayout != null) {
4912                 nullLayouts();
4913                 requestLayout();
4914                 invalidate();
4915             }
4916         }
4917     }
4918 
4919     /**
4920      * Set true for using width of bounding box as a source of automatic line breaking and drawing.
4921      *
4922      * If this value is false, the TextView determines the View width, drawing offset and automatic
4923      * line breaking based on total advances as text widths. By setting true, use glyph bound's as a
4924      * source of text width.
4925      *
4926      * If the font used for this TextView has glyphs that has negative bearing X or glyph xMax is
4927      * greater than advance, the glyph clipping can be happened because the drawing area may be
4928      * bigger than advance. By setting this to true, the TextView will reserve more spaces for
4929      * drawing are, so clipping can be prevented.
4930      *
4931      * This value is true by default if the target API version is 35 or later.
4932      *
4933      * @param useBoundsForWidth true for using bounding box for width. false for using advances for
4934      *                          width.
4935      * @see #getUseBoundsForWidth()
4936      * @see #setShiftDrawingOffsetForStartOverhang(boolean)
4937      * @see #getShiftDrawingOffsetForStartOverhang()
4938      */
4939     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
4940     public void setUseBoundsForWidth(boolean useBoundsForWidth) {
4941         if (mUseBoundsForWidth != useBoundsForWidth) {
4942             mUseBoundsForWidth = useBoundsForWidth;
4943             if (mLayout != null) {
4944                 nullLayouts();
4945                 requestLayout();
4946                 invalidate();
4947             }
4948         }
4949     }
4950 
4951     /**
4952      * Returns true if using bounding box as a width, false for using advance as a width.
4953      *
4954      * @see #setUseBoundsForWidth(boolean)
4955      * @see #setShiftDrawingOffsetForStartOverhang(boolean)
4956      * @see #getShiftDrawingOffsetForStartOverhang()
4957      * @return True if using bounding box for width, false if using advance for width.
4958      */
4959     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
4960     public boolean getUseBoundsForWidth() {
4961         return mUseBoundsForWidth;
4962     }
4963 
4964     /**
4965      * Set true for shifting the drawing x offset for showing overhang at the start position.
4966      *
4967      * This flag is ignored if the {@link #getUseBoundsForWidth()} is false.
4968      *
4969      * If this value is false, the TextView draws text from the zero even if there is a glyph stroke
4970      * in a region where the x coordinate is negative. TextView clips the stroke in the region where
4971      * the X coordinate is negative unless the parents has {@link ViewGroup#getClipChildren()} to
4972      * true. This is useful for aligning multiple TextViews vertically.
4973      *
4974      * If this value is true, the TextView draws text with shifting the x coordinate of the drawing
4975      * bounding box. This prevents the clipping even if the parents doesn't have
4976      * {@link ViewGroup#getClipChildren()} to true.
4977      *
4978      * This value is false by default.
4979      *
4980      * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for showing
4981      *                                           the stroke that is in the region whre the x
4982      *                                           coorinate is negative.
4983      * @see #setUseBoundsForWidth(boolean)
4984      * @see #getUseBoundsForWidth()
4985      */
4986     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
4987     public void setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang) {
4988         if (mShiftDrawingOffsetForStartOverhang != shiftDrawingOffsetForStartOverhang) {
4989             mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
4990             if (mLayout != null) {
4991                 nullLayouts();
4992                 requestLayout();
4993                 invalidate();
4994             }
4995         }
4996     }
4997 
4998     /**
4999      * Returns true if shifting the drawing x offset for start overhang.
5000      *
5001      * @see #setShiftDrawingOffsetForStartOverhang(boolean)
5002      * @see #setUseBoundsForWidth(boolean)
5003      * @see #getUseBoundsForWidth()
5004      * @return True if shifting the drawing x offset for start overhang.
5005      */
5006     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
5007     public boolean getShiftDrawingOffsetForStartOverhang() {
5008         return mShiftDrawingOffsetForStartOverhang;
5009     }
5010 
5011     /**
5012      * Set the minimum font metrics used for line spacing.
5013      *
5014      * <p>
5015      * {@code null} is the default value. If {@code null} is set or left as default, the font
5016      * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
5017      *
5018      * <p>
5019      * The minimum meaning here is the minimum value of line spacing: maximum value of
5020      * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
5021      *
5022      * <p>
5023      * By setting this value, each line will have minimum line spacing regardless of the text
5024      * rendered. For example, usually Japanese script has larger vertical metrics than Latin script.
5025      * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
5026      * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese,
5027      * the line spacing for Japanese is reserved if the TextView contains English text. If the
5028      * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font
5029      * metrics is used.
5030      *
5031      * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value
5032      *                           obtained by
5033      *                           {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
5034      * @see #getMinimumFontMetrics()
5035      * @see Layout#getMinimumFontMetrics()
5036      * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5037      * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5038      * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5039      */
5040     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
5041     public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
5042         mMinimumFontMetrics = minimumFontMetrics;
5043     }
5044 
5045     /**
5046      * Get the minimum font metrics used for line spacing.
5047      *
5048      * @see #setMinimumFontMetrics(Paint.FontMetrics)
5049      * @see Layout#getMinimumFontMetrics()
5050      * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5051      * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5052      * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5053      *
5054      * @return a minimum font metrics. {@code null} for using the value obtained by
5055      *         {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
5056      */
5057     @Nullable
5058     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
5059     public Paint.FontMetrics getMinimumFontMetrics() {
5060         return mMinimumFontMetrics;
5061     }
5062 
5063     /**
5064      * Returns true if the locale preferred line height is used for the minimum line height.
5065      *
5066      * @return true if using locale preferred line height for the minimum line height. Otherwise
5067      *         false.
5068      *
5069      * @see #setLocalePreferredLineHeightForMinimumUsed(boolean)
5070      * @see #setMinimumFontMetrics(Paint.FontMetrics)
5071      * @see #getMinimumFontMetrics()
5072      */
5073     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
5074     public boolean isLocalePreferredLineHeightForMinimumUsed() {
5075         return mUseLocalePreferredLineHeightForMinimum;
5076     }
5077 
5078     /**
5079      * Set true if the locale preferred line height is used for the minimum line height.
5080      *
5081      * By setting this flag to true is equivalenet to call
5082      * {@link #setMinimumFontMetrics(Paint.FontMetrics)} with the one obtained by
5083      * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}.
5084      *
5085      * If custom minimum line height was specified by
5086      * {@link #setMinimumFontMetrics(Paint.FontMetrics)}, this flag will be ignored.
5087      *
5088      * @param flag true for using locale preferred line height for the minimum line height.
5089      * @see #isLocalePreferredLineHeightForMinimumUsed()
5090      * @see #setMinimumFontMetrics(Paint.FontMetrics)
5091      * @see #getMinimumFontMetrics()
5092      */
5093     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
5094     public void setLocalePreferredLineHeightForMinimumUsed(boolean flag) {
5095         mUseLocalePreferredLineHeightForMinimum = flag;
5096     }
5097 
5098     /**
5099      * @return whether fallback line spacing is enabled, {@code true} by default
5100      *
5101      * @see #setFallbackLineSpacing(boolean)
5102      *
5103      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
5104      */
5105     @InspectableProperty
5106     public boolean isFallbackLineSpacing() {
5107         return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE;
5108     }
5109 
5110     private boolean isFallbackLineSpacingForBoringLayout() {
5111         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL;
5112     }
5113 
5114     // Package privte for accessing from Editor.java
5115     /* package */ boolean isFallbackLineSpacingForStaticLayout() {
5116         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL
5117                 || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
5118     }
5119 
5120     /**
5121      * Get the value of the TextView's elegant height metrics flag. This setting selects font
5122      * variants that have not been compacted to fit Latin-based vertical
5123      * metrics, and also increases top and bottom bounds to provide more space.
5124      * @return {@code true} if the elegant height metrics flag is set.
5125      *
5126      * @see #setElegantTextHeight(boolean)
5127      * @see Paint#setElegantTextHeight(boolean)
5128      */
5129     @InspectableProperty
5130     public boolean isElegantTextHeight() {
5131         return mTextPaint.isElegantTextHeight();
5132     }
5133 
5134     /**
5135      * Gets the text letter-space value, which determines the spacing between characters.
5136      * The value returned is in ems. Normally, this value is 0.0.
5137      * @return The text letter-space value in ems.
5138      *
5139      * @see #setLetterSpacing(float)
5140      * @see Paint#setLetterSpacing
5141      */
5142     @InspectableProperty
5143     public float getLetterSpacing() {
5144         return mTextPaint.getLetterSpacing();
5145     }
5146 
5147     /**
5148      * Sets text letter-spacing in em units.  Typical values
5149      * for slight expansion will be around 0.05.  Negative values tighten text.
5150      *
5151      * @see #getLetterSpacing()
5152      * @see Paint#getLetterSpacing
5153      *
5154      * @param letterSpacing A text letter-space value in ems.
5155      * @attr ref android.R.styleable#TextView_letterSpacing
5156      */
5157     @android.view.RemotableViewMethod
5158     public void setLetterSpacing(float letterSpacing) {
5159         if (letterSpacing != mTextPaint.getLetterSpacing()) {
5160             mTextPaint.setLetterSpacing(letterSpacing);
5161 
5162             if (mLayout != null) {
5163                 nullLayouts();
5164                 requestLayout();
5165                 invalidate();
5166             }
5167         }
5168     }
5169 
5170     /**
5171      * Returns the font feature settings. The format is the same as the CSS
5172      * font-feature-settings attribute:
5173      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
5174      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
5175      *
5176      * @return the currently set font feature settings.  Default is null.
5177      *
5178      * @see #setFontFeatureSettings(String)
5179      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
5180      */
5181     @InspectableProperty
5182     @Nullable
5183     public String getFontFeatureSettings() {
5184         return mTextPaint.getFontFeatureSettings();
5185     }
5186 
5187     /**
5188      * Returns the font variation settings.
5189      *
5190      * @return the currently set font variation settings.  Returns null if no variation is
5191      * specified.
5192      *
5193      * @see #setFontVariationSettings(String)
5194      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
5195      */
5196     @Nullable
5197     public String getFontVariationSettings() {
5198         return mTextPaint.getFontVariationSettings();
5199     }
5200 
5201     /**
5202      * Sets the break strategy for breaking paragraphs into lines. The default value for
5203      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
5204      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
5205      * text "dancing" when being edited.
5206      * <p>
5207      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
5208      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
5209      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
5210      * improves the structure of text layout however has performance impact and requires more time
5211      * to do the text layout.</p>
5212      * <p>
5213      * Compared with {@link #setLineBreakStyle(int)}, line break style with different strictness is
5214      * evaluated in the ICU to identify the potential breakpoints. In
5215      * {@link #setBreakStrategy(int)}, line break strategy handles the post processing of ICU's line
5216      * break result. It aims to evaluate ICU's breakpoints and break the lines based on the
5217      * constraint.
5218      * </p>
5219      *
5220      * @attr ref android.R.styleable#TextView_breakStrategy
5221      * @see #getBreakStrategy()
5222      * @see #setHyphenationFrequency(int)
5223      */
5224     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
5225         mBreakStrategy = breakStrategy;
5226         if (mLayout != null) {
5227             nullLayouts();
5228             requestLayout();
5229             invalidate();
5230         }
5231     }
5232 
5233     /**
5234      * Gets the current strategy for breaking paragraphs into lines.
5235      * @return the current strategy for breaking paragraphs into lines.
5236      *
5237      * @attr ref android.R.styleable#TextView_breakStrategy
5238      * @see #setBreakStrategy(int)
5239      */
5240     @InspectableProperty(enumMapping = {
5241             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
5242             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
5243             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
5244     })
5245     @Layout.BreakStrategy
5246     public int getBreakStrategy() {
5247         return mBreakStrategy;
5248     }
5249 
5250     /**
5251      * Sets the frequency of automatic hyphenation to use when determining word breaks.
5252      * The default value for both TextView and {@link EditText} is
5253      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
5254      * is set from the theme.
5255      * <p/>
5256      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
5257      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
5258      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
5259      * improves the structure of text layout however has performance impact and requires more time
5260      * to do the text layout.
5261      * <p/>
5262      * Note: Before Android Q, in the theme hyphenation frequency is set to
5263      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
5264      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
5265      *
5266      * @param hyphenationFrequency the hyphenation frequency to use, one of
5267      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
5268      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
5269      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
5270      * @attr ref android.R.styleable#TextView_hyphenationFrequency
5271      * @see #getHyphenationFrequency()
5272      * @see #getBreakStrategy()
5273      */
5274     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
5275         mHyphenationFrequency = hyphenationFrequency;
5276         if (mLayout != null) {
5277             nullLayouts();
5278             requestLayout();
5279             invalidate();
5280         }
5281     }
5282 
5283     /**
5284      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
5285      * @return the current frequency of automatic hyphenation to be used when determining word
5286      * breaks.
5287      *
5288      * @attr ref android.R.styleable#TextView_hyphenationFrequency
5289      * @see #setHyphenationFrequency(int)
5290      */
5291     @InspectableProperty(enumMapping = {
5292             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
5293             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
5294             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
5295     })
5296     @Layout.HyphenationFrequency
5297     public int getHyphenationFrequency() {
5298         return mHyphenationFrequency;
5299     }
5300 
5301     /**
5302      * Sets the line-break style for text wrapping.
5303      *
5304      * <p>Line-break style specifies the line-break strategies that can be used
5305      * for text wrapping. The line-break style affects rule-based line breaking
5306      * by specifying the strictness of line-breaking rules.
5307      *
5308      * <p>The following are types of line-break styles:
5309      * <ul>
5310      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}
5311      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}
5312      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}
5313      * </ul>
5314      *
5315      * <p>The default line-break style is
5316      * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
5317      * line-breaking rules are used.
5318      *
5319      * <p>See the
5320      * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
5321      * line-break property</a> for more information.
5322      *
5323      * @param lineBreakStyle The line-break style for the text.
5324      */
5325     public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
5326         if (mLineBreakStyle != lineBreakStyle) {
5327             mLineBreakStyle = lineBreakStyle;
5328             if (mLayout != null) {
5329                 nullLayouts();
5330                 requestLayout();
5331                 invalidate();
5332             }
5333         }
5334     }
5335 
5336     /**
5337      * Sets the line-break word style for text wrapping.
5338      *
5339      * <p>The line-break word style affects dictionary-based line breaking by
5340      * providing phrase-based line-breaking opportunities. Use
5341      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
5342      * phrase-based line breaking.
5343      *
5344      * <p>The default line-break word style is
5345      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
5346      * no line-breaking word style is used.
5347      *
5348      * <p>See the
5349      * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
5350      * word-break property</a> for more information.
5351      *
5352      * @param lineBreakWordStyle The line-break word style for the text.
5353      */
5354     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
5355         if (mLineBreakWordStyle != lineBreakWordStyle) {
5356             mLineBreakWordStyle = lineBreakWordStyle;
5357             if (mLayout != null) {
5358                 nullLayouts();
5359                 requestLayout();
5360                 invalidate();
5361             }
5362         }
5363     }
5364 
5365     /**
5366      * Gets the current line-break style for text wrapping.
5367      *
5368      * @return The line-break style to be used for text wrapping.
5369      */
5370     public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
5371         return mLineBreakStyle;
5372     }
5373 
5374     /**
5375      * Gets the current line-break word style for text wrapping.
5376      *
5377      * @return The line-break word style to be used for text wrapping.
5378      */
5379     public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
5380         return mLineBreakWordStyle;
5381     }
5382 
5383     /**
5384      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
5385      *
5386      * @return a current {@link PrecomputedText.Params}
5387      * @see PrecomputedText
5388      */
5389     public @NonNull PrecomputedText.Params getTextMetricsParams() {
5390         return new PrecomputedText.Params(new TextPaint(mTextPaint),
5391                 LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
5392                 getTextDirectionHeuristic(),
5393                 mBreakStrategy, mHyphenationFrequency);
5394     }
5395 
5396     /**
5397      * Apply the text layout parameter.
5398      *
5399      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
5400      * @see PrecomputedText
5401      */
5402     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
5403         mTextPaint.set(params.getTextPaint());
5404         mUserSetTextScaleX = true;
5405         mTextDir = params.getTextDirection();
5406         mBreakStrategy = params.getBreakStrategy();
5407         mHyphenationFrequency = params.getHyphenationFrequency();
5408         LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
5409         mLineBreakStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig);
5410         mLineBreakWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig);
5411         if (mLayout != null) {
5412             nullLayouts();
5413             requestLayout();
5414             invalidate();
5415         }
5416     }
5417 
5418     /**
5419      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
5420      * last line is too short for justification, the last line will be displayed with the
5421      * alignment set by {@link android.view.View#setTextAlignment}.
5422      *
5423      * @see #getJustificationMode()
5424      */
5425     @Layout.JustificationMode
5426     @android.view.RemotableViewMethod
5427     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
5428         mJustificationMode = justificationMode;
5429         if (mLayout != null) {
5430             nullLayouts();
5431             requestLayout();
5432             invalidate();
5433         }
5434     }
5435 
5436     /**
5437      * @return true if currently paragraph justification mode.
5438      *
5439      * @see #setJustificationMode(int)
5440      */
5441     @InspectableProperty(enumMapping = {
5442             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
5443             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
5444     })
5445     public @Layout.JustificationMode int getJustificationMode() {
5446         return mJustificationMode;
5447     }
5448 
5449     /**
5450      * Sets font feature settings. The format is the same as the CSS
5451      * font-feature-settings attribute:
5452      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
5453      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
5454      *
5455      * @param fontFeatureSettings font feature settings represented as CSS compatible string
5456      *
5457      * @see #getFontFeatureSettings()
5458      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
5459      *
5460      * @attr ref android.R.styleable#TextView_fontFeatureSettings
5461      */
5462     @android.view.RemotableViewMethod
5463     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
5464         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
5465             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
5466 
5467             if (mLayout != null) {
5468                 nullLayouts();
5469                 requestLayout();
5470                 invalidate();
5471             }
5472         }
5473     }
5474 
5475 
5476     /**
5477      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
5478      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
5479      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
5480      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
5481      * are invalid. If a specified axis name is not defined in the font, the settings will be
5482      * ignored.
5483      *
5484      * <p>
5485      * Examples,
5486      * <ul>
5487      * <li>Set font width to 150.
5488      * <pre>
5489      * <code>
5490      *   TextView textView = (TextView) findViewById(R.id.textView);
5491      *   textView.setFontVariationSettings("'wdth' 150");
5492      * </code>
5493      * </pre>
5494      * </li>
5495      *
5496      * <li>Set the font slant to 20 degrees and ask for italic style.
5497      * <pre>
5498      * <code>
5499      *   TextView textView = (TextView) findViewById(R.id.textView);
5500      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
5501      * </code>
5502      * </pre>
5503      * </p>
5504      * </li>
5505      * </ul>
5506      *
5507      * @param fontVariationSettings font variation settings. You can pass null or empty string as
5508      *                              no variation settings.
5509      * @return true if the given settings is effective to at least one font file underlying this
5510      *         TextView. This function also returns true for empty settings string. Otherwise
5511      *         returns false.
5512      *
5513      * @throws IllegalArgumentException If given string is not a valid font variation settings
5514      *                                  format.
5515      *
5516      * @see #getFontVariationSettings()
5517      * @see FontVariationAxis
5518      *
5519      * @attr ref android.R.styleable#TextView_fontVariationSettings
5520      */
5521     @android.view.RemotableViewMethod
5522     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
5523         final String existingSettings = mTextPaint.getFontVariationSettings();
5524         if (fontVariationSettings == existingSettings
5525                 || (fontVariationSettings != null
5526                         && fontVariationSettings.equals(existingSettings))) {
5527             return true;
5528         }
5529         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
5530 
5531         if (effective && mLayout != null) {
5532             nullLayouts();
5533             requestLayout();
5534             invalidate();
5535         }
5536         return effective;
5537     }
5538 
5539     /**
5540      * Sets the text color for all the states (normal, selected,
5541      * focused) to be this color.
5542      *
5543      * @param color A color value in the form 0xAARRGGBB.
5544      * Do not pass a resource ID. To get a color value from a resource ID, call
5545      * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}.
5546      *
5547      * @see #setTextColor(ColorStateList)
5548      * @see #getTextColors()
5549      *
5550      * @attr ref android.R.styleable#TextView_textColor
5551      */
5552     @android.view.RemotableViewMethod
5553     public void setTextColor(@ColorInt int color) {
5554         mTextColor = ColorStateList.valueOf(color);
5555         updateTextColors();
5556     }
5557 
5558     /**
5559      * Sets the text color.
5560      *
5561      * @see #setTextColor(int)
5562      * @see #getTextColors()
5563      * @see #setHintTextColor(ColorStateList)
5564      * @see #setLinkTextColor(ColorStateList)
5565      *
5566      * @attr ref android.R.styleable#TextView_textColor
5567      */
5568     @android.view.RemotableViewMethod
5569     public void setTextColor(ColorStateList colors) {
5570         if (colors == null) {
5571             throw new NullPointerException();
5572         }
5573 
5574         mTextColor = colors;
5575         updateTextColors();
5576     }
5577 
5578     /**
5579      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
5580      *
5581      * @see #setTextColor(ColorStateList)
5582      * @see #setTextColor(int)
5583      *
5584      * @attr ref android.R.styleable#TextView_textColor
5585      */
5586     @InspectableProperty(name = "textColor")
5587     public final ColorStateList getTextColors() {
5588         return mTextColor;
5589     }
5590 
5591     /**
5592      * Return the current color selected for normal text.
5593      *
5594      * @return Returns the current text color.
5595      */
5596     @ColorInt
5597     public final int getCurrentTextColor() {
5598         return mCurTextColor;
5599     }
5600 
5601     /**
5602      * Sets the color used to display the selection highlight.
5603      *
5604      * @attr ref android.R.styleable#TextView_textColorHighlight
5605      */
5606     @android.view.RemotableViewMethod
5607     public void setHighlightColor(@ColorInt int color) {
5608         if (mHighlightColor != color) {
5609             mHighlightColor = color;
5610             invalidate();
5611         }
5612     }
5613 
5614     /**
5615      * @return the color used to display the selection highlight
5616      *
5617      * @see #setHighlightColor(int)
5618      *
5619      * @attr ref android.R.styleable#TextView_textColorHighlight
5620      */
5621     @InspectableProperty(name = "textColorHighlight")
5622     @ColorInt
5623     public int getHighlightColor() {
5624         return mHighlightColor;
5625     }
5626 
5627     /**
5628      * Sets whether the soft input method will be made visible when this
5629      * TextView gets focused. The default is true.
5630      */
5631     @android.view.RemotableViewMethod
5632     public final void setShowSoftInputOnFocus(boolean show) {
5633         createEditorIfNeeded();
5634         mEditor.mShowSoftInputOnFocus = show;
5635     }
5636 
5637     /**
5638      * Returns whether the soft input method will be made visible when this
5639      * TextView gets focused. The default is true.
5640      */
5641     public final boolean getShowSoftInputOnFocus() {
5642         // When there is no Editor, return default true value
5643         return mEditor == null || mEditor.mShowSoftInputOnFocus;
5644     }
5645 
5646     /**
5647      * Gives the text a shadow of the specified blur radius and color, the specified
5648      * distance from its drawn position.
5649      * <p>
5650      * The text shadow produced does not interact with the properties on view
5651      * that are responsible for real time shadows,
5652      * {@link View#getElevation() elevation} and
5653      * {@link View#getTranslationZ() translationZ}.
5654      *
5655      * @see Paint#setShadowLayer(float, float, float, int)
5656      *
5657      * @attr ref android.R.styleable#TextView_shadowColor
5658      * @attr ref android.R.styleable#TextView_shadowDx
5659      * @attr ref android.R.styleable#TextView_shadowDy
5660      * @attr ref android.R.styleable#TextView_shadowRadius
5661      */
5662     public void setShadowLayer(float radius, float dx, float dy, int color) {
5663         mTextPaint.setShadowLayer(radius, dx, dy, color);
5664 
5665         mShadowRadius = radius;
5666         mShadowDx = dx;
5667         mShadowDy = dy;
5668         mShadowColor = color;
5669 
5670         // Will change text clip region
5671         if (mEditor != null) {
5672             mEditor.invalidateTextDisplayList();
5673             mEditor.invalidateHandlesAndActionMode();
5674         }
5675         invalidate();
5676     }
5677 
5678     /**
5679      * Gets the radius of the shadow layer.
5680      *
5681      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
5682      *
5683      * @see #setShadowLayer(float, float, float, int)
5684      *
5685      * @attr ref android.R.styleable#TextView_shadowRadius
5686      */
5687     @InspectableProperty
5688     public float getShadowRadius() {
5689         return mShadowRadius;
5690     }
5691 
5692     /**
5693      * @return the horizontal offset of the shadow layer
5694      *
5695      * @see #setShadowLayer(float, float, float, int)
5696      *
5697      * @attr ref android.R.styleable#TextView_shadowDx
5698      */
5699     @InspectableProperty
5700     public float getShadowDx() {
5701         return mShadowDx;
5702     }
5703 
5704     /**
5705      * Gets the vertical offset of the shadow layer.
5706      * @return The vertical offset of the shadow layer.
5707      *
5708      * @see #setShadowLayer(float, float, float, int)
5709      *
5710      * @attr ref android.R.styleable#TextView_shadowDy
5711      */
5712     @InspectableProperty
5713     public float getShadowDy() {
5714         return mShadowDy;
5715     }
5716 
5717     /**
5718      * Gets the color of the shadow layer.
5719      * @return the color of the shadow layer
5720      *
5721      * @see #setShadowLayer(float, float, float, int)
5722      *
5723      * @attr ref android.R.styleable#TextView_shadowColor
5724      */
5725     @InspectableProperty
5726     @ColorInt
5727     public int getShadowColor() {
5728         return mShadowColor;
5729     }
5730 
5731     /**
5732      * Gets the {@link TextPaint} used for the text.
5733      * Use this only to consult the Paint's properties and not to change them.
5734      * @return The base paint used for the text.
5735      */
5736     public TextPaint getPaint() {
5737         return mTextPaint;
5738     }
5739 
5740     /**
5741      * Sets the autolink mask of the text.  See {@link
5742      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
5743      * possible values.
5744      *
5745      * <p class="note"><b>Note:</b>
5746      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
5747      * is deprecated and should be avoided; see its documentation.
5748      *
5749      * @attr ref android.R.styleable#TextView_autoLink
5750      */
5751     @android.view.RemotableViewMethod
5752     public final void setAutoLinkMask(int mask) {
5753         mAutoLinkMask = mask;
5754     }
5755 
5756     /**
5757      * Sets whether the movement method will automatically be set to
5758      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5759      * set to nonzero and links are detected in {@link #setText}.
5760      * The default is true.
5761      *
5762      * @attr ref android.R.styleable#TextView_linksClickable
5763      */
5764     @android.view.RemotableViewMethod
5765     public final void setLinksClickable(boolean whether) {
5766         mLinksClickable = whether;
5767     }
5768 
5769     /**
5770      * Returns whether the movement method will automatically be set to
5771      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5772      * set to nonzero and links are detected in {@link #setText}.
5773      * The default is true.
5774      *
5775      * @attr ref android.R.styleable#TextView_linksClickable
5776      */
5777     @InspectableProperty
5778     public final boolean getLinksClickable() {
5779         return mLinksClickable;
5780     }
5781 
5782     /**
5783      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5784      * (by {@link Linkify} or otherwise) if any.  You can call
5785      * {@link URLSpan#getURL} on them to find where they link to
5786      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5787      * to find the region of the text they are attached to.
5788      */
5789     public URLSpan[] getUrls() {
5790         if (mText instanceof Spanned) {
5791             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5792         } else {
5793             return new URLSpan[0];
5794         }
5795     }
5796 
5797     /**
5798      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5799      * TextView.
5800      *
5801      * @see #setHintTextColor(ColorStateList)
5802      * @see #getHintTextColors()
5803      * @see #setTextColor(int)
5804      *
5805      * @attr ref android.R.styleable#TextView_textColorHint
5806      */
5807     @android.view.RemotableViewMethod
5808     public final void setHintTextColor(@ColorInt int color) {
5809         mHintTextColor = ColorStateList.valueOf(color);
5810         updateTextColors();
5811     }
5812 
5813     /**
5814      * Sets the color of the hint text.
5815      *
5816      * @see #getHintTextColors()
5817      * @see #setHintTextColor(int)
5818      * @see #setTextColor(ColorStateList)
5819      * @see #setLinkTextColor(ColorStateList)
5820      *
5821      * @attr ref android.R.styleable#TextView_textColorHint
5822      */
5823     public final void setHintTextColor(ColorStateList colors) {
5824         mHintTextColor = colors;
5825         updateTextColors();
5826     }
5827 
5828     /**
5829      * @return the color of the hint text, for the different states of this TextView.
5830      *
5831      * @see #setHintTextColor(ColorStateList)
5832      * @see #setHintTextColor(int)
5833      * @see #setTextColor(ColorStateList)
5834      * @see #setLinkTextColor(ColorStateList)
5835      *
5836      * @attr ref android.R.styleable#TextView_textColorHint
5837      */
5838     @InspectableProperty(name = "textColorHint")
5839     public final ColorStateList getHintTextColors() {
5840         return mHintTextColor;
5841     }
5842 
5843     /**
5844      * <p>Return the current color selected to paint the hint text.</p>
5845      *
5846      * @return Returns the current hint text color.
5847      */
5848     @ColorInt
5849     public final int getCurrentHintTextColor() {
5850         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5851     }
5852 
5853     /**
5854      * Sets the color of links in the text.
5855      *
5856      * @see #setLinkTextColor(ColorStateList)
5857      * @see #getLinkTextColors()
5858      *
5859      * @attr ref android.R.styleable#TextView_textColorLink
5860      */
5861     @android.view.RemotableViewMethod
5862     public final void setLinkTextColor(@ColorInt int color) {
5863         mLinkTextColor = ColorStateList.valueOf(color);
5864         updateTextColors();
5865     }
5866 
5867     /**
5868      * Sets the color of links in the text.
5869      *
5870      * @see #setLinkTextColor(int)
5871      * @see #getLinkTextColors()
5872      * @see #setTextColor(ColorStateList)
5873      * @see #setHintTextColor(ColorStateList)
5874      *
5875      * @attr ref android.R.styleable#TextView_textColorLink
5876      */
5877     public final void setLinkTextColor(ColorStateList colors) {
5878         mLinkTextColor = colors;
5879         updateTextColors();
5880     }
5881 
5882     /**
5883      * @return the list of colors used to paint the links in the text, for the different states of
5884      * this TextView
5885      *
5886      * @see #setLinkTextColor(ColorStateList)
5887      * @see #setLinkTextColor(int)
5888      *
5889      * @attr ref android.R.styleable#TextView_textColorLink
5890      */
5891     @InspectableProperty(name = "textColorLink")
5892     public final ColorStateList getLinkTextColors() {
5893         return mLinkTextColor;
5894     }
5895 
5896     /**
5897      * Sets the horizontal alignment of the text and the
5898      * vertical gravity that will be used when there is extra space
5899      * in the TextView beyond what is required for the text itself.
5900      *
5901      * @see android.view.Gravity
5902      * @attr ref android.R.styleable#TextView_gravity
5903      */
5904     @android.view.RemotableViewMethod
5905     public void setGravity(int gravity) {
5906         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5907             gravity |= Gravity.START;
5908         }
5909         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5910             gravity |= Gravity.TOP;
5911         }
5912 
5913         boolean newLayout = false;
5914 
5915         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5916                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5917             newLayout = true;
5918         }
5919 
5920         if (gravity != mGravity) {
5921             invalidate();
5922         }
5923 
5924         mGravity = gravity;
5925 
5926         if (mLayout != null && newLayout) {
5927             // XXX this is heavy-handed because no actual content changes.
5928             int want = mLayout.getWidth();
5929             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5930 
5931             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5932                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5933         }
5934     }
5935 
5936     /**
5937      * Returns the horizontal and vertical alignment of this TextView.
5938      *
5939      * @see android.view.Gravity
5940      * @attr ref android.R.styleable#TextView_gravity
5941      */
5942     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
5943     public int getGravity() {
5944         return mGravity;
5945     }
5946 
5947     /**
5948      * Gets the flags on the Paint being used to display the text.
5949      * @return The flags on the Paint being used to display the text.
5950      * @see Paint#getFlags
5951      */
5952     public int getPaintFlags() {
5953         return mTextPaint.getFlags();
5954     }
5955 
5956     /**
5957      * Sets flags on the Paint being used to display the text and
5958      * reflows the text if they are different from the old flags.
5959      * @see Paint#setFlags
5960      */
5961     @android.view.RemotableViewMethod
5962     public void setPaintFlags(int flags) {
5963         if (mTextPaint.getFlags() != flags) {
5964             mTextPaint.setFlags(flags);
5965 
5966             if (mLayout != null) {
5967                 nullLayouts();
5968                 requestLayout();
5969                 invalidate();
5970             }
5971         }
5972     }
5973 
5974     /**
5975      * Sets whether the text should be allowed to be wider than the
5976      * View is.  If false, it will be wrapped to the width of the View.
5977      *
5978      * @attr ref android.R.styleable#TextView_scrollHorizontally
5979      */
5980     public void setHorizontallyScrolling(boolean whether) {
5981         if (mHorizontallyScrolling != whether) {
5982             mHorizontallyScrolling = whether;
5983 
5984             if (mLayout != null) {
5985                 nullLayouts();
5986                 requestLayout();
5987                 invalidate();
5988             }
5989         }
5990     }
5991 
5992     /**
5993      * Returns whether the text is allowed to be wider than the View.
5994      * If false, the text will be wrapped to the width of the View.
5995      *
5996      * @attr ref android.R.styleable#TextView_scrollHorizontally
5997      * @see #setHorizontallyScrolling(boolean)
5998      */
5999     @InspectableProperty(name = "scrollHorizontally")
6000     public final boolean isHorizontallyScrollable() {
6001         return mHorizontallyScrolling;
6002     }
6003 
6004     /**
6005      * Returns whether the text is allowed to be wider than the View.
6006      * If false, the text will be wrapped to the width of the View.
6007      *
6008      * @attr ref android.R.styleable#TextView_scrollHorizontally
6009      * @hide
6010      */
6011     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
6012     public boolean getHorizontallyScrolling() {
6013         return mHorizontallyScrolling;
6014     }
6015 
6016     /**
6017      * Sets the height of the TextView to be at least {@code minLines} tall.
6018      * <p>
6019      * This value is used for height calculation if LayoutParams does not force TextView to have an
6020      * exact height. Setting this value overrides other previous minimum height configurations such
6021      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
6022      * this value to 1.
6023      *
6024      * @param minLines the minimum height of TextView in terms of number of lines
6025      *
6026      * @see #getMinLines()
6027      * @see #setLines(int)
6028      *
6029      * @attr ref android.R.styleable#TextView_minLines
6030      */
6031     @android.view.RemotableViewMethod
6032     public void setMinLines(int minLines) {
6033         mMinimum = minLines;
6034         mMinMode = LINES;
6035 
6036         requestLayout();
6037         invalidate();
6038     }
6039 
6040     /**
6041      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
6042      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
6043      *
6044      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
6045      *         height is not defined in lines
6046      *
6047      * @see #setMinLines(int)
6048      * @see #setLines(int)
6049      *
6050      * @attr ref android.R.styleable#TextView_minLines
6051      */
6052     @InspectableProperty
6053     public int getMinLines() {
6054         return mMinMode == LINES ? mMinimum : -1;
6055     }
6056 
6057     /**
6058      * Sets the height of the TextView to be at least {@code minPixels} tall.
6059      * <p>
6060      * This value is used for height calculation if LayoutParams does not force TextView to have an
6061      * exact height. Setting this value overrides previous minimum height configurations such as
6062      * {@link #setMinLines(int)} or {@link #setLines(int)}.
6063      * <p>
6064      * The value given here is different than {@link #setMinimumHeight(int)}. Between
6065      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
6066      * used to decide the final height.
6067      *
6068      * @param minPixels the minimum height of TextView in terms of pixels
6069      *
6070      * @see #getMinHeight()
6071      * @see #setHeight(int)
6072      *
6073      * @attr ref android.R.styleable#TextView_minHeight
6074      */
6075     @android.view.RemotableViewMethod
6076     public void setMinHeight(int minPixels) {
6077         mMinimum = minPixels;
6078         mMinMode = PIXELS;
6079 
6080         requestLayout();
6081         invalidate();
6082     }
6083 
6084     /**
6085      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
6086      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
6087      *
6088      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
6089      *         defined in pixels
6090      *
6091      * @see #setMinHeight(int)
6092      * @see #setHeight(int)
6093      *
6094      * @attr ref android.R.styleable#TextView_minHeight
6095      */
6096     public int getMinHeight() {
6097         return mMinMode == PIXELS ? mMinimum : -1;
6098     }
6099 
6100     /**
6101      * Sets the height of the TextView to be at most {@code maxLines} tall.
6102      * <p>
6103      * This value is used for height calculation if LayoutParams does not force TextView to have an
6104      * exact height. Setting this value overrides previous maximum height configurations such as
6105      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
6106      *
6107      * @param maxLines the maximum height of TextView in terms of number of lines
6108      *
6109      * @see #getMaxLines()
6110      * @see #setLines(int)
6111      *
6112      * @attr ref android.R.styleable#TextView_maxLines
6113      */
6114     @android.view.RemotableViewMethod
6115     public void setMaxLines(int maxLines) {
6116         mMaximum = maxLines;
6117         mMaxMode = LINES;
6118 
6119         requestLayout();
6120         invalidate();
6121     }
6122 
6123     /**
6124      * Returns the maximum height of TextView in terms of number of lines or -1 if the
6125      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
6126      *
6127      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
6128      *         is not defined in lines.
6129      *
6130      * @see #setMaxLines(int)
6131      * @see #setLines(int)
6132      *
6133      * @attr ref android.R.styleable#TextView_maxLines
6134      */
6135     @InspectableProperty
6136     public int getMaxLines() {
6137         return mMaxMode == LINES ? mMaximum : -1;
6138     }
6139 
6140     /**
6141      * Sets the height of the TextView to be at most {@code maxPixels} tall.
6142      * <p>
6143      * This value is used for height calculation if LayoutParams does not force TextView to have an
6144      * exact height. Setting this value overrides previous maximum height configurations such as
6145      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
6146      *
6147      * @param maxPixels the maximum height of TextView in terms of pixels
6148      *
6149      * @see #getMaxHeight()
6150      * @see #setHeight(int)
6151      *
6152      * @attr ref android.R.styleable#TextView_maxHeight
6153      */
6154     @android.view.RemotableViewMethod
6155     public void setMaxHeight(int maxPixels) {
6156         mMaximum = maxPixels;
6157         mMaxMode = PIXELS;
6158 
6159         requestLayout();
6160         invalidate();
6161     }
6162 
6163     /**
6164      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
6165      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
6166      *
6167      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
6168      *         is not defined in pixels
6169      *
6170      * @see #setMaxHeight(int)
6171      * @see #setHeight(int)
6172      *
6173      * @attr ref android.R.styleable#TextView_maxHeight
6174      */
6175     @InspectableProperty
6176     public int getMaxHeight() {
6177         return mMaxMode == PIXELS ? mMaximum : -1;
6178     }
6179 
6180     /**
6181      * Sets the height of the TextView to be exactly {@code lines} tall.
6182      * <p>
6183      * This value is used for height calculation if LayoutParams does not force TextView to have an
6184      * exact height. Setting this value overrides previous minimum/maximum height configurations
6185      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
6186      * set this value to 1.
6187      *
6188      * @param lines the exact height of the TextView in terms of lines
6189      *
6190      * @see #setHeight(int)
6191      *
6192      * @attr ref android.R.styleable#TextView_lines
6193      */
6194     @android.view.RemotableViewMethod
6195     public void setLines(int lines) {
6196         mMaximum = mMinimum = lines;
6197         mMaxMode = mMinMode = LINES;
6198 
6199         requestLayout();
6200         invalidate();
6201     }
6202 
6203     /**
6204      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
6205      * <p>
6206      * This value is used for height calculation if LayoutParams does not force TextView to have an
6207      * exact height. Setting this value overrides previous minimum/maximum height configurations
6208      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
6209      *
6210      * @param pixels the exact height of the TextView in terms of pixels
6211      *
6212      * @see #setLines(int)
6213      *
6214      * @attr ref android.R.styleable#TextView_height
6215      */
6216     @android.view.RemotableViewMethod
6217     public void setHeight(int pixels) {
6218         mMaximum = mMinimum = pixels;
6219         mMaxMode = mMinMode = PIXELS;
6220 
6221         requestLayout();
6222         invalidate();
6223     }
6224 
6225     /**
6226      * Sets the width of the TextView to be at least {@code minEms} wide.
6227      * <p>
6228      * This value is used for width calculation if LayoutParams does not force TextView to have an
6229      * exact width. Setting this value overrides previous minimum width configurations such as
6230      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
6231      *
6232      * @param minEms the minimum width of TextView in terms of ems
6233      *
6234      * @see #getMinEms()
6235      * @see #setEms(int)
6236      *
6237      * @attr ref android.R.styleable#TextView_minEms
6238      */
6239     @android.view.RemotableViewMethod
6240     public void setMinEms(int minEms) {
6241         mMinWidth = minEms;
6242         mMinWidthMode = EMS;
6243 
6244         requestLayout();
6245         invalidate();
6246     }
6247 
6248     /**
6249      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
6250      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
6251      *
6252      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
6253      *         defined in ems
6254      *
6255      * @see #setMinEms(int)
6256      * @see #setEms(int)
6257      *
6258      * @attr ref android.R.styleable#TextView_minEms
6259      */
6260     @InspectableProperty
6261     public int getMinEms() {
6262         return mMinWidthMode == EMS ? mMinWidth : -1;
6263     }
6264 
6265     /**
6266      * Sets the width of the TextView to be at least {@code minPixels} wide.
6267      * <p>
6268      * This value is used for width calculation if LayoutParams does not force TextView to have an
6269      * exact width. Setting this value overrides previous minimum width configurations such as
6270      * {@link #setMinEms(int)} or {@link #setEms(int)}.
6271      * <p>
6272      * The value given here is different than {@link #setMinimumWidth(int)}. Between
6273      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
6274      * to decide the final width.
6275      *
6276      * @param minPixels the minimum width of TextView in terms of pixels
6277      *
6278      * @see #getMinWidth()
6279      * @see #setWidth(int)
6280      *
6281      * @attr ref android.R.styleable#TextView_minWidth
6282      */
6283     @android.view.RemotableViewMethod
6284     public void setMinWidth(int minPixels) {
6285         mMinWidth = minPixels;
6286         mMinWidthMode = PIXELS;
6287 
6288         requestLayout();
6289         invalidate();
6290     }
6291 
6292     /**
6293      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
6294      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
6295      *
6296      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
6297      *         defined in pixels
6298      *
6299      * @see #setMinWidth(int)
6300      * @see #setWidth(int)
6301      *
6302      * @attr ref android.R.styleable#TextView_minWidth
6303      */
6304     @InspectableProperty
6305     public int getMinWidth() {
6306         return mMinWidthMode == PIXELS ? mMinWidth : -1;
6307     }
6308 
6309     /**
6310      * Sets the width of the TextView to be at most {@code maxEms} wide.
6311      * <p>
6312      * This value is used for width calculation if LayoutParams does not force TextView to have an
6313      * exact width. Setting this value overrides previous maximum width configurations such as
6314      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
6315      *
6316      * @param maxEms the maximum width of TextView in terms of ems
6317      *
6318      * @see #getMaxEms()
6319      * @see #setEms(int)
6320      *
6321      * @attr ref android.R.styleable#TextView_maxEms
6322      */
6323     @android.view.RemotableViewMethod
6324     public void setMaxEms(int maxEms) {
6325         mMaxWidth = maxEms;
6326         mMaxWidthMode = EMS;
6327 
6328         requestLayout();
6329         invalidate();
6330     }
6331 
6332     /**
6333      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
6334      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
6335      *
6336      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
6337      *         defined in ems
6338      *
6339      * @see #setMaxEms(int)
6340      * @see #setEms(int)
6341      *
6342      * @attr ref android.R.styleable#TextView_maxEms
6343      */
6344     @InspectableProperty
6345     public int getMaxEms() {
6346         return mMaxWidthMode == EMS ? mMaxWidth : -1;
6347     }
6348 
6349     /**
6350      * Sets the width of the TextView to be at most {@code maxPixels} wide.
6351      * <p>
6352      * This value is used for width calculation if LayoutParams does not force TextView to have an
6353      * exact width. Setting this value overrides previous maximum width configurations such as
6354      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
6355      *
6356      * @param maxPixels the maximum width of TextView in terms of pixels
6357      *
6358      * @see #getMaxWidth()
6359      * @see #setWidth(int)
6360      *
6361      * @attr ref android.R.styleable#TextView_maxWidth
6362      */
6363     @android.view.RemotableViewMethod
6364     public void setMaxWidth(int maxPixels) {
6365         mMaxWidth = maxPixels;
6366         mMaxWidthMode = PIXELS;
6367 
6368         requestLayout();
6369         invalidate();
6370     }
6371 
6372     /**
6373      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
6374      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
6375      *
6376      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
6377      *         defined in pixels
6378      *
6379      * @see #setMaxWidth(int)
6380      * @see #setWidth(int)
6381      *
6382      * @attr ref android.R.styleable#TextView_maxWidth
6383      */
6384     @InspectableProperty
6385     public int getMaxWidth() {
6386         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
6387     }
6388 
6389     /**
6390      * Sets the width of the TextView to be exactly {@code ems} wide.
6391      *
6392      * This value is used for width calculation if LayoutParams does not force TextView to have an
6393      * exact width. Setting this value overrides previous minimum/maximum configurations such as
6394      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
6395      *
6396      * @param ems the exact width of the TextView in terms of ems
6397      *
6398      * @see #setWidth(int)
6399      *
6400      * @attr ref android.R.styleable#TextView_ems
6401      */
6402     @android.view.RemotableViewMethod
6403     public void setEms(int ems) {
6404         mMaxWidth = mMinWidth = ems;
6405         mMaxWidthMode = mMinWidthMode = EMS;
6406 
6407         requestLayout();
6408         invalidate();
6409     }
6410 
6411     /**
6412      * Sets the width of the TextView to be exactly {@code pixels} wide.
6413      * <p>
6414      * This value is used for width calculation if LayoutParams does not force TextView to have an
6415      * exact width. Setting this value overrides previous minimum/maximum width configurations
6416      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
6417      *
6418      * @param pixels the exact width of the TextView in terms of pixels
6419      *
6420      * @see #setEms(int)
6421      *
6422      * @attr ref android.R.styleable#TextView_width
6423      */
6424     @android.view.RemotableViewMethod
6425     public void setWidth(int pixels) {
6426         mMaxWidth = mMinWidth = pixels;
6427         mMaxWidthMode = mMinWidthMode = PIXELS;
6428 
6429         requestLayout();
6430         invalidate();
6431     }
6432 
6433     /**
6434      * Sets line spacing for this TextView.  Each line other than the last line will have its height
6435      * multiplied by {@code mult} and have {@code add} added to it.
6436      *
6437      * @param add The value in pixels that should be added to each line other than the last line.
6438      *            This will be applied after the multiplier
6439      * @param mult The value by which each line height other than the last line will be multiplied
6440      *             by
6441      *
6442      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6443      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6444      */
6445     public void setLineSpacing(float add, float mult) {
6446         if (mSpacingAdd != add || mSpacingMult != mult) {
6447             mSpacingAdd = add;
6448             mSpacingMult = mult;
6449 
6450             if (mLayout != null) {
6451                 nullLayouts();
6452                 requestLayout();
6453                 invalidate();
6454             }
6455         }
6456     }
6457 
6458     /**
6459      * Gets the line spacing multiplier
6460      *
6461      * @return the value by which each line's height is multiplied to get its actual height.
6462      *
6463      * @see #setLineSpacing(float, float)
6464      * @see #getLineSpacingExtra()
6465      *
6466      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6467      */
6468     @InspectableProperty
6469     public float getLineSpacingMultiplier() {
6470         return mSpacingMult;
6471     }
6472 
6473     /**
6474      * Gets the line spacing extra space
6475      *
6476      * @return the extra space that is added to the height of each lines of this TextView.
6477      *
6478      * @see #setLineSpacing(float, float)
6479      * @see #getLineSpacingMultiplier()
6480      *
6481      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6482      */
6483     @InspectableProperty
6484     public float getLineSpacingExtra() {
6485         return mSpacingAdd;
6486     }
6487 
6488     /**
6489      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
6490      * between subsequent baselines in the TextView.
6491      *
6492      * @param lineHeight the line height in pixels
6493      *
6494      * @see #setLineSpacing(float, float)
6495      * @see #getLineSpacingExtra()
6496      *
6497      * @attr ref android.R.styleable#TextView_lineHeight
6498      */
6499     @android.view.RemotableViewMethod
6500     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
6501         setLineHeightPx(lineHeight);
6502     }
6503 
6504     private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) {
6505         Preconditions.checkArgumentNonNegative(lineHeight,
6506                 "Expecting non-negative lineHeight while the input is " + lineHeight);
6507 
6508         final int fontHeight = getPaint().getFontMetricsInt(null);
6509         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
6510         // TODO(b/274974975): should this also check if lineSpacing needs to change?
6511         if (lineHeight != fontHeight) {
6512             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
6513             setLineSpacing(lineHeight - fontHeight, 1f);
6514 
6515             mLineHeightComplexDimen =
6516                         TypedValue.createComplexDimension(lineHeight, TypedValue.COMPLEX_UNIT_PX);
6517         }
6518     }
6519 
6520     /**
6521      * Sets an explicit line height to a given unit and value for this TextView. This is equivalent
6522      * to the vertical distance between subsequent baselines in the TextView. See {@link
6523      * TypedValue} for the possible dimension units.
6524      *
6525      * @param unit The desired dimension unit. SP units are strongly recommended so that line height
6526      *             stays proportional to the text size when fonts are scaled up for accessibility.
6527      * @param lineHeight The desired line height in the given units.
6528      *
6529      * @see #setLineSpacing(float, float)
6530      * @see #getLineSpacingExtra()
6531      *
6532      * @attr ref android.R.styleable#TextView_lineHeight
6533      */
6534     @android.view.RemotableViewMethod
6535     public void setLineHeight(
6536             @TypedValue.ComplexDimensionUnit int unit,
6537             @FloatRange(from = 0) float lineHeight
6538     ) {
6539         var metrics = getDisplayMetricsOrSystem();
6540         // We can avoid the recalculation if we know non-linear font scaling isn't being used
6541         // (an optimization for the majority case).
6542         // We also don't try to do the recalculation unless both textSize and lineHeight are in SP.
6543         if (!FontScaleConverterFactory.isNonLinearFontScalingActive(
6544                     getResources().getConfiguration().fontScale)
6545                 || unit != TypedValue.COMPLEX_UNIT_SP
6546                 || mTextSizeUnit != TypedValue.COMPLEX_UNIT_SP
6547         ) {
6548             setLineHeightPx(TypedValue.applyDimension(unit, lineHeight, metrics));
6549 
6550             // Do this last so it overwrites what setLineHeightPx() sets it to.
6551             mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
6552             return;
6553         }
6554 
6555         // Recalculate a proportional line height when non-linear font scaling is in effect.
6556         // Otherwise, a desired 2x line height at font scale 1.0 will not be 2x at font scale 2.0,
6557         // due to non-linear font scaling compressing higher SP sizes. See b/273326061 for details.
6558         // We know they are using SP units for both the text size and the line height
6559         // at this point, so determine the ratio between them. This is the *intended* line spacing
6560         // multiplier if font scale == 1.0. We can then determine what the pixel value for the line
6561         // height would be if we preserved proportions.
6562         var textSizePx = getTextSize();
6563         var textSizeSp = TypedValue.convertPixelsToDimension(
6564                 TypedValue.COMPLEX_UNIT_SP,
6565                 textSizePx,
6566                 metrics
6567         );
6568         var ratio = lineHeight / textSizeSp;
6569         setLineHeightPx(textSizePx * ratio);
6570 
6571         // Do this last so it overwrites what setLineHeightPx() sets it to.
6572         mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
6573     }
6574 
6575     private void maybeRecalculateLineHeight() {
6576         if (mLineHeightComplexDimen == 0) {
6577             return;
6578         }
6579         int unit = TypedValue.getUnitFromComplexDimension(mLineHeightComplexDimen);
6580         if (unit != TypedValue.COMPLEX_UNIT_SP) {
6581             // The lineHeight was never supplied in SP, so we didn't do any fancy recalculations
6582             // in setLineHeight(). We don't need to recalculate.
6583             return;
6584         }
6585 
6586         setLineHeight(unit, TypedValue.complexToFloat(mLineHeightComplexDimen));
6587     }
6588 
6589     /**
6590      * Set Highlights
6591      *
6592      * @param highlights A highlight object. Call with null for reset.
6593      *
6594      * @see #getHighlights()
6595      * @see Highlights
6596      */
6597     public void setHighlights(@Nullable Highlights highlights) {
6598         mHighlights = highlights;
6599         mHighlightPathsBogus = true;
6600         invalidate();
6601     }
6602 
6603     /**
6604      * Returns highlights
6605      *
6606      * @return a highlight to be drawn. null if no highlight was set.
6607      *
6608      * @see #setHighlights(Highlights)
6609      * @see Highlights
6610      *
6611      */
6612     @Nullable
6613     public Highlights getHighlights() {
6614         return mHighlights;
6615     }
6616 
6617     /**
6618      * Sets the search result ranges with flatten range representation.
6619      *
6620      * Ranges are represented of flattened inclusive start and exclusive end integers array. The
6621      * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array.
6622      * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the
6623      * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array
6624      * [1, 2, 3, 4].
6625      *
6626      * TextView will render the search result with the highlights with specified color in the theme.
6627      * If there is a focused search result, it is rendered with focused color. By calling this
6628      * method, the focused search index will be cleared.
6629      *
6630      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6631      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6632      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6633      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6634      *
6635      * @see #getSearchResultHighlights()
6636      * @see #setFocusedSearchResultIndex(int)
6637      * @see #getFocusedSearchResultIndex()
6638      * @see #setSearchResultHighlightColor(int)
6639      * @see #getSearchResultHighlightColor()
6640      * @see #setFocusedSearchResultHighlightColor(int)
6641      * @see #getFocusedSearchResultHighlightColor()
6642      *
6643      * @param ranges the flatten ranges of the search result. null for clear.
6644      */
6645     public void setSearchResultHighlights(@Nullable int... ranges) {
6646         if (ranges == null) {
6647             mSearchResultHighlights = null;
6648             mHighlightPathsBogus = true;
6649             return;
6650         }
6651         if (ranges.length % 2 == 1) {
6652             throw new IllegalArgumentException(
6653                     "Flatten ranges must have even numbered elements");
6654         }
6655         for (int j = 0; j < ranges.length / 2; ++j) {
6656             int start = ranges[j * 2];
6657             int end = ranges[j * 2 + 1];
6658             if (start > end) {
6659                 throw new IllegalArgumentException(
6660                         "Reverse range found in the flatten range: " + start + ", " + end + ""
6661                                 + " at " + j + "-th range");
6662             }
6663         }
6664         mHighlightPathsBogus = true;
6665         mSearchResultHighlights = ranges;
6666         mFocusedSearchResultIndex = FOCUSED_SEARCH_RESULT_INDEX_NONE;
6667         invalidate();
6668     }
6669 
6670     /**
6671      * Gets the current search result ranges.
6672      *
6673      * @see #setSearchResultHighlights(int[])
6674      * @see #setFocusedSearchResultIndex(int)
6675      * @see #getFocusedSearchResultIndex()
6676      * @see #setSearchResultHighlightColor(int)
6677      * @see #getSearchResultHighlightColor()
6678      * @see #setFocusedSearchResultHighlightColor(int)
6679      * @see #getFocusedSearchResultHighlightColor()
6680      *
6681      * @return a flatten search result ranges. null if not available.
6682      */
6683     @Nullable
6684     public int[] getSearchResultHighlights() {
6685         return mSearchResultHighlights;
6686     }
6687 
6688     /**
6689      * A special index used for {@link #setFocusedSearchResultIndex(int)} and
6690      * {@link #getFocusedSearchResultIndex()} inidicating there is no focused search result.
6691      */
6692     public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1;
6693 
6694     /**
6695      * Sets the focused search result index.
6696      *
6697      * The focused search result is drawn in a focused color.
6698      * Calling {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} for clearing focused search result.
6699      *
6700      * This method must be called after setting search result ranges by
6701      * {@link #setSearchResultHighlights(int[])}.
6702      *
6703      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6704      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6705      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6706      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6707      *
6708      * @see #setSearchResultHighlights(int[])
6709      * @see #getSearchResultHighlights()
6710      * @see #setFocusedSearchResultIndex(int)
6711      * @see #getFocusedSearchResultIndex()
6712      * @see #setSearchResultHighlightColor(int)
6713      * @see #getSearchResultHighlightColor()
6714      * @see #setFocusedSearchResultHighlightColor(int)
6715      * @see #getFocusedSearchResultHighlightColor()
6716      *
6717      * @param index a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
6718      */
6719     public void setFocusedSearchResultIndex(int index) {
6720         if (mSearchResultHighlights == null) {
6721             throw new IllegalArgumentException("Search result range must be set beforehand.");
6722         }
6723         if (index < -1 || index >= mSearchResultHighlights.length / 2) {
6724             throw new IllegalArgumentException("Focused index(" + index + ") must be larger than "
6725                     + "-1 and less than range count(" + (mSearchResultHighlights.length / 2) + ")");
6726         }
6727         mFocusedSearchResultIndex = index;
6728         mHighlightPathsBogus = true;
6729         invalidate();
6730     }
6731 
6732     /**
6733      * Gets the focused search result index.
6734      *
6735      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6736      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6737      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6738      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6739      *
6740      * @see #setSearchResultHighlights(int[])
6741      * @see #getSearchResultHighlights()
6742      * @see #setFocusedSearchResultIndex(int)
6743      * @see #getFocusedSearchResultIndex()
6744      * @see #setSearchResultHighlightColor(int)
6745      * @see #getSearchResultHighlightColor()
6746      * @see #setFocusedSearchResultHighlightColor(int)
6747      * @see #getFocusedSearchResultHighlightColor()
6748 
6749      * @return a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
6750      */
6751     public int getFocusedSearchResultIndex() {
6752         return mFocusedSearchResultIndex;
6753     }
6754 
6755     /**
6756      * Sets the search result highlight color.
6757      *
6758      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6759      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6760      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6761      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6762      *
6763      * @see #setSearchResultHighlights(int[])
6764      * @see #getSearchResultHighlights()
6765      * @see #setFocusedSearchResultIndex(int)
6766      * @see #getFocusedSearchResultIndex()
6767      * @see #setSearchResultHighlightColor(int)
6768      * @see #getSearchResultHighlightColor()
6769      * @see #setFocusedSearchResultHighlightColor(int)
6770      * @see #getFocusedSearchResultHighlightColor()
6771 
6772      * @param color a search result highlight color.
6773      */
6774     public void setSearchResultHighlightColor(@ColorInt int color) {
6775         mSearchResultHighlightColor = color;
6776     }
6777 
6778     /**
6779      * Gets the search result highlight color.
6780      *
6781      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6782      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6783      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6784      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6785      *
6786      * @see #setSearchResultHighlights(int[])
6787      * @see #getSearchResultHighlights()
6788      * @see #setFocusedSearchResultIndex(int)
6789      * @see #getFocusedSearchResultIndex()
6790      * @see #setSearchResultHighlightColor(int)
6791      * @see #getSearchResultHighlightColor()
6792      * @see #setFocusedSearchResultHighlightColor(int)
6793      * @see #getFocusedSearchResultHighlightColor()
6794 
6795      * @return a search result highlight color.
6796      */
6797     @ColorInt
6798     public int getSearchResultHighlightColor() {
6799         return mSearchResultHighlightColor;
6800     }
6801 
6802     /**
6803      * Sets focused search result highlight color.
6804      *
6805      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6806      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6807      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6808      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6809      *
6810      * @see #setSearchResultHighlights(int[])
6811      * @see #getSearchResultHighlights()
6812      * @see #setFocusedSearchResultIndex(int)
6813      * @see #getFocusedSearchResultIndex()
6814      * @see #setSearchResultHighlightColor(int)
6815      * @see #getSearchResultHighlightColor()
6816      * @see #setFocusedSearchResultHighlightColor(int)
6817      * @see #getFocusedSearchResultHighlightColor()
6818 
6819      * @param color a focused search result highlight color.
6820      */
6821     public void setFocusedSearchResultHighlightColor(@ColorInt int color) {
6822         mFocusedSearchResultHighlightColor = color;
6823     }
6824 
6825     /**
6826      * Gets focused search result highlight color.
6827      *
6828      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6829      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6830      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6831      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6832      *
6833      * @see #setSearchResultHighlights(int[])
6834      * @see #getSearchResultHighlights()
6835      * @see #setFocusedSearchResultIndex(int)
6836      * @see #getFocusedSearchResultIndex()
6837      * @see #setSearchResultHighlightColor(int)
6838      * @see #getSearchResultHighlightColor()
6839      * @see #setFocusedSearchResultHighlightColor(int)
6840      * @see #getFocusedSearchResultHighlightColor()
6841 
6842      * @return a focused search result highlight color.
6843      */
6844     @ColorInt
6845     public int getFocusedSearchResultHighlightColor() {
6846         return mFocusedSearchResultHighlightColor;
6847     }
6848 
6849     /**
6850      * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
6851      * will be selected by the ongoing select handwriting gesture. While the gesture preview
6852      * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
6853      * the gesture preview highlight will be cleared.
6854      */
6855     private void setSelectGesturePreviewHighlight(int start, int end) {
6856         // Selection preview highlight color is the same as selection highlight color.
6857         setGesturePreviewHighlight(start, end, mHighlightColor);
6858     }
6859 
6860     /**
6861      * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
6862      * will be deleted by the ongoing delete handwriting gesture. While the gesture preview
6863      * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
6864      * the gesture preview highlight will be cleared.
6865      */
6866     private void setDeleteGesturePreviewHighlight(int start, int end) {
6867         // Deletion preview highlight color is 20% opacity of the default text color.
6868         int color = mTextColor.getDefaultColor();
6869         color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
6870         setGesturePreviewHighlight(start, end, color);
6871     }
6872 
6873     private void setGesturePreviewHighlight(int start, int end, int color) {
6874         mGesturePreviewHighlightStart = start;
6875         mGesturePreviewHighlightEnd = end;
6876         if (mGesturePreviewHighlightPaint == null) {
6877             mGesturePreviewHighlightPaint = new Paint();
6878             mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL);
6879         }
6880         mGesturePreviewHighlightPaint.setColor(color);
6881 
6882         if (mEditor != null) {
6883             mEditor.hideCursorAndSpanControllers();
6884             mEditor.stopTextActionModeWithPreservingSelection();
6885         }
6886 
6887         mHighlightPathsBogus = true;
6888         invalidate();
6889     }
6890 
6891     private void clearGesturePreviewHighlight() {
6892         mGesturePreviewHighlightStart = -1;
6893         mGesturePreviewHighlightEnd = -1;
6894         mHighlightPathsBogus = true;
6895         invalidate();
6896     }
6897 
6898     boolean hasGesturePreviewHighlight() {
6899         return mGesturePreviewHighlightStart >= 0;
6900     }
6901 
6902     /**
6903      * Convenience method to append the specified text to the TextView's
6904      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6905      * if it was not already editable.
6906      *
6907      * @param text text to be appended to the already displayed text
6908      */
6909     public final void append(CharSequence text) {
6910         append(text, 0, text.length());
6911     }
6912 
6913     /**
6914      * Convenience method to append the specified text slice to the TextView's
6915      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6916      * if it was not already editable.
6917      *
6918      * @param text text to be appended to the already displayed text
6919      * @param start the index of the first character in the {@code text}
6920      * @param end the index of the character following the last character in the {@code text}
6921      *
6922      * @see Appendable#append(CharSequence, int, int)
6923      */
6924     public void append(CharSequence text, int start, int end) {
6925         if (!(mText instanceof Editable)) {
6926             setText(mText, BufferType.EDITABLE);
6927         }
6928 
6929         ((Editable) mText).append(text, start, end);
6930 
6931         if (mAutoLinkMask != 0) {
6932             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
6933             // Do not change the movement method for text that support text selection as it
6934             // would prevent an arbitrary cursor displacement.
6935             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
6936                 setMovementMethod(LinkMovementMethod.getInstance());
6937             }
6938         }
6939     }
6940 
6941     private void updateTextColors() {
6942         boolean inval = false;
6943         final int[] drawableState = getDrawableState();
6944         int color = mTextColor.getColorForState(drawableState, 0);
6945         if (color != mCurTextColor) {
6946             mCurTextColor = color;
6947             inval = true;
6948         }
6949         if (mLinkTextColor != null) {
6950             color = mLinkTextColor.getColorForState(drawableState, 0);
6951             if (color != mTextPaint.linkColor) {
6952                 mTextPaint.linkColor = color;
6953                 inval = true;
6954             }
6955         }
6956         if (mHintTextColor != null) {
6957             color = mHintTextColor.getColorForState(drawableState, 0);
6958             if (color != mCurHintTextColor) {
6959                 mCurHintTextColor = color;
6960                 if (mText.length() == 0) {
6961                     inval = true;
6962                 }
6963             }
6964         }
6965         if (inval) {
6966             // Text needs to be redrawn with the new color
6967             if (mEditor != null) mEditor.invalidateTextDisplayList();
6968             invalidate();
6969         }
6970     }
6971 
6972     @Override
6973     protected void drawableStateChanged() {
6974         super.drawableStateChanged();
6975 
6976         if (mTextColor != null && mTextColor.isStateful()
6977                 || (mHintTextColor != null && mHintTextColor.isStateful())
6978                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
6979             updateTextColors();
6980         }
6981 
6982         if (mDrawables != null) {
6983             final int[] state = getDrawableState();
6984             for (Drawable dr : mDrawables.mShowing) {
6985                 if (dr != null && dr.isStateful() && dr.setState(state)) {
6986                     invalidateDrawable(dr);
6987                 }
6988             }
6989         }
6990     }
6991 
6992     @Override
6993     public void drawableHotspotChanged(float x, float y) {
6994         super.drawableHotspotChanged(x, y);
6995 
6996         if (mDrawables != null) {
6997             for (Drawable dr : mDrawables.mShowing) {
6998                 if (dr != null) {
6999                     dr.setHotspot(x, y);
7000                 }
7001             }
7002         }
7003     }
7004 
7005     @Override
7006     public Parcelable onSaveInstanceState() {
7007         Parcelable superState = super.onSaveInstanceState();
7008 
7009         // Save state if we are forced to
7010         final boolean freezesText = getFreezesText();
7011         boolean hasSelection = false;
7012         int start = -1;
7013         int end = -1;
7014 
7015         if (mText != null) {
7016             start = getSelectionStart();
7017             end = getSelectionEnd();
7018             if (start >= 0 || end >= 0) {
7019                 // Or save state if there is a selection
7020                 hasSelection = true;
7021             }
7022         }
7023 
7024         if (freezesText || hasSelection) {
7025             SavedState ss = new SavedState(superState);
7026 
7027             if (freezesText) {
7028                 if (mText instanceof Spanned) {
7029                     final Spannable sp = new SpannableStringBuilder(mText);
7030 
7031                     if (mEditor != null) {
7032                         removeMisspelledSpans(sp);
7033                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
7034                     }
7035 
7036                     ss.text = sp;
7037                 } else {
7038                     ss.text = mText.toString();
7039                 }
7040             }
7041 
7042             if (hasSelection) {
7043                 // XXX Should also save the current scroll position!
7044                 ss.selStart = start;
7045                 ss.selEnd = end;
7046             }
7047 
7048             if (isFocused() && start >= 0 && end >= 0) {
7049                 ss.frozenWithFocus = true;
7050             }
7051 
7052             ss.error = getError();
7053 
7054             if (mEditor != null) {
7055                 ss.editorState = mEditor.saveInstanceState();
7056             }
7057             return ss;
7058         }
7059 
7060         return superState;
7061     }
7062 
7063     void removeMisspelledSpans(Spannable spannable) {
7064         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
7065                 SuggestionSpan.class);
7066         for (int i = 0; i < suggestionSpans.length; i++) {
7067             int flags = suggestionSpans[i].getFlags();
7068             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
7069                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
7070                 spannable.removeSpan(suggestionSpans[i]);
7071             }
7072         }
7073     }
7074 
7075     @Override
7076     public void onRestoreInstanceState(Parcelable state) {
7077         if (!(state instanceof SavedState)) {
7078             super.onRestoreInstanceState(state);
7079             return;
7080         }
7081 
7082         SavedState ss = (SavedState) state;
7083         super.onRestoreInstanceState(ss.getSuperState());
7084 
7085         // XXX restore buffer type too, as well as lots of other stuff
7086         if (ss.text != null) {
7087             setText(ss.text);
7088         }
7089 
7090         if (ss.selStart >= 0 && ss.selEnd >= 0) {
7091             if (mSpannable != null) {
7092                 int len = mText.length();
7093 
7094                 if (ss.selStart > len || ss.selEnd > len) {
7095                     String restored = "";
7096 
7097                     if (ss.text != null) {
7098                         restored = "(restored) ";
7099                     }
7100 
7101                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
7102                             + " out of range for " + restored + "text " + mText);
7103                 } else {
7104                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
7105 
7106                     if (ss.frozenWithFocus) {
7107                         createEditorIfNeeded();
7108                         mEditor.mFrozenWithFocus = true;
7109                     }
7110                 }
7111             }
7112         }
7113 
7114         if (ss.error != null) {
7115             final CharSequence error = ss.error;
7116             // Display the error later, after the first layout pass
7117             post(new Runnable() {
7118                 public void run() {
7119                     if (mEditor == null || !mEditor.mErrorWasChanged) {
7120                         setError(error);
7121                     }
7122                 }
7123             });
7124         }
7125 
7126         if (ss.editorState != null) {
7127             createEditorIfNeeded();
7128             mEditor.restoreInstanceState(ss.editorState);
7129         }
7130     }
7131 
7132     /**
7133      * Control whether this text view saves its entire text contents when
7134      * freezing to an icicle, in addition to dynamic state such as cursor
7135      * position.  By default this is false, not saving the text.  Set to true
7136      * if the text in the text view is not being saved somewhere else in
7137      * persistent storage (such as in a content provider) so that if the
7138      * view is later thawed the user will not lose their data. For
7139      * {@link android.widget.EditText} it is always enabled, regardless of
7140      * the value of the attribute.
7141      *
7142      * @param freezesText Controls whether a frozen icicle should include the
7143      * entire text data: true to include it, false to not.
7144      *
7145      * @attr ref android.R.styleable#TextView_freezesText
7146      */
7147     @android.view.RemotableViewMethod
7148     public void setFreezesText(boolean freezesText) {
7149         mFreezesText = freezesText;
7150     }
7151 
7152     /**
7153      * Return whether this text view is including its entire text contents
7154      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
7155      *
7156      * @return Returns true if text is included, false if it isn't.
7157      *
7158      * @see #setFreezesText
7159      */
7160     @InspectableProperty
7161     public boolean getFreezesText() {
7162         return mFreezesText;
7163     }
7164 
7165     ///////////////////////////////////////////////////////////////////////////
7166 
7167     /**
7168      * Sets the Factory used to create new {@link Editable Editables}.
7169      *
7170      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
7171      *
7172      * @see android.text.Editable.Factory
7173      * @see android.widget.TextView.BufferType#EDITABLE
7174      */
7175     public final void setEditableFactory(Editable.Factory factory) {
7176         mEditableFactory = factory;
7177         setText(mText);
7178     }
7179 
7180     /**
7181      * Sets the Factory used to create new {@link Spannable Spannables}.
7182      *
7183      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
7184      *
7185      * @see android.text.Spannable.Factory
7186      * @see android.widget.TextView.BufferType#SPANNABLE
7187      */
7188     public final void setSpannableFactory(Spannable.Factory factory) {
7189         mSpannableFactory = factory;
7190         setText(mText);
7191     }
7192 
7193     /**
7194      * Sets the text to be displayed. TextView <em>does not</em> accept
7195      * HTML-like formatting, which you can do with text strings in XML resource files.
7196      * To style your strings, attach android.text.style.* objects to a
7197      * {@link android.text.SpannableString}, or see the
7198      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
7199      * Available Resource Types</a> documentation for an example of setting
7200      * formatted text in the XML resource file.
7201      * <p/>
7202      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7203      * intermediate {@link Spannable Spannables}. Likewise it will use
7204      * {@link android.text.Editable.Factory} to create final or intermediate
7205      * {@link Editable Editables}.
7206      *
7207      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
7208      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
7209      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
7210      *
7211      * @param text text to be displayed
7212      *
7213      * @attr ref android.R.styleable#TextView_text
7214      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
7215      *                                  parameters used to create the PrecomputedText mismatches
7216      *                                  with this TextView.
7217      */
7218     @android.view.RemotableViewMethod(asyncImpl = "setTextAsync")
7219     public final void setText(CharSequence text) {
7220         setText(text, mBufferType);
7221     }
7222 
7223     /**
7224      * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}.
7225      * This should be called on a background thread, and returns a Runnable which is then must be
7226      * called on the main thread to complete the operation and set text.
7227      * @param text text to be displayed
7228      * @return Runnable that sets text; must be called on the main thread by the caller of this
7229      * method to complete the operation
7230      * @hide
7231      */
7232     @NonNull
7233     public Runnable setTextAsync(@Nullable CharSequence text) {
7234         return () -> setText(text);
7235     }
7236 
7237     /**
7238      * Sets the text to be displayed but retains the cursor position. Same as
7239      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
7240      * new text.
7241      * <p/>
7242      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7243      * intermediate {@link Spannable Spannables}. Likewise it will use
7244      * {@link android.text.Editable.Factory} to create final or intermediate
7245      * {@link Editable Editables}.
7246      *
7247      * @param text text to be displayed
7248      *
7249      * @see #setText(CharSequence)
7250      */
7251     @android.view.RemotableViewMethod
7252     public final void setTextKeepState(CharSequence text) {
7253         setTextKeepState(text, mBufferType);
7254     }
7255 
7256     /**
7257      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
7258      * <p/>
7259      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7260      * intermediate {@link Spannable Spannables}. Likewise it will use
7261      * {@link android.text.Editable.Factory} to create final or intermediate
7262      * {@link Editable Editables}.
7263      *
7264      * Subclasses overriding this method should ensure that the following post condition holds,
7265      * in order to guarantee the safety of the view's measurement and layout operations:
7266      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
7267      * will be different from {@code null}.
7268      *
7269      * @param text text to be displayed
7270      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7271      *              stored as a static text, styleable/spannable text, or editable text
7272      *
7273      * @see #setText(CharSequence)
7274      * @see android.widget.TextView.BufferType
7275      * @see #setSpannableFactory(Spannable.Factory)
7276      * @see #setEditableFactory(Editable.Factory)
7277      *
7278      * @attr ref android.R.styleable#TextView_text
7279      * @attr ref android.R.styleable#TextView_bufferType
7280      */
7281     public void setText(CharSequence text, BufferType type) {
7282         setText(text, type, true, 0);
7283 
7284         // drop any potential mCharWrappper leaks
7285         mCharWrapper = null;
7286     }
7287 
7288     @UnsupportedAppUsage
7289     private void setText(CharSequence text, BufferType type,
7290                          boolean notifyBefore, int oldlen) {
7291         if (mEditor != null) {
7292             mEditor.beforeSetText();
7293         }
7294         mTextSetFromXmlOrResourceId = false;
7295         if (text == null) {
7296             text = "";
7297         }
7298 
7299         // If suggestions are not enabled, remove the suggestion spans from the text
7300         if (!isSuggestionsEnabled()) {
7301             text = removeSuggestionSpans(text);
7302         }
7303 
7304         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
7305 
7306         if (text instanceof Spanned
7307                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
7308             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
7309                 setHorizontalFadingEdgeEnabled(true);
7310                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
7311             } else {
7312                 setHorizontalFadingEdgeEnabled(false);
7313                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7314             }
7315             setEllipsize(TextUtils.TruncateAt.MARQUEE);
7316         }
7317 
7318         int n = mFilters.length;
7319         for (int i = 0; i < n; i++) {
7320             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
7321             if (out != null) {
7322                 text = out;
7323             }
7324         }
7325 
7326         if (notifyBefore) {
7327             if (mText != null) {
7328                 oldlen = mText.length();
7329                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
7330             } else {
7331                 sendBeforeTextChanged("", 0, 0, text.length());
7332             }
7333         }
7334 
7335         boolean needEditableForNotification = false;
7336 
7337         if (mListeners != null && mListeners.size() != 0) {
7338             needEditableForNotification = true;
7339         }
7340 
7341         PrecomputedText precomputed =
7342                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
7343         if (type == BufferType.EDITABLE || getKeyListener() != null
7344                 || needEditableForNotification) {
7345             createEditorIfNeeded();
7346             mEditor.forgetUndoRedo();
7347             mEditor.scheduleRestartInputForSetText();
7348             Editable t = mEditableFactory.newEditable(text);
7349             text = t;
7350             setFilters(t, mFilters);
7351         } else if (precomputed != null) {
7352             if (mTextDir == null) {
7353                 mTextDir = getTextDirectionHeuristic();
7354             }
7355             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
7356                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
7357                             mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
7358                                     mLineBreakStyle, mLineBreakWordStyle));
7359             switch (checkResult) {
7360                 case PrecomputedText.Params.UNUSABLE:
7361                     throw new IllegalArgumentException(
7362                         "PrecomputedText's Parameters don't match the parameters of this TextView."
7363                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
7364                         + "to override the settings of this TextView: "
7365                         + "PrecomputedText: " + precomputed.getParams()
7366                         + "TextView: " + getTextMetricsParams());
7367                 case PrecomputedText.Params.NEED_RECOMPUTE:
7368                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
7369                     break;
7370                 case PrecomputedText.Params.USABLE:
7371                     // pass through
7372             }
7373         } else if (type == BufferType.SPANNABLE || mMovement != null) {
7374             text = mSpannableFactory.newSpannable(text);
7375         } else if (!(text instanceof CharWrapper)) {
7376             text = TextUtils.stringOrSpannedString(text);
7377         }
7378 
7379         @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE;
7380         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
7381             a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText);
7382         }
7383 
7384         if (mAutoLinkMask != 0) {
7385             Spannable s2;
7386 
7387             if (type == BufferType.EDITABLE || text instanceof Spannable) {
7388                 s2 = (Spannable) text;
7389             } else {
7390                 s2 = mSpannableFactory.newSpannable(text);
7391             }
7392 
7393             if (Linkify.addLinks(s2, mAutoLinkMask)) {
7394                 text = s2;
7395                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
7396 
7397                 /*
7398                  * We must go ahead and set the text before changing the
7399                  * movement method, because setMovementMethod() may call
7400                  * setText() again to try to upgrade the buffer type.
7401                  */
7402                 setTextInternal(text);
7403                 if (a11yTextChangeType == AccessibilityUtils.NONE) {
7404                     a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN;
7405                 }
7406 
7407                 // Do not change the movement method for text that support text selection as it
7408                 // would prevent an arbitrary cursor displacement.
7409                 if (mLinksClickable && !textCanBeSelected()) {
7410                     setMovementMethod(LinkMovementMethod.getInstance());
7411                 }
7412             }
7413         }
7414 
7415         mBufferType = type;
7416         setTextInternal(text);
7417 
7418         if (mTransformation == null) {
7419             mTransformed = text;
7420         } else {
7421             mTransformed = mTransformation.getTransformation(text, this);
7422         }
7423         if (mTransformed == null) {
7424             // Should not happen if the transformation method follows the non-null postcondition.
7425             mTransformed = "";
7426         }
7427 
7428         final int textLength = text.length();
7429         final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
7430 
7431         if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) {
7432             Spannable sp = (Spannable) text;
7433 
7434             // Remove any ChangeWatchers that might have come from other TextViews.
7435             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
7436             final int count = watchers.length;
7437             for (int i = 0; i < count; i++) {
7438                 sp.removeSpan(watchers[i]);
7439             }
7440 
7441             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
7442 
7443             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
7444                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
7445 
7446             if (mEditor != null) mEditor.addSpanWatchers(sp);
7447 
7448             if (mTransformation != null) {
7449                 final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
7450                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
7451                         | (priority << Spanned.SPAN_PRIORITY_SHIFT));
7452             }
7453 
7454             if (mMovement != null) {
7455                 mMovement.initialize(this, (Spannable) text);
7456 
7457                 /*
7458                  * Initializing the movement method will have set the
7459                  * selection, so reset mSelectionMoved to keep that from
7460                  * interfering with the normal on-focus selection-setting.
7461                  */
7462                 if (mEditor != null) mEditor.mSelectionMoved = false;
7463             }
7464         }
7465 
7466         if (mLayout != null) {
7467             checkForRelayout();
7468         }
7469 
7470         sendOnTextChanged(text, 0, oldlen, textLength);
7471         onTextChanged(text, 0, oldlen, textLength);
7472 
7473         mHideHint = false;
7474 
7475         if (a11yTextChangeType == AccessibilityUtils.TEXT) {
7476             notifyViewAccessibilityStateChangedIfNeeded(
7477                     AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
7478         } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) {
7479             notifyViewAccessibilityStateChangedIfNeeded(
7480                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7481         }
7482 
7483         if (needEditableForNotification) {
7484             sendAfterTextChanged((Editable) text);
7485         } else {
7486             notifyListeningManagersAfterTextChanged();
7487         }
7488 
7489         if (mEditor != null) {
7490             // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
7491             mEditor.prepareCursorControllers();
7492 
7493             mEditor.maybeFireScheduledRestartInputForSetText();
7494         }
7495     }
7496 
7497     /**
7498      * Sets the TextView to display the specified slice of the specified
7499      * char array. You must promise that you will not change the contents
7500      * of the array except for right before another call to setText(),
7501      * since the TextView has no way to know that the text
7502      * has changed and that it needs to invalidate and re-layout.
7503      *
7504      * @throws NullPointerException if text is null
7505      * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length
7506      *
7507      * @param text char array to be displayed
7508      * @param start start index in the char array
7509      * @param len length of char count after {@code start}
7510      */
7511     public final void setText(@NonNull char[] text, int start, int len) {
7512         int oldlen = 0;
7513 
7514         if (start < 0 || len < 0 || start + len > text.length) {
7515             throw new IndexOutOfBoundsException(start + ", " + len);
7516         }
7517 
7518         /*
7519          * We must do the before-notification here ourselves because if
7520          * the old text is a CharWrapper we destroy it before calling
7521          * into the normal path.
7522          */
7523         if (mText != null) {
7524             oldlen = mText.length();
7525             sendBeforeTextChanged(mText, 0, oldlen, len);
7526         } else {
7527             sendBeforeTextChanged("", 0, 0, len);
7528         }
7529 
7530         if (mCharWrapper == null) {
7531             mCharWrapper = new CharWrapper(text, start, len);
7532         } else {
7533             mCharWrapper.set(text, start, len);
7534         }
7535 
7536         setText(mCharWrapper, mBufferType, false, oldlen);
7537     }
7538 
7539     /**
7540      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
7541      * the cursor position. Same as
7542      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
7543      * position (if any) is retained in the new text.
7544      * <p/>
7545      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7546      * intermediate {@link Spannable Spannables}. Likewise it will use
7547      * {@link android.text.Editable.Factory} to create final or intermediate
7548      * {@link Editable Editables}.
7549      *
7550      * @param text text to be displayed
7551      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7552      *              stored as a static text, styleable/spannable text, or editable text
7553      *
7554      * @see #setText(CharSequence, android.widget.TextView.BufferType)
7555      */
7556     public final void setTextKeepState(CharSequence text, BufferType type) {
7557         int start = getSelectionStart();
7558         int end = getSelectionEnd();
7559         int len = text.length();
7560 
7561         setText(text, type);
7562 
7563         if (start >= 0 || end >= 0) {
7564             if (mSpannable != null) {
7565                 Selection.setSelection(mSpannable,
7566                                        Math.max(0, Math.min(start, len)),
7567                                        Math.max(0, Math.min(end, len)));
7568             }
7569         }
7570     }
7571 
7572     /**
7573      * Sets the text to be displayed using a string resource identifier.
7574      *
7575      * @param resid the resource identifier of the string resource to be displayed
7576      *
7577      * @see #setText(CharSequence)
7578      *
7579      * @attr ref android.R.styleable#TextView_text
7580      */
7581     @android.view.RemotableViewMethod
7582     public final void setText(@StringRes int resid) {
7583         setText(getContext().getResources().getText(resid));
7584         mTextSetFromXmlOrResourceId = true;
7585         mTextId = resid;
7586     }
7587 
7588     /**
7589      * Sets the text to be displayed using a string resource identifier and the
7590      * {@link android.widget.TextView.BufferType}.
7591      * <p/>
7592      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7593      * intermediate {@link Spannable Spannables}. Likewise it will use
7594      * {@link android.text.Editable.Factory} to create final or intermediate
7595      * {@link Editable Editables}.
7596      *
7597      * @param resid the resource identifier of the string resource to be displayed
7598      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7599      *              stored as a static text, styleable/spannable text, or editable text
7600      *
7601      * @see #setText(int)
7602      * @see #setText(CharSequence)
7603      * @see android.widget.TextView.BufferType
7604      * @see #setSpannableFactory(Spannable.Factory)
7605      * @see #setEditableFactory(Editable.Factory)
7606      *
7607      * @attr ref android.R.styleable#TextView_text
7608      * @attr ref android.R.styleable#TextView_bufferType
7609      */
7610     public final void setText(@StringRes int resid, BufferType type) {
7611         setText(getContext().getResources().getText(resid), type);
7612         mTextSetFromXmlOrResourceId = true;
7613         mTextId = resid;
7614     }
7615 
7616     /**
7617      * Sets the text to be displayed when the text of the TextView is empty.
7618      * Null means to use the normal empty text. The hint does not currently
7619      * participate in determining the size of the view.
7620      *
7621      * @attr ref android.R.styleable#TextView_hint
7622      */
7623     @android.view.RemotableViewMethod
7624     public final void setHint(CharSequence hint) {
7625         setHintInternal(hint);
7626 
7627         if (mEditor != null && isInputMethodTarget()) {
7628             mEditor.reportExtractedText();
7629         }
7630     }
7631 
7632     private void setHintInternal(CharSequence hint) {
7633         mHideHint = false;
7634         mHint = TextUtils.stringOrSpannedString(hint);
7635 
7636         if (mLayout != null) {
7637             checkForRelayout();
7638         }
7639 
7640         if (mText.length() == 0) {
7641             invalidate();
7642         }
7643 
7644         // Invalidate display list if hint is currently used
7645         if (mEditor != null && mText.length() == 0 && mHint != null) {
7646             mEditor.invalidateTextDisplayList();
7647         }
7648     }
7649 
7650     /**
7651      * Sets the text to be displayed when the text of the TextView is empty,
7652      * from a resource.
7653      *
7654      * @attr ref android.R.styleable#TextView_hint
7655      */
7656     @android.view.RemotableViewMethod
7657     public final void setHint(@StringRes int resid) {
7658         mHintId = resid;
7659         setHint(getContext().getResources().getText(resid));
7660     }
7661 
7662     /**
7663      * Returns the hint that is displayed when the text of the TextView
7664      * is empty.
7665      *
7666      * @attr ref android.R.styleable#TextView_hint
7667      */
7668     @InspectableProperty
7669     @ViewDebug.CapturedViewProperty
7670     public CharSequence getHint() {
7671         return mHint;
7672     }
7673 
7674     /**
7675      * Temporarily hides the hint text until the text is modified, or the hint text is modified, or
7676      * the view gains or loses focus.
7677      *
7678      * @hide
7679      */
7680     public void hideHint() {
7681         if (isShowingHint()) {
7682             mHideHint = true;
7683             invalidate();
7684         }
7685     }
7686 
7687     /**
7688      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
7689      * line characters instead of letting it wrap onto multiple lines.
7690      *
7691      * @attr ref android.R.styleable#TextView_singleLine
7692      */
7693     @InspectableProperty
7694     public boolean isSingleLine() {
7695         return mSingleLine;
7696     }
7697 
7698     private static boolean isMultilineInputType(int type) {
7699         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
7700                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
7701     }
7702 
7703     /**
7704      * Removes the suggestion spans.
7705      */
7706     CharSequence removeSuggestionSpans(CharSequence text) {
7707         if (text instanceof Spanned) {
7708             Spannable spannable;
7709             if (text instanceof Spannable) {
7710                 spannable = (Spannable) text;
7711             } else {
7712                 spannable = mSpannableFactory.newSpannable(text);
7713             }
7714 
7715             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
7716             if (spans.length == 0) {
7717                 return text;
7718             } else {
7719                 text = spannable;
7720             }
7721 
7722             for (int i = 0; i < spans.length; i++) {
7723                 spannable.removeSpan(spans[i]);
7724             }
7725         }
7726         return text;
7727     }
7728 
7729     /**
7730      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
7731      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
7732      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
7733      * then a soft keyboard will not be displayed for this text view.
7734      *
7735      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
7736      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
7737      * type.
7738      *
7739      * @see #getInputType()
7740      * @see #setRawInputType(int)
7741      * @see android.text.InputType
7742      * @attr ref android.R.styleable#TextView_inputType
7743      */
7744     public void setInputType(int type) {
7745         final boolean wasPassword = isPasswordInputType(getInputType());
7746         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
7747         setInputType(type, false);
7748         final boolean isPassword = isPasswordInputType(type);
7749         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
7750         boolean forceUpdate = false;
7751         if (isPassword) {
7752             setTransformationMethod(PasswordTransformationMethod.getInstance());
7753             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
7754                     Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
7755         } else if (isVisiblePassword) {
7756             if (mTransformation == PasswordTransformationMethod.getInstance()) {
7757                 forceUpdate = true;
7758             }
7759             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
7760                     Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
7761         } else if (wasPassword || wasVisiblePassword) {
7762             // not in password mode, clean up typeface and transformation
7763             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
7764                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
7765                     FontStyle.FONT_WEIGHT_UNSPECIFIED);
7766             if (mTransformation == PasswordTransformationMethod.getInstance()) {
7767                 forceUpdate = true;
7768             }
7769         }
7770 
7771         boolean singleLine = !isMultilineInputType(type);
7772 
7773         // We need to update the single line mode if it has changed or we
7774         // were previously in password mode.
7775         if (mSingleLine != singleLine || forceUpdate) {
7776             // Change single line mode, but only change the transformation if
7777             // we are not in password mode.
7778             applySingleLine(singleLine, !isPassword, true, true);
7779         }
7780 
7781         if (!isSuggestionsEnabled()) {
7782             setTextInternal(removeSuggestionSpans(mText));
7783         }
7784 
7785         InputMethodManager imm = getInputMethodManager();
7786         if (imm != null) imm.restartInput(this);
7787     }
7788 
7789     /**
7790      * It would be better to rely on the input type for everything. A password inputType should have
7791      * a password transformation. We should hence use isPasswordInputType instead of this method.
7792      *
7793      * We should:
7794      * - Call setInputType in setKeyListener instead of changing the input type directly (which
7795      * would install the correct transformation).
7796      * - Refuse the installation of a non-password transformation in setTransformation if the input
7797      * type is password.
7798      *
7799      * However, this is like this for legacy reasons and we cannot break existing apps. This method
7800      * is useful since it matches what the user can see (obfuscated text or not).
7801      *
7802      * @return true if the current transformation method is of the password type.
7803      */
7804     boolean hasPasswordTransformationMethod() {
7805         return mTransformation instanceof PasswordTransformationMethod;
7806     }
7807 
7808     /**
7809      * Returns true if the current inputType is any type of password.
7810      *
7811      * @hide
7812      */
7813     public boolean isAnyPasswordInputType() {
7814         final int inputType = getInputType();
7815         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
7816     }
7817 
7818     static boolean isPasswordInputType(int inputType) {
7819         final int variation =
7820                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
7821         return variation
7822                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
7823                 || variation
7824                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
7825                 || variation
7826                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
7827     }
7828 
7829     private static boolean isVisiblePasswordInputType(int inputType) {
7830         final int variation =
7831                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
7832         return variation
7833                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
7834     }
7835 
7836     /**
7837      * Directly change the content type integer of the text view, without
7838      * modifying any other state.
7839      * @see #setInputType(int)
7840      * @see android.text.InputType
7841      * @attr ref android.R.styleable#TextView_inputType
7842      */
7843     public void setRawInputType(int type) {
7844         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
7845         createEditorIfNeeded();
7846         mEditor.mInputType = type;
7847     }
7848 
7849     @Override
7850     public String[] getAutofillHints() {
7851         String[] hints = super.getAutofillHints();
7852         if (isAnyPasswordInputType()) {
7853             if (!ArrayUtils.contains(hints, AUTOFILL_HINT_PASSWORD_AUTO)) {
7854                 hints = ArrayUtils.appendElement(String.class, hints,
7855                         AUTOFILL_HINT_PASSWORD_AUTO);
7856             }
7857         }
7858         return hints;
7859     }
7860 
7861     /**
7862      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
7863      *         a {@code Locale} object that can be used to customize key various listeners.
7864      * @see DateKeyListener#getInstance(Locale)
7865      * @see DateTimeKeyListener#getInstance(Locale)
7866      * @see DigitsKeyListener#getInstance(Locale)
7867      * @see TimeKeyListener#getInstance(Locale)
7868      */
7869     @Nullable
7870     private Locale getCustomLocaleForKeyListenerOrNull() {
7871         if (!mUseInternationalizedInput) {
7872             // If the application does not target O, stick to the previous behavior.
7873             return null;
7874         }
7875         final LocaleList locales = getImeHintLocales();
7876         if (locales == null) {
7877             // If the application does not explicitly specify IME hint locale, also stick to the
7878             // previous behavior.
7879             return null;
7880         }
7881         return locales.get(0);
7882     }
7883 
7884     @UnsupportedAppUsage
7885     private void setInputType(int type, boolean direct) {
7886         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
7887         KeyListener input;
7888         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
7889             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
7890             TextKeyListener.Capitalize cap;
7891             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
7892                 cap = TextKeyListener.Capitalize.CHARACTERS;
7893             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
7894                 cap = TextKeyListener.Capitalize.WORDS;
7895             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
7896                 cap = TextKeyListener.Capitalize.SENTENCES;
7897             } else {
7898                 cap = TextKeyListener.Capitalize.NONE;
7899             }
7900             input = TextKeyListener.getInstance(autotext, cap);
7901         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
7902             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7903             input = DigitsKeyListener.getInstance(
7904                     locale,
7905                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
7906                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
7907             if (locale != null) {
7908                 // Override type, if necessary for i18n.
7909                 int newType = input.getInputType();
7910                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
7911                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
7912                     // The class is different from the original class. So we need to override
7913                     // 'type'. But we want to keep the password flag if it's there.
7914                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
7915                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
7916                     }
7917                     type = newType;
7918                 }
7919             }
7920         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
7921             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7922             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
7923                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
7924                     input = DateKeyListener.getInstance(locale);
7925                     break;
7926                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
7927                     input = TimeKeyListener.getInstance(locale);
7928                     break;
7929                 default:
7930                     input = DateTimeKeyListener.getInstance(locale);
7931                     break;
7932             }
7933             if (mUseInternationalizedInput) {
7934                 type = input.getInputType(); // Override type, if necessary for i18n.
7935             }
7936         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
7937             input = DialerKeyListener.getInstance();
7938         } else {
7939             input = TextKeyListener.getInstance();
7940         }
7941         setRawInputType(type);
7942         mListenerChanged = false;
7943         if (direct) {
7944             createEditorIfNeeded();
7945             mEditor.mKeyListener = input;
7946         } else {
7947             setKeyListenerOnly(input);
7948         }
7949     }
7950 
7951     /**
7952      * Get the type of the editable content.
7953      *
7954      * @see #setInputType(int)
7955      * @see android.text.InputType
7956      */
7957     @InspectableProperty(flagMapping = {
7958             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
7959             @FlagEntry(
7960                     name = "text",
7961                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7962                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
7963             @FlagEntry(
7964                     name = "textUri",
7965                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7966                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
7967             @FlagEntry(
7968                     name = "textEmailAddress",
7969                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7970                     target = InputType.TYPE_CLASS_TEXT
7971                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
7972             @FlagEntry(
7973                     name = "textEmailSubject",
7974                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7975                     target = InputType.TYPE_CLASS_TEXT
7976                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
7977             @FlagEntry(
7978                     name = "textShortMessage",
7979                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7980                     target = InputType.TYPE_CLASS_TEXT
7981                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
7982             @FlagEntry(
7983                     name = "textLongMessage",
7984                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7985                     target = InputType.TYPE_CLASS_TEXT
7986                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
7987             @FlagEntry(
7988                     name = "textPersonName",
7989                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7990                     target = InputType.TYPE_CLASS_TEXT
7991                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
7992             @FlagEntry(
7993                     name = "textPostalAddress",
7994                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7995                     target = InputType.TYPE_CLASS_TEXT
7996                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
7997             @FlagEntry(
7998                     name = "textPassword",
7999                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8000                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
8001             @FlagEntry(
8002                     name = "textVisiblePassword",
8003                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8004                     target = InputType.TYPE_CLASS_TEXT
8005                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
8006             @FlagEntry(
8007                     name = "textWebEditText",
8008                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8009                     target = InputType.TYPE_CLASS_TEXT
8010                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
8011             @FlagEntry(
8012                     name = "textFilter",
8013                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8014                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
8015             @FlagEntry(
8016                     name = "textPhonetic",
8017                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8018                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
8019             @FlagEntry(
8020                     name = "textWebEmailAddress",
8021                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8022                     target = InputType.TYPE_CLASS_TEXT
8023                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
8024             @FlagEntry(
8025                     name = "textWebPassword",
8026                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8027                     target = InputType.TYPE_CLASS_TEXT
8028                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
8029             @FlagEntry(
8030                     name = "number",
8031                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8032                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
8033             @FlagEntry(
8034                     name = "numberPassword",
8035                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8036                     target = InputType.TYPE_CLASS_NUMBER
8037                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
8038             @FlagEntry(
8039                     name = "phone",
8040                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8041                     target = InputType.TYPE_CLASS_PHONE),
8042             @FlagEntry(
8043                     name = "datetime",
8044                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8045                     target = InputType.TYPE_CLASS_DATETIME
8046                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
8047             @FlagEntry(
8048                     name = "date",
8049                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8050                     target = InputType.TYPE_CLASS_DATETIME
8051                             | InputType.TYPE_DATETIME_VARIATION_DATE),
8052             @FlagEntry(
8053                     name = "time",
8054                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8055                     target = InputType.TYPE_CLASS_DATETIME
8056                             | InputType.TYPE_DATETIME_VARIATION_TIME),
8057             @FlagEntry(
8058                     name = "textCapCharacters",
8059                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8060                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
8061             @FlagEntry(
8062                     name = "textCapWords",
8063                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8064                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
8065             @FlagEntry(
8066                     name = "textCapSentences",
8067                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8068                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
8069             @FlagEntry(
8070                     name = "textAutoCorrect",
8071                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8072                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
8073             @FlagEntry(
8074                     name = "textAutoComplete",
8075                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8076                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
8077             @FlagEntry(
8078                     name = "textMultiLine",
8079                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8080                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
8081             @FlagEntry(
8082                     name = "textImeMultiLine",
8083                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8084                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
8085             @FlagEntry(
8086                     name = "textNoSuggestions",
8087                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8088                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
8089             @FlagEntry(
8090                     name = "numberSigned",
8091                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8092                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
8093             @FlagEntry(
8094                     name = "numberDecimal",
8095                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8096                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
8097     })
8098     public int getInputType() {
8099         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
8100     }
8101 
8102     /**
8103      * Change the editor type integer associated with the text view, which
8104      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
8105      * when it has focus.
8106      * @see #getImeOptions
8107      * @see android.view.inputmethod.EditorInfo
8108      * @attr ref android.R.styleable#TextView_imeOptions
8109      */
8110     public void setImeOptions(int imeOptions) {
8111         createEditorIfNeeded();
8112         mEditor.createInputContentTypeIfNeeded();
8113         mEditor.mInputContentType.imeOptions = imeOptions;
8114     }
8115 
8116     /**
8117      * Get the type of the Input Method Editor (IME).
8118      * @return the type of the IME
8119      * @see #setImeOptions(int)
8120      * @see EditorInfo
8121      */
8122     @InspectableProperty(flagMapping = {
8123             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
8124             @FlagEntry(
8125                     name = "actionUnspecified",
8126                     mask = EditorInfo.IME_MASK_ACTION,
8127                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
8128             @FlagEntry(
8129                     name = "actionNone",
8130                     mask = EditorInfo.IME_MASK_ACTION,
8131                     target = EditorInfo.IME_ACTION_NONE),
8132             @FlagEntry(
8133                     name = "actionGo",
8134                     mask = EditorInfo.IME_MASK_ACTION,
8135                     target = EditorInfo.IME_ACTION_GO),
8136             @FlagEntry(
8137                     name = "actionSearch",
8138                     mask = EditorInfo.IME_MASK_ACTION,
8139                     target = EditorInfo.IME_ACTION_SEARCH),
8140             @FlagEntry(
8141                     name = "actionSend",
8142                     mask = EditorInfo.IME_MASK_ACTION,
8143                     target = EditorInfo.IME_ACTION_SEND),
8144             @FlagEntry(
8145                     name = "actionNext",
8146                     mask = EditorInfo.IME_MASK_ACTION,
8147                     target = EditorInfo.IME_ACTION_NEXT),
8148             @FlagEntry(
8149                     name = "actionDone",
8150                     mask = EditorInfo.IME_MASK_ACTION,
8151                     target = EditorInfo.IME_ACTION_DONE),
8152             @FlagEntry(
8153                     name = "actionPrevious",
8154                     mask = EditorInfo.IME_MASK_ACTION,
8155                     target = EditorInfo.IME_ACTION_PREVIOUS),
8156             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
8157             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
8158             @FlagEntry(
8159                     name = "flagNavigatePrevious",
8160                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
8161             @FlagEntry(
8162                     name = "flagNoAccessoryAction",
8163                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
8164             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
8165             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
8166             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
8167             @FlagEntry(
8168                     name = "flagNoPersonalizedLearning",
8169                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
8170     })
8171     public int getImeOptions() {
8172         return mEditor != null && mEditor.mInputContentType != null
8173                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
8174     }
8175 
8176     /**
8177      * Change the custom IME action associated with the text view, which
8178      * will be reported to an IME with {@link EditorInfo#actionLabel}
8179      * and {@link EditorInfo#actionId} when it has focus.
8180      * @see #getImeActionLabel
8181      * @see #getImeActionId
8182      * @see android.view.inputmethod.EditorInfo
8183      * @attr ref android.R.styleable#TextView_imeActionLabel
8184      * @attr ref android.R.styleable#TextView_imeActionId
8185      */
8186     public void setImeActionLabel(CharSequence label, int actionId) {
8187         createEditorIfNeeded();
8188         mEditor.createInputContentTypeIfNeeded();
8189         mEditor.mInputContentType.imeActionLabel = label;
8190         mEditor.mInputContentType.imeActionId = actionId;
8191     }
8192 
8193     /**
8194      * Get the IME action label previous set with {@link #setImeActionLabel}.
8195      *
8196      * @see #setImeActionLabel
8197      * @see android.view.inputmethod.EditorInfo
8198      */
8199     @InspectableProperty
8200     public CharSequence getImeActionLabel() {
8201         return mEditor != null && mEditor.mInputContentType != null
8202                 ? mEditor.mInputContentType.imeActionLabel : null;
8203     }
8204 
8205     /**
8206      * Get the IME action ID previous set with {@link #setImeActionLabel}.
8207      *
8208      * @see #setImeActionLabel
8209      * @see android.view.inputmethod.EditorInfo
8210      */
8211     @InspectableProperty
8212     public int getImeActionId() {
8213         return mEditor != null && mEditor.mInputContentType != null
8214                 ? mEditor.mInputContentType.imeActionId : 0;
8215     }
8216 
8217     /**
8218      * Set a special listener to be called when an action is performed
8219      * on the text view.  This will be called when the enter key is pressed,
8220      * or when an action supplied to the IME is selected by the user.  Setting
8221      * this means that the normal hard key event will not insert a newline
8222      * into the text view, even if it is multi-line; holding down the ALT
8223      * modifier will, however, allow the user to insert a newline character.
8224      */
8225     public void setOnEditorActionListener(OnEditorActionListener l) {
8226         createEditorIfNeeded();
8227         mEditor.createInputContentTypeIfNeeded();
8228         mEditor.mInputContentType.onEditorActionListener = l;
8229     }
8230 
8231     /**
8232      * Called when an attached input method calls
8233      * {@link InputConnection#performEditorAction(int)
8234      * InputConnection.performEditorAction()}
8235      * for this text view.  The default implementation will call your action
8236      * listener supplied to {@link #setOnEditorActionListener}, or perform
8237      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
8238      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
8239      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
8240      * EditorInfo.IME_ACTION_DONE}.
8241      *
8242      * <p>For backwards compatibility, if no IME options have been set and the
8243      * text view would not normally advance focus on enter, then
8244      * the NEXT and DONE actions received here will be turned into an enter
8245      * key down/up pair to go through the normal key handling.
8246      *
8247      * @param actionCode The code of the action being performed.
8248      *
8249      * @see #setOnEditorActionListener
8250      */
8251     public void onEditorAction(int actionCode) {
8252         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
8253         if (ict != null) {
8254             if (ict.onEditorActionListener != null) {
8255                 if (ict.onEditorActionListener.onEditorAction(this,
8256                         actionCode, null)) {
8257                     return;
8258                 }
8259             }
8260 
8261             // This is the handling for some default action.
8262             // Note that for backwards compatibility we don't do this
8263             // default handling if explicit ime options have not been given,
8264             // instead turning this into the normal enter key codes that an
8265             // app may be expecting.
8266             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
8267                 View v = focusSearch(FOCUS_FORWARD);
8268                 if (v != null) {
8269                     if (!v.requestFocus(FOCUS_FORWARD)) {
8270                         throw new IllegalStateException("focus search returned a view "
8271                                 + "that wasn't able to take focus!");
8272                     }
8273                 }
8274                 return;
8275 
8276             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
8277                 View v = focusSearch(FOCUS_BACKWARD);
8278                 if (v != null) {
8279                     if (!v.requestFocus(FOCUS_BACKWARD)) {
8280                         throw new IllegalStateException("focus search returned a view "
8281                                 + "that wasn't able to take focus!");
8282                     }
8283                 }
8284                 return;
8285 
8286             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
8287                 InputMethodManager imm = getInputMethodManager();
8288                 if (imm != null) {
8289                     imm.hideSoftInputFromView(this, 0);
8290                 }
8291                 return;
8292             }
8293         }
8294 
8295         ViewRootImpl viewRootImpl = getViewRootImpl();
8296         if (viewRootImpl != null) {
8297             long eventTime = SystemClock.uptimeMillis();
8298             viewRootImpl.dispatchKeyFromIme(
8299                     new KeyEvent(eventTime, eventTime,
8300                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
8301                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
8302                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
8303                     | KeyEvent.FLAG_EDITOR_ACTION));
8304             viewRootImpl.dispatchKeyFromIme(
8305                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
8306                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
8307                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
8308                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
8309                     | KeyEvent.FLAG_EDITOR_ACTION));
8310         }
8311     }
8312 
8313     /**
8314      * Set the private content type of the text, which is the
8315      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
8316      * field that will be filled in when creating an input connection.
8317      *
8318      * @see #getPrivateImeOptions()
8319      * @see EditorInfo#privateImeOptions
8320      * @attr ref android.R.styleable#TextView_privateImeOptions
8321      */
8322     public void setPrivateImeOptions(String type) {
8323         createEditorIfNeeded();
8324         mEditor.createInputContentTypeIfNeeded();
8325         mEditor.mInputContentType.privateImeOptions = type;
8326     }
8327 
8328     /**
8329      * Get the private type of the content.
8330      *
8331      * @see #setPrivateImeOptions(String)
8332      * @see EditorInfo#privateImeOptions
8333      */
8334     @InspectableProperty
8335     public String getPrivateImeOptions() {
8336         return mEditor != null && mEditor.mInputContentType != null
8337                 ? mEditor.mInputContentType.privateImeOptions : null;
8338     }
8339 
8340     /**
8341      * Set the extra input data of the text, which is the
8342      * {@link EditorInfo#extras TextBoxAttribute.extras}
8343      * Bundle that will be filled in when creating an input connection.  The
8344      * given integer is the resource identifier of an XML resource holding an
8345      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
8346      *
8347      * @see #getInputExtras(boolean)
8348      * @see EditorInfo#extras
8349      * @attr ref android.R.styleable#TextView_editorExtras
8350      */
8351     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
8352         createEditorIfNeeded();
8353         XmlResourceParser parser = getResources().getXml(xmlResId);
8354         mEditor.createInputContentTypeIfNeeded();
8355         mEditor.mInputContentType.extras = new Bundle();
8356         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
8357     }
8358 
8359     /**
8360      * Retrieve the input extras currently associated with the text view, which
8361      * can be viewed as well as modified.
8362      *
8363      * @param create If true, the extras will be created if they don't already
8364      * exist.  Otherwise, null will be returned if none have been created.
8365      * @see #setInputExtras(int)
8366      * @see EditorInfo#extras
8367      * @attr ref android.R.styleable#TextView_editorExtras
8368      */
8369     public Bundle getInputExtras(boolean create) {
8370         if (mEditor == null && !create) return null;
8371         createEditorIfNeeded();
8372         if (mEditor.mInputContentType == null) {
8373             if (!create) return null;
8374             mEditor.createInputContentTypeIfNeeded();
8375         }
8376         if (mEditor.mInputContentType.extras == null) {
8377             if (!create) return null;
8378             mEditor.mInputContentType.extras = new Bundle();
8379         }
8380         return mEditor.mInputContentType.extras;
8381     }
8382 
8383     /**
8384      * Change "hint" locales associated with the text view, which will be reported to an IME with
8385      * {@link EditorInfo#hintLocales} when it has focus.
8386      *
8387      * Starting with Android O, this also causes internationalized listeners to be created (or
8388      * change locale) based on the first locale in the input locale list.
8389      *
8390      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
8391      * call {@link InputMethodManager#restartInput(View)}.</p>
8392      * @param hintLocales List of the languages that the user is supposed to switch to no matter
8393      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
8394      * @see #getImeHintLocales()
8395      * @see android.view.inputmethod.EditorInfo#hintLocales
8396      */
8397     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
8398         createEditorIfNeeded();
8399         mEditor.createInputContentTypeIfNeeded();
8400         mEditor.mInputContentType.imeHintLocales = hintLocales;
8401         if (mUseInternationalizedInput) {
8402             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
8403         }
8404     }
8405 
8406     /**
8407      * @return The current languages list "hint". {@code null} when no "hint" is available.
8408      * @see #setImeHintLocales(LocaleList)
8409      * @see android.view.inputmethod.EditorInfo#hintLocales
8410      */
8411     @Nullable
8412     public LocaleList getImeHintLocales() {
8413         if (mEditor == null) {
8414             return null;
8415         }
8416         if (mEditor.mInputContentType == null) {
8417             return null;
8418         }
8419         return mEditor.mInputContentType.imeHintLocales;
8420     }
8421 
8422     /**
8423      * Returns the error message that was set to be displayed with
8424      * {@link #setError}, or <code>null</code> if no error was set
8425      * or if it the error was cleared by the widget after user input.
8426      */
8427     public CharSequence getError() {
8428         return mEditor == null ? null : mEditor.mError;
8429     }
8430 
8431     /**
8432      * Sets the right-hand compound drawable of the TextView to the "error"
8433      * icon and sets an error message that will be displayed in a popup when
8434      * the TextView has focus.  The icon and error message will be reset to
8435      * null when any key events cause changes to the TextView's text.  If the
8436      * <code>error</code> is <code>null</code>, the error message and icon
8437      * will be cleared.
8438      */
8439     @android.view.RemotableViewMethod
8440     public void setError(CharSequence error) {
8441         if (error == null) {
8442             setError(null, null);
8443         } else {
8444             Drawable dr = getContext().getDrawable(
8445                     com.android.internal.R.drawable.indicator_input_error);
8446 
8447             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
8448             setError(error, dr);
8449         }
8450     }
8451 
8452     /**
8453      * Sets the right-hand compound drawable of the TextView to the specified
8454      * icon and sets an error message that will be displayed in a popup when
8455      * the TextView has focus.  The icon and error message will be reset to
8456      * null when any key events cause changes to the TextView's text.  The
8457      * drawable must already have had {@link Drawable#setBounds} set on it.
8458      * If the <code>error</code> is <code>null</code>, the error message will
8459      * be cleared (and you should provide a <code>null</code> icon as well).
8460      */
8461     public void setError(CharSequence error, Drawable icon) {
8462         createEditorIfNeeded();
8463         mEditor.setError(error, icon);
8464         notifyViewAccessibilityStateChangedIfNeeded(
8465                 AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR
8466                         | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID);
8467     }
8468 
8469     @Override
8470     protected boolean setFrame(int l, int t, int r, int b) {
8471         boolean result = super.setFrame(l, t, r, b);
8472 
8473         if (mEditor != null) mEditor.setFrame();
8474 
8475         restartMarqueeIfNeeded();
8476 
8477         return result;
8478     }
8479 
8480     private void restartMarqueeIfNeeded() {
8481         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8482             mRestartMarquee = false;
8483             startMarquee();
8484         }
8485     }
8486 
8487     /**
8488      * Sets the list of input filters that will be used if the buffer is
8489      * Editable. Has no effect otherwise.
8490      *
8491      * @attr ref android.R.styleable#TextView_maxLength
8492      */
8493     public void setFilters(InputFilter[] filters) {
8494         if (filters == null) {
8495             throw new IllegalArgumentException();
8496         }
8497 
8498         mFilters = filters;
8499 
8500         if (mText instanceof Editable) {
8501             setFilters((Editable) mText, filters);
8502         }
8503     }
8504 
8505     /**
8506      * Sets the list of input filters on the specified Editable,
8507      * and includes mInput in the list if it is an InputFilter.
8508      */
8509     private void setFilters(Editable e, InputFilter[] filters) {
8510         if (mEditor != null) {
8511             final boolean undoFilter = mEditor.mUndoInputFilter != null;
8512             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
8513             int num = 0;
8514             if (undoFilter) num++;
8515             if (keyFilter) num++;
8516             if (num > 0) {
8517                 InputFilter[] nf = new InputFilter[filters.length + num];
8518 
8519                 System.arraycopy(filters, 0, nf, 0, filters.length);
8520                 num = 0;
8521                 if (undoFilter) {
8522                     nf[filters.length] = mEditor.mUndoInputFilter;
8523                     num++;
8524                 }
8525                 if (keyFilter) {
8526                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
8527                 }
8528 
8529                 e.setFilters(nf);
8530                 return;
8531             }
8532         }
8533         e.setFilters(filters);
8534     }
8535 
8536     /**
8537      * Returns the current list of input filters.
8538      *
8539      * @attr ref android.R.styleable#TextView_maxLength
8540      */
8541     public InputFilter[] getFilters() {
8542         return mFilters;
8543     }
8544 
8545     /////////////////////////////////////////////////////////////////////////
8546 
8547     private int getBoxHeight(Layout l) {
8548         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
8549         int padding = (l == mHintLayout)
8550                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
8551                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
8552         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
8553     }
8554 
8555     @UnsupportedAppUsage
8556     int getVerticalOffset(boolean forceNormal) {
8557         int voffset = 0;
8558         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
8559 
8560         Layout l = mLayout;
8561         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
8562             l = mHintLayout;
8563         }
8564 
8565         if (gravity != Gravity.TOP) {
8566             int boxht = getBoxHeight(l);
8567             int textht = l.getHeight();
8568 
8569             if (textht < boxht) {
8570                 if (gravity == Gravity.BOTTOM) {
8571                     voffset = boxht - textht;
8572                 } else { // (gravity == Gravity.CENTER_VERTICAL)
8573                     voffset = (boxht - textht) >> 1;
8574                 }
8575             }
8576         }
8577         return voffset;
8578     }
8579 
8580     private int getBottomVerticalOffset(boolean forceNormal) {
8581         int voffset = 0;
8582         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
8583 
8584         Layout l = mLayout;
8585         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
8586             l = mHintLayout;
8587         }
8588 
8589         if (gravity != Gravity.BOTTOM) {
8590             int boxht = getBoxHeight(l);
8591             int textht = l.getHeight();
8592 
8593             if (textht < boxht) {
8594                 if (gravity == Gravity.TOP) {
8595                     voffset = boxht - textht;
8596                 } else { // (gravity == Gravity.CENTER_VERTICAL)
8597                     voffset = (boxht - textht) >> 1;
8598                 }
8599             }
8600         }
8601         return voffset;
8602     }
8603 
8604     void invalidateCursorPath() {
8605         if (mHighlightPathBogus) {
8606             invalidateCursor();
8607         } else {
8608             final int horizontalPadding = getCompoundPaddingLeft();
8609             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
8610 
8611             if (mEditor.mDrawableForCursor == null) {
8612                 synchronized (TEMP_RECTF) {
8613                     /*
8614                      * The reason for this concern about the thickness of the
8615                      * cursor and doing the floor/ceil on the coordinates is that
8616                      * some EditTexts (notably textfields in the Browser) have
8617                      * anti-aliased text where not all the characters are
8618                      * necessarily at integer-multiple locations.  This should
8619                      * make sure the entire cursor gets invalidated instead of
8620                      * sometimes missing half a pixel.
8621                      */
8622                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
8623                     if (thick < 1.0f) {
8624                         thick = 1.0f;
8625                     }
8626 
8627                     thick /= 2.0f;
8628 
8629                     // mHighlightPath is guaranteed to be non null at that point.
8630                     mHighlightPath.computeBounds(TEMP_RECTF, false);
8631 
8632                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
8633                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
8634                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
8635                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
8636                 }
8637             } else {
8638                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
8639                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
8640                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
8641             }
8642         }
8643     }
8644 
8645     void invalidateCursor() {
8646         int where = getSelectionEnd();
8647 
8648         invalidateCursor(where, where, where);
8649     }
8650 
8651     private void invalidateCursor(int a, int b, int c) {
8652         if (a >= 0 || b >= 0 || c >= 0) {
8653             int start = Math.min(Math.min(a, b), c);
8654             int end = Math.max(Math.max(a, b), c);
8655             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
8656         }
8657     }
8658 
8659     /**
8660      * Invalidates the region of text enclosed between the start and end text offsets.
8661      */
8662     void invalidateRegion(int start, int end, boolean invalidateCursor) {
8663         if (mLayout == null) {
8664             invalidate();
8665         } else {
8666             start = originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
8667             end = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
8668             int lineStart = mLayout.getLineForOffset(start);
8669             int top = mLayout.getLineTop(lineStart);
8670 
8671             // This is ridiculous, but the descent from the line above
8672             // can hang down into the line we really want to redraw,
8673             // so we have to invalidate part of the line above to make
8674             // sure everything that needs to be redrawn really is.
8675             // (But not the whole line above, because that would cause
8676             // the same problem with the descenders on the line above it!)
8677             if (lineStart > 0) {
8678                 top -= mLayout.getLineDescent(lineStart - 1);
8679             }
8680 
8681             int lineEnd;
8682 
8683             if (start == end) {
8684                 lineEnd = lineStart;
8685             } else {
8686                 lineEnd = mLayout.getLineForOffset(end);
8687             }
8688 
8689             int bottom = mLayout.getLineBottom(lineEnd);
8690 
8691             // mEditor can be null in case selection is set programmatically.
8692             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
8693                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
8694                 top = Math.min(top, bounds.top);
8695                 bottom = Math.max(bottom, bounds.bottom);
8696             }
8697 
8698             final int compoundPaddingLeft = getCompoundPaddingLeft();
8699             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
8700 
8701             int left, right;
8702             if (lineStart == lineEnd && !invalidateCursor) {
8703                 left = (int) mLayout.getPrimaryHorizontal(start);
8704                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
8705                 left += compoundPaddingLeft;
8706                 right += compoundPaddingLeft;
8707             } else {
8708                 // Rectangle bounding box when the region spans several lines
8709                 left = compoundPaddingLeft;
8710                 right = getWidth() - getCompoundPaddingRight();
8711             }
8712 
8713             invalidate(mScrollX + left, verticalPadding + top,
8714                     mScrollX + right, verticalPadding + bottom);
8715         }
8716     }
8717 
8718     private void registerForPreDraw() {
8719         if (!mPreDrawRegistered) {
8720             getViewTreeObserver().addOnPreDrawListener(this);
8721             mPreDrawRegistered = true;
8722         }
8723     }
8724 
8725     private void unregisterForPreDraw() {
8726         getViewTreeObserver().removeOnPreDrawListener(this);
8727         mPreDrawRegistered = false;
8728         mPreDrawListenerDetached = false;
8729     }
8730 
8731     /**
8732      * {@inheritDoc}
8733      */
8734     @Override
8735     public boolean onPreDraw() {
8736         if (mLayout == null) {
8737             assumeLayout();
8738         }
8739 
8740         if (mMovement != null) {
8741             /* This code also provides auto-scrolling when a cursor is moved using a
8742              * CursorController (insertion point or selection limits).
8743              * For selection, ensure start or end is visible depending on controller's state.
8744              */
8745             int curs = getSelectionEnd();
8746             // Do not create the controller if it is not already created.
8747             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
8748                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
8749                 curs = getSelectionStart();
8750             }
8751 
8752             /*
8753              * TODO: This should really only keep the end in view if
8754              * it already was before the text changed.  I'm not sure
8755              * of a good way to tell from here if it was.
8756              */
8757             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8758                 curs = mText.length();
8759             }
8760 
8761             if (curs >= 0) {
8762                 bringPointIntoView(curs);
8763             }
8764         } else {
8765             bringTextIntoView();
8766         }
8767 
8768         // This has to be checked here since:
8769         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
8770         //   a screen rotation) since layout is not yet initialized at that point.
8771         if (mEditor != null && mEditor.mCreatedWithASelection) {
8772             mEditor.refreshTextActionMode();
8773             mEditor.mCreatedWithASelection = false;
8774         }
8775 
8776         unregisterForPreDraw();
8777 
8778         return true;
8779     }
8780 
8781     @Override
8782     protected void onAttachedToWindow() {
8783         super.onAttachedToWindow();
8784 
8785         if (mEditor != null) mEditor.onAttachedToWindow();
8786 
8787         if (mPreDrawListenerDetached) {
8788             getViewTreeObserver().addOnPreDrawListener(this);
8789             mPreDrawListenerDetached = false;
8790         }
8791     }
8792 
8793     /** @hide */
8794     @Override
8795     protected void onDetachedFromWindowInternal() {
8796         if (mPreDrawRegistered) {
8797             getViewTreeObserver().removeOnPreDrawListener(this);
8798             mPreDrawListenerDetached = true;
8799         }
8800 
8801         resetResolvedDrawables();
8802 
8803         if (mEditor != null) mEditor.onDetachedFromWindow();
8804 
8805         super.onDetachedFromWindowInternal();
8806     }
8807 
8808     @Override
8809     public void onScreenStateChanged(int screenState) {
8810         super.onScreenStateChanged(screenState);
8811         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
8812     }
8813 
8814     @Override
8815     protected boolean isPaddingOffsetRequired() {
8816         return mShadowRadius != 0 || mDrawables != null;
8817     }
8818 
8819     @Override
8820     protected int getLeftPaddingOffset() {
8821         return getCompoundPaddingLeft() - mPaddingLeft
8822                 + (int) Math.min(0, mShadowDx - mShadowRadius);
8823     }
8824 
8825     @Override
8826     protected int getTopPaddingOffset() {
8827         return (int) Math.min(0, mShadowDy - mShadowRadius);
8828     }
8829 
8830     @Override
8831     protected int getBottomPaddingOffset() {
8832         return (int) Math.max(0, mShadowDy + mShadowRadius);
8833     }
8834 
8835     @Override
8836     protected int getRightPaddingOffset() {
8837         return -(getCompoundPaddingRight() - mPaddingRight)
8838                 + (int) Math.max(0, mShadowDx + mShadowRadius);
8839     }
8840 
8841     @Override
8842     protected boolean verifyDrawable(@NonNull Drawable who) {
8843         final boolean verified = super.verifyDrawable(who);
8844         if (!verified && mDrawables != null) {
8845             for (Drawable dr : mDrawables.mShowing) {
8846                 if (who == dr) {
8847                     return true;
8848                 }
8849             }
8850         }
8851         return verified;
8852     }
8853 
8854     @Override
8855     public void jumpDrawablesToCurrentState() {
8856         super.jumpDrawablesToCurrentState();
8857         if (mDrawables != null) {
8858             for (Drawable dr : mDrawables.mShowing) {
8859                 if (dr != null) {
8860                     dr.jumpToCurrentState();
8861                 }
8862             }
8863         }
8864     }
8865 
8866     @Override
8867     public void invalidateDrawable(@NonNull Drawable drawable) {
8868         boolean handled = false;
8869 
8870         if (verifyDrawable(drawable)) {
8871             final Rect dirty = drawable.getBounds();
8872             int scrollX = mScrollX;
8873             int scrollY = mScrollY;
8874 
8875             // IMPORTANT: The coordinates below are based on the coordinates computed
8876             // for each compound drawable in onDraw(). Make sure to update each section
8877             // accordingly.
8878             final TextView.Drawables drawables = mDrawables;
8879             if (drawables != null) {
8880                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
8881                     final int compoundPaddingTop = getCompoundPaddingTop();
8882                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8883                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8884 
8885                     scrollX += mPaddingLeft;
8886                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
8887                     handled = true;
8888                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
8889                     final int compoundPaddingTop = getCompoundPaddingTop();
8890                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8891                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8892 
8893                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
8894                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
8895                     handled = true;
8896                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
8897                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8898                     final int compoundPaddingRight = getCompoundPaddingRight();
8899                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8900 
8901                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
8902                     scrollY += mPaddingTop;
8903                     handled = true;
8904                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
8905                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8906                     final int compoundPaddingRight = getCompoundPaddingRight();
8907                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8908 
8909                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
8910                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
8911                     handled = true;
8912                 }
8913             }
8914 
8915             if (handled) {
8916                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
8917                         dirty.right + scrollX, dirty.bottom + scrollY);
8918             }
8919         }
8920 
8921         if (!handled) {
8922             super.invalidateDrawable(drawable);
8923         }
8924     }
8925 
8926     @Override
8927     public boolean hasOverlappingRendering() {
8928         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
8929         return ((getBackground() != null && getBackground().getCurrent() != null)
8930                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
8931                 || mShadowColor != 0);
8932     }
8933 
8934     /**
8935      *
8936      * Returns the state of the {@code textIsSelectable} flag (See
8937      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
8938      * to allow users to select and copy text in a non-editable TextView, the content of an
8939      * {@link EditText} can always be selected, independently of the value of this flag.
8940      * <p>
8941      *
8942      * @return True if the text displayed in this TextView can be selected by the user.
8943      *
8944      * @attr ref android.R.styleable#TextView_textIsSelectable
8945      */
8946     @InspectableProperty(name = "textIsSelectable")
8947     public boolean isTextSelectable() {
8948         return mEditor == null ? false : mEditor.mTextIsSelectable;
8949     }
8950 
8951     /**
8952      * Sets whether the content of this view is selectable by the user. The default is
8953      * {@code false}, meaning that the content is not selectable.
8954      * <p>
8955      * When you use a TextView to display a useful piece of information to the user (such as a
8956      * contact's address), make it selectable, so that the user can select and copy its
8957      * content. You can also use set the XML attribute
8958      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
8959      * <p>
8960      * When you call this method to set the value of {@code textIsSelectable}, it sets
8961      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
8962      * and {@code longClickable} to the same value. These flags correspond to the attributes
8963      * {@link android.R.styleable#View_focusable android:focusable},
8964      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
8965      * {@link android.R.styleable#View_clickable android:clickable}, and
8966      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
8967      * flags to a state you had set previously, call one or more of the following methods:
8968      * {@link #setFocusable(boolean) setFocusable()},
8969      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
8970      * {@link #setClickable(boolean) setClickable()} or
8971      * {@link #setLongClickable(boolean) setLongClickable()}.
8972      *
8973      * @param selectable Whether the content of this TextView should be selectable.
8974      */
8975     public void setTextIsSelectable(boolean selectable) {
8976         if (!selectable && mEditor == null) return; // false is default value with no edit data
8977 
8978         createEditorIfNeeded();
8979         if (mEditor.mTextIsSelectable == selectable) return;
8980 
8981         mEditor.mTextIsSelectable = selectable;
8982         setFocusableInTouchMode(selectable);
8983         setFocusable(FOCUSABLE_AUTO);
8984         setClickable(selectable);
8985         setLongClickable(selectable);
8986 
8987         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
8988 
8989         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
8990         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
8991 
8992         // Called by setText above, but safer in case of future code changes
8993         mEditor.prepareCursorControllers();
8994     }
8995 
8996     @Override
8997     protected int[] onCreateDrawableState(int extraSpace) {
8998         final int[] drawableState;
8999 
9000         if (mSingleLine) {
9001             drawableState = super.onCreateDrawableState(extraSpace);
9002         } else {
9003             drawableState = super.onCreateDrawableState(extraSpace + 1);
9004             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
9005         }
9006 
9007         if (isTextSelectable()) {
9008             // Disable pressed state, which was introduced when TextView was made clickable.
9009             // Prevents text color change.
9010             // setClickable(false) would have a similar effect, but it also disables focus changes
9011             // and long press actions, which are both needed by text selection.
9012             final int length = drawableState.length;
9013             for (int i = 0; i < length; i++) {
9014                 if (drawableState[i] == R.attr.state_pressed) {
9015                     final int[] nonPressedState = new int[length - 1];
9016                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
9017                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
9018                     return nonPressedState;
9019                 }
9020             }
9021         }
9022 
9023         return drawableState;
9024     }
9025 
9026     private void maybeUpdateHighlightPaths() {
9027         if (!mHighlightPathsBogus) {
9028             return;
9029         }
9030 
9031         if (mHighlightPaths != null) {
9032             mPathRecyclePool.addAll(mHighlightPaths);
9033             mHighlightPaths.clear();
9034             mHighlightPaints.clear();
9035         } else {
9036             mHighlightPaths = new ArrayList<>();
9037             mHighlightPaints = new ArrayList<>();
9038         }
9039 
9040         if (mHighlights != null) {
9041             for (int i = 0; i < mHighlights.getSize(); ++i) {
9042                 final int[] ranges = mHighlights.getRanges(i);
9043                 final Paint paint = mHighlights.getPaint(i);
9044                 final Path path;
9045                 if (mPathRecyclePool.isEmpty()) {
9046                     path = new Path();
9047                 } else {
9048                     path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9049                     mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9050                     path.reset();
9051                 }
9052 
9053                 boolean atLeastOnePathAdded = false;
9054                 for (int j = 0; j < ranges.length / 2; ++j) {
9055                     final int start = ranges[2 * j];
9056                     final int end = ranges[2 * j + 1];
9057                     if (start < end) {
9058                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
9059                                 path.addRect(left, top, right, bottom, Path.Direction.CW)
9060                         );
9061                         atLeastOnePathAdded = true;
9062                     }
9063                 }
9064                 if (atLeastOnePathAdded) {
9065                     mHighlightPaths.add(path);
9066                     mHighlightPaints.add(paint);
9067                 }
9068             }
9069         }
9070 
9071         addSearchHighlightPaths();
9072 
9073         if (hasGesturePreviewHighlight()) {
9074             final Path path;
9075             if (mPathRecyclePool.isEmpty()) {
9076                 path = new Path();
9077             } else {
9078                 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9079                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9080                 path.reset();
9081             }
9082             mLayout.getSelectionPath(
9083                     mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path);
9084             mHighlightPaths.add(path);
9085             mHighlightPaints.add(mGesturePreviewHighlightPaint);
9086         }
9087 
9088         mHighlightPathsBogus = false;
9089     }
9090 
9091     private void addSearchHighlightPaths() {
9092         if (mSearchResultHighlights != null) {
9093             final Path searchResultPath;
9094             if (mPathRecyclePool.isEmpty()) {
9095                 searchResultPath = new Path();
9096             } else {
9097                 searchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9098                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9099                 searchResultPath.reset();
9100             }
9101             final Path focusedSearchResultPath;
9102             if (mFocusedSearchResultIndex == FOCUSED_SEARCH_RESULT_INDEX_NONE) {
9103                 focusedSearchResultPath = null;
9104             } else if (mPathRecyclePool.isEmpty()) {
9105                 focusedSearchResultPath = new Path();
9106             } else {
9107                 focusedSearchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9108                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9109                 focusedSearchResultPath.reset();
9110             }
9111 
9112             boolean atLeastOnePathAdded = false;
9113             for (int j = 0; j < mSearchResultHighlights.length / 2; ++j) {
9114                 final int start = mSearchResultHighlights[2 * j];
9115                 final int end = mSearchResultHighlights[2 * j + 1];
9116                 if (start < end) {
9117                     if (j == mFocusedSearchResultIndex) {
9118                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
9119                                 focusedSearchResultPath.addRect(left, top, right, bottom,
9120                                         Path.Direction.CW)
9121                         );
9122                     } else {
9123                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
9124                                 searchResultPath.addRect(left, top, right, bottom,
9125                                         Path.Direction.CW)
9126                         );
9127                         atLeastOnePathAdded = true;
9128                     }
9129                 }
9130             }
9131             if (atLeastOnePathAdded) {
9132                 if (mSearchResultHighlightPaint == null) {
9133                     mSearchResultHighlightPaint = new Paint();
9134                 }
9135                 mSearchResultHighlightPaint.setColor(mSearchResultHighlightColor);
9136                 mSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
9137                 mHighlightPaths.add(searchResultPath);
9138                 mHighlightPaints.add(mSearchResultHighlightPaint);
9139             }
9140             if (focusedSearchResultPath != null) {
9141                 if (mFocusedSearchResultHighlightPaint == null) {
9142                     mFocusedSearchResultHighlightPaint = new Paint();
9143                 }
9144                 mFocusedSearchResultHighlightPaint.setColor(mFocusedSearchResultHighlightColor);
9145                 mFocusedSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
9146                 mHighlightPaths.add(focusedSearchResultPath);
9147                 mHighlightPaints.add(mFocusedSearchResultHighlightPaint);
9148             }
9149         }
9150     }
9151 
9152     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
9153     private Path getUpdatedHighlightPath() {
9154         Path highlight = null;
9155         Paint highlightPaint = mHighlightPaint;
9156 
9157         final int selStart = getSelectionStartTransformed();
9158         final int selEnd = getSelectionEndTransformed();
9159         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
9160             if (selStart == selEnd) {
9161                 if (mEditor != null && mEditor.shouldRenderCursor()) {
9162                     if (mHighlightPathBogus) {
9163                         if (mHighlightPath == null) mHighlightPath = new Path();
9164                         mHighlightPath.reset();
9165                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
9166                         mEditor.updateCursorPosition();
9167                         mHighlightPathBogus = false;
9168                     }
9169 
9170                     // XXX should pass to skin instead of drawing directly
9171                     highlightPaint.setColor(mCurTextColor);
9172                     highlightPaint.setStyle(Paint.Style.STROKE);
9173                     highlight = mHighlightPath;
9174                 }
9175             } else {
9176                 if (mHighlightPathBogus) {
9177                     if (mHighlightPath == null) mHighlightPath = new Path();
9178                     mHighlightPath.reset();
9179                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
9180                     mHighlightPathBogus = false;
9181                 }
9182 
9183                 // XXX should pass to skin instead of drawing directly
9184                 highlightPaint.setColor(mHighlightColor);
9185                 highlightPaint.setStyle(Paint.Style.FILL);
9186 
9187                 highlight = mHighlightPath;
9188             }
9189         }
9190         return highlight;
9191     }
9192 
9193     /**
9194      * @hide
9195      */
9196     public int getHorizontalOffsetForDrawables() {
9197         return 0;
9198     }
9199 
9200     @Override
9201     protected void onDraw(Canvas canvas) {
9202         restartMarqueeIfNeeded();
9203 
9204         // Draw the background for this view
9205         super.onDraw(canvas);
9206 
9207         final int compoundPaddingLeft = getCompoundPaddingLeft();
9208         final int compoundPaddingTop = getCompoundPaddingTop();
9209         final int compoundPaddingRight = getCompoundPaddingRight();
9210         final int compoundPaddingBottom = getCompoundPaddingBottom();
9211         final int scrollX = mScrollX;
9212         final int scrollY = mScrollY;
9213         final int right = mRight;
9214         final int left = mLeft;
9215         final int bottom = mBottom;
9216         final int top = mTop;
9217         final boolean isLayoutRtl = isLayoutRtl();
9218         final int offset = getHorizontalOffsetForDrawables();
9219         final int leftOffset = isLayoutRtl ? 0 : offset;
9220         final int rightOffset = isLayoutRtl ? offset : 0;
9221 
9222         final Drawables dr = mDrawables;
9223         if (dr != null) {
9224             /*
9225              * Compound, not extended, because the icon is not clipped
9226              * if the text height is smaller.
9227              */
9228 
9229             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
9230             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
9231 
9232             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9233             // Make sure to update invalidateDrawable() when changing this code.
9234             if (dr.mShowing[Drawables.LEFT] != null) {
9235                 canvas.save();
9236                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
9237                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
9238                 dr.mShowing[Drawables.LEFT].draw(canvas);
9239                 canvas.restore();
9240             }
9241 
9242             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9243             // Make sure to update invalidateDrawable() when changing this code.
9244             if (dr.mShowing[Drawables.RIGHT] != null) {
9245                 canvas.save();
9246                 canvas.translate(scrollX + right - left - mPaddingRight
9247                         - dr.mDrawableSizeRight - rightOffset,
9248                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
9249                 dr.mShowing[Drawables.RIGHT].draw(canvas);
9250                 canvas.restore();
9251             }
9252 
9253             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9254             // Make sure to update invalidateDrawable() when changing this code.
9255             if (dr.mShowing[Drawables.TOP] != null) {
9256                 canvas.save();
9257                 canvas.translate(scrollX + compoundPaddingLeft
9258                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
9259                 dr.mShowing[Drawables.TOP].draw(canvas);
9260                 canvas.restore();
9261             }
9262 
9263             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9264             // Make sure to update invalidateDrawable() when changing this code.
9265             if (dr.mShowing[Drawables.BOTTOM] != null) {
9266                 canvas.save();
9267                 canvas.translate(scrollX + compoundPaddingLeft
9268                         + (hspace - dr.mDrawableWidthBottom) / 2,
9269                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
9270                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
9271                 canvas.restore();
9272             }
9273         }
9274 
9275         int color = mCurTextColor;
9276 
9277         if (mLayout == null) {
9278             assumeLayout();
9279         }
9280 
9281         Layout layout = mLayout;
9282 
9283         if (mHint != null && !mHideHint && mText.length() == 0) {
9284             if (mHintTextColor != null) {
9285                 color = mCurHintTextColor;
9286             }
9287 
9288             layout = mHintLayout;
9289         }
9290 
9291         mTextPaint.setColor(color);
9292         mTextPaint.drawableState = getDrawableState();
9293 
9294         canvas.save();
9295         /*  Would be faster if we didn't have to do this. Can we chop the
9296             (displayable) text so that we don't need to do this ever?
9297         */
9298 
9299         int extendedPaddingTop = getExtendedPaddingTop();
9300         int extendedPaddingBottom = getExtendedPaddingBottom();
9301 
9302         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
9303         final int maxScrollY = mLayout.getHeight() - vspace;
9304 
9305         float clipLeft = compoundPaddingLeft + scrollX;
9306         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
9307         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
9308         float clipBottom = bottom - top + scrollY
9309                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
9310 
9311         if (mShadowRadius != 0) {
9312             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
9313             clipRight += Math.max(0, mShadowDx + mShadowRadius);
9314 
9315             clipTop += Math.min(0, mShadowDy - mShadowRadius);
9316             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
9317         }
9318 
9319         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
9320 
9321         int voffsetText = 0;
9322         int voffsetCursor = 0;
9323 
9324         // translate in by our padding
9325         /* shortcircuit calling getVerticaOffset() */
9326         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9327             voffsetText = getVerticalOffset(false);
9328             voffsetCursor = getVerticalOffset(true);
9329         }
9330         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
9331 
9332         final int layoutDirection = getLayoutDirection();
9333         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
9334         if (isMarqueeFadeEnabled()) {
9335             if (!mSingleLine && getLineCount() == 1 && canMarquee()
9336                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
9337                 final int width = mRight - mLeft;
9338                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
9339                 final float dx = mLayout.getLineRight(0) - (width - padding);
9340                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9341             }
9342 
9343             if (mMarquee != null && mMarquee.isRunning()) {
9344                 final float dx = -mMarquee.getScroll();
9345                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9346             }
9347         }
9348 
9349         final int cursorOffsetVertical = voffsetCursor - voffsetText;
9350 
9351         maybeUpdateHighlightPaths();
9352         // If there is a gesture preview highlight, then the selection or cursor is not drawn.
9353         Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
9354         if (mEditor != null) {
9355             mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
9356                     mHighlightPaint, cursorOffsetVertical);
9357         } else {
9358             layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
9359                     cursorOffsetVertical);
9360         }
9361 
9362         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
9363             final float dx = mMarquee.getGhostOffset();
9364             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9365             layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
9366                     cursorOffsetVertical);
9367         }
9368 
9369         canvas.restore();
9370     }
9371 
9372     @Override
9373     public void getFocusedRect(Rect r) {
9374         if (mLayout == null) {
9375             super.getFocusedRect(r);
9376             return;
9377         }
9378 
9379         int selEnd = getSelectionEndTransformed();
9380         if (selEnd < 0) {
9381             super.getFocusedRect(r);
9382             return;
9383         }
9384 
9385         int selStart = getSelectionStartTransformed();
9386         if (selStart < 0 || selStart >= selEnd) {
9387             int line = mLayout.getLineForOffset(selEnd);
9388             r.top = mLayout.getLineTop(line);
9389             r.bottom = mLayout.getLineBottom(line);
9390             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
9391             r.right = r.left + 4;
9392         } else {
9393             int lineStart = mLayout.getLineForOffset(selStart);
9394             int lineEnd = mLayout.getLineForOffset(selEnd);
9395             r.top = mLayout.getLineTop(lineStart);
9396             r.bottom = mLayout.getLineBottom(lineEnd);
9397             if (lineStart == lineEnd) {
9398                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
9399                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
9400             } else {
9401                 // Selection extends across multiple lines -- make the focused
9402                 // rect cover the entire width.
9403                 if (mHighlightPathBogus) {
9404                     if (mHighlightPath == null) mHighlightPath = new Path();
9405                     mHighlightPath.reset();
9406                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
9407                     mHighlightPathBogus = false;
9408                 }
9409                 synchronized (TEMP_RECTF) {
9410                     mHighlightPath.computeBounds(TEMP_RECTF, true);
9411                     r.left = (int) TEMP_RECTF.left - 1;
9412                     r.right = (int) TEMP_RECTF.right + 1;
9413                 }
9414             }
9415         }
9416 
9417         // Adjust for padding and gravity.
9418         int paddingLeft = getCompoundPaddingLeft();
9419         int paddingTop = getExtendedPaddingTop();
9420         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9421             paddingTop += getVerticalOffset(false);
9422         }
9423         r.offset(paddingLeft, paddingTop);
9424         int paddingBottom = getExtendedPaddingBottom();
9425         r.bottom += paddingBottom;
9426     }
9427 
9428     /**
9429      * Return the number of lines of text, or 0 if the internal Layout has not
9430      * been built.
9431      */
9432     public int getLineCount() {
9433         return mLayout != null ? mLayout.getLineCount() : 0;
9434     }
9435 
9436     /**
9437      * Return the baseline for the specified line (0...getLineCount() - 1)
9438      * If bounds is not null, return the top, left, right, bottom extents
9439      * of the specified line in it. If the internal Layout has not been built,
9440      * return 0 and set bounds to (0, 0, 0, 0)
9441      * @param line which line to examine (0..getLineCount() - 1)
9442      * @param bounds Optional. If not null, it returns the extent of the line
9443      * @return the Y-coordinate of the baseline
9444      */
9445     public int getLineBounds(int line, Rect bounds) {
9446         if (mLayout == null) {
9447             if (bounds != null) {
9448                 bounds.set(0, 0, 0, 0);
9449             }
9450             return 0;
9451         } else {
9452             int baseline = mLayout.getLineBounds(line, bounds);
9453 
9454             int voffset = getExtendedPaddingTop();
9455             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9456                 voffset += getVerticalOffset(true);
9457             }
9458             if (bounds != null) {
9459                 bounds.offset(getCompoundPaddingLeft(), voffset);
9460             }
9461             return baseline + voffset;
9462         }
9463     }
9464 
9465     @Override
9466     public int getBaseline() {
9467         if (mLayout == null) {
9468             return super.getBaseline();
9469         }
9470 
9471         return getBaselineOffset() + mLayout.getLineBaseline(0);
9472     }
9473 
9474     int getBaselineOffset() {
9475         int voffset = 0;
9476         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9477             voffset = getVerticalOffset(true);
9478         }
9479 
9480         if (isLayoutModeOptical(mParent)) {
9481             voffset -= getOpticalInsets().top;
9482         }
9483 
9484         return getExtendedPaddingTop() + voffset;
9485     }
9486 
9487     /**
9488      * @hide
9489      */
9490     @Override
9491     protected int getFadeTop(boolean offsetRequired) {
9492         if (mLayout == null) return 0;
9493 
9494         int voffset = 0;
9495         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9496             voffset = getVerticalOffset(true);
9497         }
9498 
9499         if (offsetRequired) voffset += getTopPaddingOffset();
9500 
9501         return getExtendedPaddingTop() + voffset;
9502     }
9503 
9504     /**
9505      * @hide
9506      */
9507     @Override
9508     protected int getFadeHeight(boolean offsetRequired) {
9509         return mLayout != null ? mLayout.getHeight() : 0;
9510     }
9511 
9512     @Override
9513     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
9514         if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
9515             if (mSpannable != null && mLinksClickable) {
9516                 final float x = event.getX(pointerIndex);
9517                 final float y = event.getY(pointerIndex);
9518                 final int offset = getOffsetForPosition(x, y);
9519                 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
9520                         ClickableSpan.class);
9521                 if (clickables.length > 0) {
9522                     return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
9523                 }
9524             }
9525             if (isTextSelectable() || isTextEditable()) {
9526                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
9527             }
9528         }
9529         return super.onResolvePointerIcon(event, pointerIndex);
9530     }
9531 
9532     @Override
9533     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
9534         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
9535         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
9536         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
9537         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
9538             return true;
9539         }
9540         return super.onKeyPreIme(keyCode, event);
9541     }
9542 
9543     /**
9544      * @hide
9545      */
9546     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
9547         // Do nothing unless mEditor is in text action mode.
9548         if (mEditor == null || mEditor.getTextActionMode() == null) {
9549             return false;
9550         }
9551 
9552         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
9553             KeyEvent.DispatcherState state = getKeyDispatcherState();
9554             if (state != null) {
9555                 state.startTracking(event, this);
9556             }
9557             return true;
9558         } else if (event.getAction() == KeyEvent.ACTION_UP) {
9559             KeyEvent.DispatcherState state = getKeyDispatcherState();
9560             if (state != null) {
9561                 state.handleUpEvent(event);
9562             }
9563             if (event.isTracking() && !event.isCanceled()) {
9564                 stopTextActionMode();
9565                 return true;
9566             }
9567         }
9568         return false;
9569     }
9570 
9571     @Override
9572     public boolean onKeyDown(int keyCode, KeyEvent event) {
9573         final int which = doKeyDown(keyCode, event, null);
9574         if (which == KEY_EVENT_NOT_HANDLED) {
9575             return super.onKeyDown(keyCode, event);
9576         }
9577 
9578         return true;
9579     }
9580 
9581     @Override
9582     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
9583         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
9584         final int which = doKeyDown(keyCode, down, event);
9585         if (which == KEY_EVENT_NOT_HANDLED) {
9586             // Go through default dispatching.
9587             return super.onKeyMultiple(keyCode, repeatCount, event);
9588         }
9589         if (which == KEY_EVENT_HANDLED) {
9590             // Consumed the whole thing.
9591             return true;
9592         }
9593 
9594         repeatCount--;
9595 
9596         // We are going to dispatch the remaining events to either the input
9597         // or movement method.  To do this, we will just send a repeated stream
9598         // of down and up events until we have done the complete repeatCount.
9599         // It would be nice if those interfaces had an onKeyMultiple() method,
9600         // but adding that is a more complicated change.
9601         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
9602         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
9603             // mEditor and mEditor.mInput are not null from doKeyDown
9604             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
9605             while (--repeatCount > 0) {
9606                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
9607                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
9608             }
9609             hideErrorIfUnchanged();
9610 
9611         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
9612             // mMovement is not null from doKeyDown
9613             mMovement.onKeyUp(this, mSpannable, keyCode, up);
9614             while (--repeatCount > 0) {
9615                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
9616                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
9617             }
9618         }
9619 
9620         return true;
9621     }
9622 
9623     /**
9624      * Returns true if pressing ENTER in this field advances focus instead
9625      * of inserting the character.  This is true mostly in single-line fields,
9626      * but also in mail addresses and subjects which will display on multiple
9627      * lines but where it doesn't make sense to insert newlines.
9628      */
9629     private boolean shouldAdvanceFocusOnEnter() {
9630         if (getKeyListener() == null) {
9631             return false;
9632         }
9633 
9634         if (mSingleLine) {
9635             return true;
9636         }
9637 
9638         if (mEditor != null
9639                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
9640                         == EditorInfo.TYPE_CLASS_TEXT) {
9641             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9642             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
9643                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
9644                 return true;
9645             }
9646         }
9647 
9648         return false;
9649     }
9650 
9651     private boolean isDirectionalNavigationKey(int keyCode) {
9652         switch(keyCode) {
9653             case KeyEvent.KEYCODE_DPAD_UP:
9654             case KeyEvent.KEYCODE_DPAD_DOWN:
9655             case KeyEvent.KEYCODE_DPAD_LEFT:
9656             case KeyEvent.KEYCODE_DPAD_RIGHT:
9657                 return true;
9658         }
9659         return false;
9660     }
9661 
9662     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
9663         if (!isEnabled()) {
9664             return KEY_EVENT_NOT_HANDLED;
9665         }
9666 
9667         // If this is the initial keydown, we don't want to prevent a movement away from this view.
9668         // While this shouldn't be necessary because any time we're preventing default movement we
9669         // should be restricting the focus to remain within this view, thus we'll also receive
9670         // the key up event, occasionally key up events will get dropped and we don't want to
9671         // prevent the user from traversing out of this on the next key down.
9672         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
9673             mPreventDefaultMovement = false;
9674         }
9675 
9676         switch (keyCode) {
9677             case KeyEvent.KEYCODE_ENTER:
9678             case KeyEvent.KEYCODE_NUMPAD_ENTER:
9679                 if (event.hasNoModifiers()) {
9680                     // When mInputContentType is set, we know that we are
9681                     // running in a "modern" cupcake environment, so don't need
9682                     // to worry about the application trying to capture
9683                     // enter key events.
9684                     if (mEditor != null && mEditor.mInputContentType != null) {
9685                         // If there is an action listener, given them a
9686                         // chance to consume the event.
9687                         if (mEditor.mInputContentType.onEditorActionListener != null
9688                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
9689                                         this,
9690                                         getActionIdForEnterEvent(),
9691                                         event)) {
9692                             mEditor.mInputContentType.enterDown = true;
9693                             // We are consuming the enter key for them.
9694                             return KEY_EVENT_HANDLED;
9695                         }
9696                     }
9697 
9698                     // If our editor should move focus when enter is pressed, or
9699                     // this is a generated event from an IME action button, then
9700                     // don't let it be inserted into the text.
9701                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
9702                             || shouldAdvanceFocusOnEnter()) {
9703                         if (hasOnClickListeners()) {
9704                             return KEY_EVENT_NOT_HANDLED;
9705                         }
9706                         return KEY_EVENT_HANDLED;
9707                     }
9708                 }
9709                 break;
9710 
9711             case KeyEvent.KEYCODE_DPAD_CENTER:
9712                 if (event.hasNoModifiers()) {
9713                     if (shouldAdvanceFocusOnEnter()) {
9714                         return KEY_EVENT_NOT_HANDLED;
9715                     }
9716                 }
9717                 break;
9718 
9719             case KeyEvent.KEYCODE_TAB:
9720                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
9721                     // Tab is used to move focus.
9722                     return KEY_EVENT_NOT_HANDLED;
9723                 }
9724                 break;
9725 
9726                 // Has to be done on key down (and not on key up) to correctly be intercepted.
9727             case KeyEvent.KEYCODE_BACK:
9728                 if (mEditor != null && mEditor.getTextActionMode() != null) {
9729                     stopTextActionMode();
9730                     return KEY_EVENT_HANDLED;
9731                 }
9732                 break;
9733 
9734             case KeyEvent.KEYCODE_ESCAPE:
9735                 if (com.android.text.flags.Flags.escapeClearsFocus() && event.hasNoModifiers()) {
9736                     if (mEditor != null && mEditor.getTextActionMode() != null) {
9737                         stopTextActionMode();
9738                         return KEY_EVENT_HANDLED;
9739                     }
9740                     if (hasFocus()) {
9741                         clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
9742                         InputMethodManager imm = getInputMethodManager();
9743                         if (imm != null) {
9744                             imm.hideSoftInputFromView(this, 0);
9745                         }
9746                         return KEY_EVENT_HANDLED;
9747                     }
9748                 }
9749                 break;
9750 
9751             case KeyEvent.KEYCODE_CUT:
9752                 if (event.hasNoModifiers() && canCut()) {
9753                     if (onTextContextMenuItem(ID_CUT)) {
9754                         return KEY_EVENT_HANDLED;
9755                     }
9756                 }
9757                 break;
9758 
9759             case KeyEvent.KEYCODE_COPY:
9760                 if (event.hasNoModifiers() && canCopy()) {
9761                     if (onTextContextMenuItem(ID_COPY)) {
9762                         return KEY_EVENT_HANDLED;
9763                     }
9764                 }
9765                 break;
9766 
9767             case KeyEvent.KEYCODE_PASTE:
9768                 if (event.hasNoModifiers() && canPaste()) {
9769                     if (onTextContextMenuItem(ID_PASTE)) {
9770                         return KEY_EVENT_HANDLED;
9771                     }
9772                 }
9773                 break;
9774 
9775             case KeyEvent.KEYCODE_FORWARD_DEL:
9776                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
9777                     if (onTextContextMenuItem(ID_CUT)) {
9778                         return KEY_EVENT_HANDLED;
9779                     }
9780                 }
9781                 break;
9782 
9783             case KeyEvent.KEYCODE_INSERT:
9784                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
9785                     if (onTextContextMenuItem(ID_COPY)) {
9786                         return KEY_EVENT_HANDLED;
9787                     }
9788                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
9789                     if (onTextContextMenuItem(ID_PASTE)) {
9790                         return KEY_EVENT_HANDLED;
9791                     }
9792                 }
9793                 break;
9794         }
9795 
9796         if (mEditor != null && mEditor.mKeyListener != null) {
9797             boolean doDown = true;
9798             if (otherEvent != null) {
9799                 try {
9800                     beginBatchEdit();
9801                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
9802                             otherEvent);
9803                     hideErrorIfUnchanged();
9804                     doDown = false;
9805                     if (handled) {
9806                         return KEY_EVENT_HANDLED;
9807                     }
9808                 } catch (AbstractMethodError e) {
9809                     // onKeyOther was added after 1.0, so if it isn't
9810                     // implemented we need to try to dispatch as a regular down.
9811                 } finally {
9812                     endBatchEdit();
9813                 }
9814             }
9815 
9816             if (doDown) {
9817                 beginBatchEdit();
9818                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
9819                         keyCode, event);
9820                 endBatchEdit();
9821                 hideErrorIfUnchanged();
9822                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
9823             }
9824         }
9825 
9826         // bug 650865: sometimes we get a key event before a layout.
9827         // don't try to move around if we don't know the layout.
9828 
9829         if (mMovement != null && mLayout != null) {
9830             boolean doDown = true;
9831             if (otherEvent != null) {
9832                 try {
9833                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
9834                     doDown = false;
9835                     if (handled) {
9836                         return KEY_EVENT_HANDLED;
9837                     }
9838                 } catch (AbstractMethodError e) {
9839                     // onKeyOther was added after 1.0, so if it isn't
9840                     // implemented we need to try to dispatch as a regular down.
9841                 }
9842             }
9843             if (doDown) {
9844                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
9845                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
9846                         mPreventDefaultMovement = true;
9847                     }
9848                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
9849                 }
9850             }
9851             // Consume arrows from keyboard devices to prevent focus leaving the editor.
9852             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
9853             // to move focus with arrows.
9854             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
9855                     && isDirectionalNavigationKey(keyCode)) {
9856                 return KEY_EVENT_HANDLED;
9857             }
9858         }
9859 
9860         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
9861                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
9862     }
9863 
9864     /**
9865      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
9866      * can be recorded.
9867      * @hide
9868      */
9869     public void resetErrorChangedFlag() {
9870         /*
9871          * Keep track of what the error was before doing the input
9872          * so that if an input filter changed the error, we leave
9873          * that error showing.  Otherwise, we take down whatever
9874          * error was showing when the user types something.
9875          */
9876         if (mEditor != null) mEditor.mErrorWasChanged = false;
9877     }
9878 
9879     /**
9880      * @hide
9881      */
9882     public void hideErrorIfUnchanged() {
9883         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
9884             setError(null, null);
9885         }
9886     }
9887 
9888     @Override
9889     public boolean onKeyUp(int keyCode, KeyEvent event) {
9890         if (!isEnabled()) {
9891             return super.onKeyUp(keyCode, event);
9892         }
9893 
9894         if (!KeyEvent.isModifierKey(keyCode)) {
9895             mPreventDefaultMovement = false;
9896         }
9897 
9898         switch (keyCode) {
9899             case KeyEvent.KEYCODE_DPAD_CENTER:
9900                 if (event.hasNoModifiers()) {
9901                     /*
9902                      * If there is a click listener, just call through to
9903                      * super, which will invoke it.
9904                      *
9905                      * If there isn't a click listener, try to show the soft
9906                      * input method.  (It will also
9907                      * call performClick(), but that won't do anything in
9908                      * this case.)
9909                      */
9910                     if (!hasOnClickListeners()) {
9911                         if (mMovement != null && mText instanceof Editable
9912                                 && mLayout != null && onCheckIsTextEditor()) {
9913                             InputMethodManager imm = getInputMethodManager();
9914                             viewClicked(imm);
9915                             if (imm != null && getShowSoftInputOnFocus()) {
9916                                 imm.showSoftInput(this, 0);
9917                             }
9918                         }
9919                     }
9920                 }
9921                 return super.onKeyUp(keyCode, event);
9922 
9923             case KeyEvent.KEYCODE_ENTER:
9924             case KeyEvent.KEYCODE_NUMPAD_ENTER:
9925                 if (event.hasNoModifiers()) {
9926                     if (mEditor != null && mEditor.mInputContentType != null
9927                             && mEditor.mInputContentType.onEditorActionListener != null
9928                             && mEditor.mInputContentType.enterDown) {
9929                         mEditor.mInputContentType.enterDown = false;
9930                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
9931                                 this, getActionIdForEnterEvent(), event)) {
9932                             return true;
9933                         }
9934                     }
9935 
9936                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
9937                             || shouldAdvanceFocusOnEnter()) {
9938                         /*
9939                          * If there is a click listener, just call through to
9940                          * super, which will invoke it.
9941                          *
9942                          * If there isn't a click listener, try to advance focus,
9943                          * but still call through to super, which will reset the
9944                          * pressed state and longpress state.  (It will also
9945                          * call performClick(), but that won't do anything in
9946                          * this case.)
9947                          */
9948                         if (!hasOnClickListeners()) {
9949                             View v = focusSearch(FOCUS_DOWN);
9950 
9951                             if (v != null) {
9952                                 if (!v.requestFocus(FOCUS_DOWN)) {
9953                                     throw new IllegalStateException("focus search returned a view "
9954                                             + "that wasn't able to take focus!");
9955                                 }
9956 
9957                                 /*
9958                                  * Return true because we handled the key; super
9959                                  * will return false because there was no click
9960                                  * listener.
9961                                  */
9962                                 super.onKeyUp(keyCode, event);
9963                                 return true;
9964                             } else if ((event.getFlags()
9965                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
9966                                 // No target for next focus, but make sure the IME
9967                                 // if this came from it.
9968                                 InputMethodManager imm = getInputMethodManager();
9969                                 if (imm != null) {
9970                                     imm.hideSoftInputFromView(this, 0);
9971                                 }
9972                             }
9973                         }
9974                     }
9975                     return super.onKeyUp(keyCode, event);
9976                 }
9977                 break;
9978         }
9979 
9980         if (mEditor != null && mEditor.mKeyListener != null) {
9981             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
9982                 return true;
9983             }
9984         }
9985 
9986         if (mMovement != null && mLayout != null) {
9987             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
9988                 return true;
9989             }
9990         }
9991 
9992         return super.onKeyUp(keyCode, event);
9993     }
9994 
9995     private int getActionIdForEnterEvent() {
9996         // If it's not single line, no action
9997         if (!isSingleLine()) {
9998             return EditorInfo.IME_NULL;
9999         }
10000         // Return the action that was specified for Enter
10001         return getImeOptions() & EditorInfo.IME_MASK_ACTION;
10002     }
10003 
10004     @Override
10005     public boolean onCheckIsTextEditor() {
10006         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
10007     }
10008 
10009     private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) {
10010         final View nextView = focusSearch(direction);
10011         return nextView != null && nextView.onCheckIsTextEditor();
10012     }
10013 
10014     @Override
10015     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
10016         if (onCheckIsTextEditor() && isEnabled()) {
10017             mEditor.createInputMethodStateIfNeeded();
10018             mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = 0;
10019             mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = 0;
10020 
10021             outAttrs.inputType = getInputType();
10022             if (mEditor.mInputContentType != null) {
10023                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
10024                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
10025                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
10026                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
10027                 outAttrs.extras = mEditor.mInputContentType.extras;
10028                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
10029             } else {
10030                 outAttrs.imeOptions = EditorInfo.IME_NULL;
10031                 outAttrs.hintLocales = null;
10032             }
10033             if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) {
10034                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
10035             }
10036             if (hasEditorInFocusSearchDirection(FOCUS_UP)) {
10037                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
10038             }
10039             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
10040                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
10041                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
10042                     // An action has not been set, but the enter key will move to
10043                     // the next focus, so set the action to that.
10044                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
10045                 } else {
10046                     // An action has not been set, and there is no focus to move
10047                     // to, so let's just supply a "done" action.
10048                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
10049                 }
10050                 if (!shouldAdvanceFocusOnEnter()) {
10051                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
10052                 }
10053             }
10054             if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) {
10055                 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT;
10056             }
10057             if (isMultilineInputType(outAttrs.inputType)) {
10058                 // Multi-line text editors should always show an enter key.
10059                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
10060             }
10061             outAttrs.hintText = mHint;
10062             outAttrs.targetInputMethodUser = mTextOperationUser;
10063             if (mText instanceof Editable) {
10064                 InputConnection ic = new EditableInputConnection(this);
10065                 outAttrs.initialSelStart = getSelectionStart();
10066                 outAttrs.initialSelEnd = getSelectionEnd();
10067                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
10068                 outAttrs.setInitialSurroundingText(mText);
10069                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
10070                 if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) {
10071                     boolean handwritingEnabled = isAutoHandwritingEnabled();
10072                     outAttrs.setStylusHandwritingEnabled(handwritingEnabled);
10073                     // AndroidX Core library 1.13.0 introduced
10074                     // EditorInfoCompat#setStylusHandwritingEnabled and
10075                     // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the
10076                     // EditorInfo extras bundle. These methods do not set or check the Android V
10077                     // property since the Android V SDK was not yet available. In order for
10078                     // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for
10079                     // EditorInfo created by Android V TextView, the extras bundle value is also set
10080                     // here.
10081                     if (outAttrs.extras == null) {
10082                         outAttrs.extras = new Bundle();
10083                     }
10084                     outAttrs.extras.putBoolean(
10085                             STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled);
10086                 }
10087                 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
10088                 gestures.add(SelectGesture.class);
10089                 gestures.add(SelectRangeGesture.class);
10090                 gestures.add(DeleteGesture.class);
10091                 gestures.add(DeleteRangeGesture.class);
10092                 gestures.add(InsertGesture.class);
10093                 gestures.add(RemoveSpaceGesture.class);
10094                 gestures.add(JoinOrSplitGesture.class);
10095                 gestures.add(InsertModeGesture.class);
10096                 outAttrs.setSupportedHandwritingGestures(gestures);
10097 
10098                 Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>();
10099                 previews.add(SelectGesture.class);
10100                 previews.add(SelectRangeGesture.class);
10101                 previews.add(DeleteGesture.class);
10102                 previews.add(DeleteRangeGesture.class);
10103                 outAttrs.setSupportedHandwritingGesturePreviews(previews);
10104 
10105                 return ic;
10106             }
10107         }
10108         return null;
10109     }
10110 
10111     /**
10112      * Called back by the system to handle {@link InputConnection#requestCursorUpdates(int, int)}.
10113      *
10114      * @param cursorUpdateMode modes defined in {@link InputConnection.CursorUpdateMode}.
10115      * @param cursorUpdateFilter modes defined in {@link InputConnection.CursorUpdateFilter}.
10116      *
10117      * @hide
10118      */
10119     public void onRequestCursorUpdatesInternal(
10120             @InputConnection.CursorUpdateMode int cursorUpdateMode,
10121             @InputConnection.CursorUpdateFilter int cursorUpdateFilter) {
10122         mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = cursorUpdateMode;
10123         mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = cursorUpdateFilter;
10124         if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) == 0) {
10125             return;
10126         }
10127         if (isInLayout()) {
10128             // In this case, the view hierarchy is currently undergoing a layout pass.
10129             // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
10130             // pass is finished.
10131         } else {
10132             // This will schedule a layout pass of the view tree, and the layout event
10133             // eventually triggers IMM#updateCursorAnchorInfo.
10134             requestLayout();
10135         }
10136     }
10137 
10138     /**
10139      * If this TextView contains editable content, extract a portion of it
10140      * based on the information in <var>request</var> in to <var>outText</var>.
10141      * @return Returns true if the text was successfully extracted, else false.
10142      */
10143     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
10144         createEditorIfNeeded();
10145         return mEditor.extractText(request, outText);
10146     }
10147 
10148     /**
10149      * This is used to remove all style-impacting spans from text before new
10150      * extracted text is being replaced into it, so that we don't have any
10151      * lingering spans applied during the replace.
10152      */
10153     static void removeParcelableSpans(Spannable spannable, int start, int end) {
10154         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
10155         int i = spans.length;
10156         while (i > 0) {
10157             i--;
10158             spannable.removeSpan(spans[i]);
10159         }
10160     }
10161 
10162     /**
10163      * Apply to this text view the given extracted text, as previously
10164      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
10165      */
10166     public void setExtractedText(ExtractedText text) {
10167         Editable content = getEditableText();
10168         if (text.text != null) {
10169             if (content == null) {
10170                 setText(text.text, TextView.BufferType.EDITABLE);
10171             } else {
10172                 int start = 0;
10173                 int end = content.length();
10174 
10175                 if (text.partialStartOffset >= 0) {
10176                     final int N = content.length();
10177                     start = text.partialStartOffset;
10178                     if (start > N) start = N;
10179                     end = text.partialEndOffset;
10180                     if (end > N) end = N;
10181                 }
10182 
10183                 removeParcelableSpans(content, start, end);
10184                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
10185                     if (text.text instanceof Spanned) {
10186                         // OK to copy spans only.
10187                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
10188                                 Object.class, content, start);
10189                     }
10190                 } else {
10191                     content.replace(start, end, text.text);
10192                 }
10193             }
10194         }
10195 
10196         // Now set the selection position...  make sure it is in range, to
10197         // avoid crashes.  If this is a partial update, it is possible that
10198         // the underlying text may have changed, causing us problems here.
10199         // Also we just don't want to trust clients to do the right thing.
10200         Spannable sp = (Spannable) getText();
10201         final int N = sp.length();
10202         int start = text.selectionStart;
10203         if (start < 0) {
10204             start = 0;
10205         } else if (start > N) {
10206             start = N;
10207         }
10208         int end = text.selectionEnd;
10209         if (end < 0) {
10210             end = 0;
10211         } else if (end > N) {
10212             end = N;
10213         }
10214         Selection.setSelection(sp, start, end);
10215 
10216         // Finally, update the selection mode.
10217         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
10218             MetaKeyKeyListener.startSelecting(this, sp);
10219         } else {
10220             MetaKeyKeyListener.stopSelecting(this, sp);
10221         }
10222 
10223         setHintInternal(text.hint);
10224     }
10225 
10226     /**
10227      * @hide
10228      */
10229     public void setExtracting(ExtractedTextRequest req) {
10230         if (mEditor.mInputMethodState != null) {
10231             mEditor.mInputMethodState.mExtractedTextRequest = req;
10232         }
10233         // This would stop a possible selection mode, but no such mode is started in case
10234         // extracted mode will start. Some text is selected though, and will trigger an action mode
10235         // in the extracted view.
10236         mEditor.hideCursorAndSpanControllers();
10237         stopTextActionMode();
10238         if (mEditor.mSelectionModifierCursorController != null) {
10239             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
10240         }
10241     }
10242 
10243     /**
10244      * Called by the framework in response to a text completion from
10245      * the current input method, provided by it calling
10246      * {@link InputConnection#commitCompletion
10247      * InputConnection.commitCompletion()}.  The default implementation does
10248      * nothing; text views that are supporting auto-completion should override
10249      * this to do their desired behavior.
10250      *
10251      * @param text The auto complete text the user has selected.
10252      */
10253     public void onCommitCompletion(CompletionInfo text) {
10254         // intentionally empty
10255     }
10256 
10257     /**
10258      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
10259      * dictionary) from the current input method, provided by it calling
10260      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
10261      * The default implementation flashes the background of the corrected word to provide
10262      * feedback to the user.
10263      *
10264      * @param info The auto correct info about the text that was corrected.
10265      */
10266     public void onCommitCorrection(CorrectionInfo info) {
10267         if (mEditor != null) mEditor.onCommitCorrection(info);
10268     }
10269 
10270     public void beginBatchEdit() {
10271         if (mEditor != null) mEditor.beginBatchEdit();
10272     }
10273 
10274     public void endBatchEdit() {
10275         if (mEditor != null) mEditor.endBatchEdit();
10276     }
10277 
10278     /**
10279      * Called by the framework in response to a request to begin a batch
10280      * of edit operations through a call to link {@link #beginBatchEdit()}.
10281      */
10282     public void onBeginBatchEdit() {
10283         // intentionally empty
10284     }
10285 
10286     /**
10287      * Called by the framework in response to a request to end a batch
10288      * of edit operations through a call to link {@link #endBatchEdit}.
10289      */
10290     public void onEndBatchEdit() {
10291         // intentionally empty
10292     }
10293 
10294     /** @hide */
10295     public void onPerformSpellCheck() {
10296         if (mEditor != null && mEditor.mSpellChecker != null) {
10297             mEditor.mSpellChecker.onPerformSpellCheck();
10298         }
10299     }
10300 
10301     /**
10302      * Called by the framework in response to a private command from the
10303      * current method, provided by it calling
10304      * {@link InputConnection#performPrivateCommand
10305      * InputConnection.performPrivateCommand()}.
10306      *
10307      * @param action The action name of the command.
10308      * @param data Any additional data for the command.  This may be null.
10309      * @return Return true if you handled the command, else false.
10310      */
10311     public boolean onPrivateIMECommand(String action, Bundle data) {
10312         return false;
10313     }
10314 
10315     /**
10316      * Return whether the text is transformed and has {@link OffsetMapping}.
10317      * @hide
10318      */
10319     public boolean isOffsetMappingAvailable() {
10320         return mTransformation != null && mTransformed instanceof OffsetMapping;
10321     }
10322 
10323     /** @hide */
10324     public boolean previewHandwritingGesture(
10325             @NonNull PreviewableHandwritingGesture gesture,
10326             @Nullable CancellationSignal cancellationSignal) {
10327         if (gesture instanceof SelectGesture) {
10328             performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true);
10329         } else if (gesture instanceof SelectRangeGesture) {
10330             performHandwritingSelectRangeGesture(
10331                     (SelectRangeGesture) gesture, /* isPreview= */ true);
10332         } else if (gesture instanceof DeleteGesture) {
10333             performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true);
10334         } else if (gesture instanceof DeleteRangeGesture) {
10335             performHandwritingDeleteRangeGesture(
10336                     (DeleteRangeGesture) gesture, /* isPreview= */ true);
10337         } else {
10338             return false;
10339         }
10340         if (cancellationSignal != null) {
10341             cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight);
10342         }
10343         return true;
10344     }
10345 
10346     /** @hide */
10347     public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
10348         return performHandwritingSelectGesture(gesture, /* isPreview= */ false);
10349     }
10350 
10351     private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) {
10352         if (isOffsetMappingAvailable()) {
10353             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10354         }
10355         int[] range = getRangeForRect(
10356                 convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
10357                 gesture.getGranularity());
10358         if (range == null) {
10359             return handleGestureFailure(gesture, isPreview);
10360         }
10361         return performHandwritingSelectGesture(range, isPreview);
10362     }
10363 
10364     private int performHandwritingSelectGesture(int[] range, boolean isPreview) {
10365         if (isPreview) {
10366             setSelectGesturePreviewHighlight(range[0], range[1]);
10367         } else {
10368             Selection.setSelection(getEditableText(), range[0], range[1]);
10369             mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
10370         }
10371         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10372     }
10373 
10374     /** @hide */
10375     public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) {
10376         return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false);
10377     }
10378 
10379     private int performHandwritingSelectRangeGesture(
10380             @NonNull SelectRangeGesture gesture, boolean isPreview) {
10381         if (isOffsetMappingAvailable()) {
10382             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10383         }
10384         int[] startRange = getRangeForRect(
10385                 convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
10386                 gesture.getGranularity());
10387         if (startRange == null) {
10388             return handleGestureFailure(gesture, isPreview);
10389         }
10390         int[] endRange = getRangeForRect(
10391                 convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()),
10392                 gesture.getGranularity());
10393         if (endRange == null) {
10394             return handleGestureFailure(gesture, isPreview);
10395         }
10396         int[] range = new int[] {
10397                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
10398         };
10399         return performHandwritingSelectGesture(range, isPreview);
10400     }
10401 
10402     /** @hide */
10403     public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
10404         return performHandwritingDeleteGesture(gesture, /* isPreview= */ false);
10405     }
10406 
10407     private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) {
10408         if (isOffsetMappingAvailable()) {
10409             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10410         }
10411         int[] range = getRangeForRect(
10412                 convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
10413                 gesture.getGranularity());
10414         if (range == null) {
10415             return handleGestureFailure(gesture, isPreview);
10416         }
10417         return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
10418     }
10419 
10420     private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) {
10421         if (isPreview) {
10422             setDeleteGesturePreviewHighlight(range[0], range[1]);
10423         } else {
10424             if (granularity == HandwritingGesture.GRANULARITY_WORD) {
10425                 range = adjustHandwritingDeleteGestureRange(range);
10426             }
10427 
10428             Selection.setSelection(getEditableText(), range[0]);
10429             getEditableText().delete(range[0], range[1]);
10430         }
10431         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10432     }
10433 
10434     /** @hide */
10435     public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) {
10436         return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false);
10437     }
10438 
10439     private int performHandwritingDeleteRangeGesture(
10440             @NonNull DeleteRangeGesture gesture, boolean isPreview) {
10441         if (isOffsetMappingAvailable()) {
10442             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10443         }
10444         int[] startRange = getRangeForRect(
10445                 convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
10446                 gesture.getGranularity());
10447         if (startRange == null) {
10448             return handleGestureFailure(gesture, isPreview);
10449         }
10450         int[] endRange = getRangeForRect(
10451                 convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()),
10452                 gesture.getGranularity());
10453         if (endRange == null) {
10454             return handleGestureFailure(gesture, isPreview);
10455         }
10456         int[] range = new int[] {
10457                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
10458         };
10459         return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
10460     }
10461 
10462     private int[] adjustHandwritingDeleteGestureRange(int[] range) {
10463         // For handwriting delete gestures with word granularity, adjust the start and end offsets
10464         // to remove extra whitespace around the deleted text.
10465 
10466         int start = range[0];
10467         int end = range[1];
10468 
10469         // If the deleted text is at the start of the text, the behavior is the same as the case
10470         // where the deleted text follows a new line character.
10471         int codePointBeforeStart = start > 0
10472                 ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT;
10473         // If the deleted text is at the end of the text, the behavior is the same as the case where
10474         // the deleted text precedes a new line character.
10475         int codePointAtEnd = end < mText.length()
10476                 ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT;
10477 
10478         if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)
10479                 && (TextUtils.isWhitespace(codePointAtEnd)
10480                         || TextUtils.isPunctuation(codePointAtEnd))) {
10481             // Remove whitespace (except new lines) before the deleted text, in these cases:
10482             // - There is whitespace following the deleted text
10483             //     e.g. "one [deleted] three" -> "one | three" -> "one| three"
10484             // - There is punctuation following the deleted text
10485             //     e.g. "one [deleted]!" -> "one |!" -> "one|!"
10486             // - There is a new line following the deleted text
10487             //     e.g. "one [deleted]\n" -> "one |\n" -> "one|\n"
10488             // - The deleted text is at the end of the text
10489             //     e.g. "one [deleted]" -> "one |" -> "one|"
10490             // (The pipe | indicates the cursor position.)
10491             do {
10492                 start -= Character.charCount(codePointBeforeStart);
10493                 if (start == 0) break;
10494                 codePointBeforeStart = Character.codePointBefore(mText, start);
10495             } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart));
10496             return new int[] {start, end};
10497         }
10498 
10499         if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)
10500                 && (TextUtils.isWhitespace(codePointBeforeStart)
10501                         || TextUtils.isPunctuation(codePointBeforeStart))) {
10502             // Remove whitespace (except new lines) after the deleted text, in these cases:
10503             // - There is punctuation preceding the deleted text
10504             //     e.g. "([deleted] two)" -> "(| two)" -> "(|two)"
10505             // - There is a new line preceding the deleted text
10506             //     e.g. "\n[deleted] two" -> "\n| two" -> "\n|two"
10507             // - The deleted text is at the start of the text
10508             //     e.g. "[deleted] two" -> "| two" -> "|two"
10509             // (The pipe | indicates the cursor position.)
10510             do {
10511                 end += Character.charCount(codePointAtEnd);
10512                 if (end == mText.length()) break;
10513                 codePointAtEnd = Character.codePointAt(mText, end);
10514             } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd));
10515             return new int[] {start, end};
10516         }
10517 
10518         // Return the original range.
10519         return range;
10520     }
10521 
10522     /** @hide */
10523     public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
10524         if (isOffsetMappingAvailable()) {
10525             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10526         }
10527         PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
10528         int line = getLineForHandwritingGesture(point);
10529         if (line == -1) {
10530             return handleGestureFailure(gesture);
10531         }
10532         int offset = mLayout.getOffsetForHorizontal(line, point.x);
10533         String textToInsert = gesture.getTextToInsert();
10534         return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture);
10535         // TODO(b/243980426): Insert extra spaces if necessary.
10536     }
10537 
10538     /** @hide */
10539     public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) {
10540         if (isOffsetMappingAvailable()) {
10541             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10542         }
10543         PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());
10544         PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint());
10545 
10546         // The operation should be applied to the first line of text containing one of the points.
10547         int startPointLine = getLineForHandwritingGesture(startPoint);
10548         int endPointLine = getLineForHandwritingGesture(endPoint);
10549         int line;
10550         if (startPointLine == -1) {
10551             if (endPointLine == -1) {
10552                 return handleGestureFailure(gesture);
10553             }
10554             line = endPointLine;
10555         } else {
10556             line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine);
10557         }
10558 
10559         // The operation should be applied to all characters touched by the line joining the points.
10560         float lineVerticalCenter = (mLayout.getLineTop(line)
10561                 + mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) / 2f;
10562         // Create a rectangle which is +/-0.1f around the line's vertical center, so that the
10563         // rectangle doesn't touch the line above or below. (The line height is at least 1f.)
10564         RectF area = new RectF(
10565                 Math.min(startPoint.x, endPoint.x),
10566                 lineVerticalCenter + 0.1f,
10567                 Math.max(startPoint.x, endPoint.x),
10568                 lineVerticalCenter - 0.1f);
10569         int[] range = mLayout.getRangeForRect(
10570                 area, new GraphemeClusterSegmentFinder(mText, mTextPaint),
10571                 Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
10572         if (range == null) {
10573             return handleGestureFailure(gesture);
10574         }
10575         int startOffset = range[0];
10576         int endOffset = range[1];
10577         // TODO(b/247557062): This doesn't handle bidirectional text correctly.
10578 
10579         Pattern whitespacePattern = getWhitespacePattern();
10580         Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
10581         int lastRemoveOffset = -1;
10582         while (matcher.find()) {
10583             lastRemoveOffset = startOffset + matcher.start();
10584             getEditableText().delete(lastRemoveOffset, startOffset + matcher.end());
10585             startOffset = lastRemoveOffset;
10586             endOffset -= matcher.end() - matcher.start();
10587             if (startOffset == endOffset) {
10588                 break;
10589             }
10590             matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
10591         }
10592         if (lastRemoveOffset == -1) {
10593             return handleGestureFailure(gesture);
10594         }
10595         Selection.setSelection(getEditableText(), lastRemoveOffset);
10596         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10597     }
10598 
10599     /** @hide */
10600     public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {
10601         if (isOffsetMappingAvailable()) {
10602             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10603         }
10604         PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint());
10605 
10606         int line = getLineForHandwritingGesture(point);
10607         if (line == -1) {
10608             return handleGestureFailure(gesture);
10609         }
10610 
10611         int startOffset = mLayout.getOffsetForHorizontal(line, point.x);
10612         if (mLayout.isLevelBoundary(startOffset)) {
10613             // TODO(b/247551937): Support gesture at level boundaries.
10614             return handleGestureFailure(gesture);
10615         }
10616 
10617         int endOffset = startOffset;
10618         while (startOffset > 0) {
10619             int codePointBeforeStart = Character.codePointBefore(mText, startOffset);
10620             if (!TextUtils.isWhitespace(codePointBeforeStart)) {
10621                 break;
10622             }
10623             startOffset -= Character.charCount(codePointBeforeStart);
10624         }
10625         while (endOffset < mText.length()) {
10626             int codePointAtEnd = Character.codePointAt(mText, endOffset);
10627             if (!TextUtils.isWhitespace(codePointAtEnd)) {
10628                 break;
10629             }
10630             endOffset += Character.charCount(codePointAtEnd);
10631         }
10632         if (startOffset < endOffset) {
10633             Selection.setSelection(getEditableText(), startOffset);
10634             getEditableText().delete(startOffset, endOffset);
10635             return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10636         } else {
10637             // No whitespace found, so insert a space.
10638             return tryInsertTextForHandwritingGesture(startOffset, " ", gesture);
10639         }
10640     }
10641 
10642     /** @hide */
10643     public int performHandwritingInsertModeGesture(@NonNull InsertModeGesture gesture) {
10644         final PointF insertPoint =
10645                 convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
10646         final int line = getLineForHandwritingGesture(insertPoint);
10647         final CancellationSignal cancellationSignal = gesture.getCancellationSignal();
10648 
10649         // If no cancellationSignal is provided, don't enter the insert mode.
10650         if (line == -1 || cancellationSignal == null) {
10651             return handleGestureFailure(gesture);
10652         }
10653 
10654         final int offset = mLayout.getOffsetForHorizontal(line, insertPoint.x);
10655 
10656         if (!mEditor.enterInsertMode(offset)) {
10657             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10658         }
10659         cancellationSignal.setOnCancelListener(() -> mEditor.exitInsertMode());
10660         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10661     }
10662 
10663     private int handleGestureFailure(HandwritingGesture gesture) {
10664         return handleGestureFailure(gesture, /* isPreview= */ false);
10665     }
10666 
10667     private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) {
10668         clearGesturePreviewHighlight();
10669         if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) {
10670             getEditableText()
10671                     .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText());
10672             return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK;
10673         }
10674         return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10675     }
10676 
10677     /**
10678      * Returns the closest line such that the point is either inside the line bounds or within
10679      * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns
10680      * -1 if the point is not within the margin of any line bounds.
10681      */
10682     private int getLineForHandwritingGesture(PointF point) {
10683         int line = mLayout.getLineForVertical((int) point.y);
10684         int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin();
10685         if (line < mLayout.getLineCount() - 1
10686                 && point.y > mLayout.getLineBottom(line) - lineMargin
10687                 && point.y
10688                         > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) {
10689             // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical
10690             // returns i. If the point is within lineMargin of line (i + 1), and closer to line
10691             // (i + 1) than line i, then the gesture operation should be applied to line (i + 1).
10692             line++;
10693         } else if (point.y < mLayout.getLineTop(line) - lineMargin
10694                 || point.y
10695                         > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)
10696                                 + lineMargin) {
10697             // The point is not within lineMargin of a line.
10698             return -1;
10699         }
10700         if (point.x < -lineMargin || point.x > mLayout.getWidth() + lineMargin) {
10701             // The point is not within lineMargin of a line.
10702             return -1;
10703         }
10704         return line;
10705     }
10706 
10707     @Nullable
10708     private int[] getRangeForRect(@NonNull RectF area, int granularity) {
10709         SegmentFinder segmentFinder;
10710         if (granularity == HandwritingGesture.GRANULARITY_WORD) {
10711             WordIterator wordIterator = getWordIterator();
10712             wordIterator.setCharSequence(mText, 0, mText.length());
10713             segmentFinder = new WordSegmentFinder(mText, wordIterator);
10714         } else {
10715             segmentFinder = new GraphemeClusterSegmentFinder(mText, mTextPaint);
10716         }
10717 
10718         return mLayout.getRangeForRect(
10719                 area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
10720     }
10721 
10722     private int tryInsertTextForHandwritingGesture(
10723             int offset, String textToInsert, HandwritingGesture gesture) {
10724         // A temporary cursor span is placed at the insertion offset. The span will be pushed
10725         // forward when text is inserted, then the real cursor can be placed after the inserted
10726         // text. A temporary cursor span is used in order to avoid modifying the real selection span
10727         // in the case that the text is filtered out.
10728         Editable editableText = getEditableText();
10729         if (mTempCursor == null) {
10730             mTempCursor = new NoCopySpan.Concrete();
10731         }
10732         editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT);
10733 
10734         editableText.insert(offset, textToInsert);
10735 
10736         int newOffset = editableText.getSpanStart(mTempCursor);
10737         editableText.removeSpan(mTempCursor);
10738         if (newOffset == offset) {
10739             // The inserted text was filtered out.
10740             return handleGestureFailure(gesture);
10741         } else {
10742             // Place the cursor after the inserted text.
10743             Selection.setSelection(editableText, newOffset);
10744             return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10745         }
10746     }
10747 
10748     private Pattern getWhitespacePattern() {
10749         if (mWhitespacePattern == null) {
10750             mWhitespacePattern = Pattern.compile("\\s+");
10751         }
10752         return mWhitespacePattern;
10753     }
10754 
10755     /** @hide */
10756     @VisibleForTesting
10757     @UnsupportedAppUsage
10758     public void nullLayouts() {
10759         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
10760             mSavedLayout = (BoringLayout) mLayout;
10761         }
10762         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
10763             mSavedHintLayout = (BoringLayout) mHintLayout;
10764         }
10765 
10766         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
10767 
10768         mBoring = mHintBoring = null;
10769 
10770         // Since it depends on the value of mLayout
10771         if (mEditor != null) mEditor.prepareCursorControllers();
10772     }
10773 
10774     /**
10775      * Make a new Layout based on the already-measured size of the view,
10776      * on the assumption that it was measured correctly at some point.
10777      */
10778     @UnsupportedAppUsage
10779     private void assumeLayout() {
10780         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10781 
10782         if (width < 1) {
10783             width = 0;
10784         }
10785 
10786         int physicalWidth = width;
10787 
10788         if (mHorizontallyScrolling) {
10789             width = VERY_WIDE;
10790         }
10791 
10792         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
10793                       physicalWidth, false);
10794     }
10795 
10796     @UnsupportedAppUsage
10797     private Layout.Alignment getLayoutAlignment() {
10798         Layout.Alignment alignment;
10799         switch (getTextAlignment()) {
10800             case TEXT_ALIGNMENT_GRAVITY:
10801                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
10802                     case Gravity.START:
10803                         alignment = Layout.Alignment.ALIGN_NORMAL;
10804                         break;
10805                     case Gravity.END:
10806                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
10807                         break;
10808                     case Gravity.LEFT:
10809                         alignment = Layout.Alignment.ALIGN_LEFT;
10810                         break;
10811                     case Gravity.RIGHT:
10812                         alignment = Layout.Alignment.ALIGN_RIGHT;
10813                         break;
10814                     case Gravity.CENTER_HORIZONTAL:
10815                         alignment = Layout.Alignment.ALIGN_CENTER;
10816                         break;
10817                     default:
10818                         alignment = Layout.Alignment.ALIGN_NORMAL;
10819                         break;
10820                 }
10821                 break;
10822             case TEXT_ALIGNMENT_TEXT_START:
10823                 alignment = Layout.Alignment.ALIGN_NORMAL;
10824                 break;
10825             case TEXT_ALIGNMENT_TEXT_END:
10826                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
10827                 break;
10828             case TEXT_ALIGNMENT_CENTER:
10829                 alignment = Layout.Alignment.ALIGN_CENTER;
10830                 break;
10831             case TEXT_ALIGNMENT_VIEW_START:
10832                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
10833                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
10834                 break;
10835             case TEXT_ALIGNMENT_VIEW_END:
10836                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
10837                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
10838                 break;
10839             case TEXT_ALIGNMENT_INHERIT:
10840                 // This should never happen as we have already resolved the text alignment
10841                 // but better safe than sorry so we just fall through
10842             default:
10843                 alignment = Layout.Alignment.ALIGN_NORMAL;
10844                 break;
10845         }
10846         return alignment;
10847     }
10848 
10849     private Paint.FontMetrics getResolvedMinimumFontMetrics() {
10850         if (mMinimumFontMetrics != null) {
10851             return mMinimumFontMetrics;
10852         }
10853         if (!mUseLocalePreferredLineHeightForMinimum) {
10854             return null;
10855         }
10856 
10857         if (mLocalePreferredFontMetrics == null) {
10858             mLocalePreferredFontMetrics = new Paint.FontMetrics();
10859         }
10860         mTextPaint.getFontMetricsForLocale(mLocalePreferredFontMetrics);
10861         return mLocalePreferredFontMetrics;
10862     }
10863 
10864     /**
10865      * The width passed in is now the desired layout width,
10866      * not the full view width with padding.
10867      * {@hide}
10868      */
10869     @VisibleForTesting
10870     @UnsupportedAppUsage
10871     public void makeNewLayout(int wantWidth, int hintWidth,
10872                                  BoringLayout.Metrics boring,
10873                                  BoringLayout.Metrics hintBoring,
10874                                  int ellipsisWidth, boolean bringIntoView) {
10875         stopMarquee();
10876 
10877         // Update "old" cached values
10878         mOldMaximum = mMaximum;
10879         mOldMaxMode = mMaxMode;
10880 
10881         mHighlightPathBogus = true;
10882         mHighlightPathsBogus = true;
10883 
10884         if (wantWidth < 0) {
10885             wantWidth = 0;
10886         }
10887         if (hintWidth < 0) {
10888             hintWidth = 0;
10889         }
10890 
10891         Layout.Alignment alignment = getLayoutAlignment();
10892         final boolean testDirChange = mSingleLine && mLayout != null
10893                 && (alignment == Layout.Alignment.ALIGN_NORMAL
10894                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
10895         int oldDir = 0;
10896         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
10897         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
10898         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
10899                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
10900         TruncateAt effectiveEllipsize = mEllipsize;
10901         if (mEllipsize == TruncateAt.MARQUEE
10902                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10903             effectiveEllipsize = TruncateAt.END_SMALL;
10904         }
10905 
10906         if (mTextDir == null) {
10907             mTextDir = getTextDirectionHeuristic();
10908         }
10909 
10910         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
10911                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
10912         if (switchEllipsize) {
10913             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
10914                     ? TruncateAt.END : TruncateAt.MARQUEE;
10915             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
10916                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
10917         }
10918 
10919         shouldEllipsize = mEllipsize != null;
10920         mHintLayout = null;
10921 
10922         if (mHint != null) {
10923             if (shouldEllipsize) hintWidth = wantWidth;
10924 
10925             if (hintBoring == UNKNOWN_BORING) {
10926                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
10927                         isFallbackLineSpacingForBoringLayout(),
10928                         getResolvedMinimumFontMetrics(), mHintBoring);
10929 
10930                 if (hintBoring != null) {
10931                     mHintBoring = hintBoring;
10932                 }
10933             }
10934 
10935             if (hintBoring != null) {
10936                 if (hintBoring.width <= hintWidth
10937                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
10938                     if (mSavedHintLayout != null) {
10939                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
10940                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10941                                 hintBoring, mIncludePad);
10942                     } else {
10943                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
10944                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10945                                 hintBoring, mIncludePad);
10946                     }
10947 
10948                     mSavedHintLayout = (BoringLayout) mHintLayout;
10949                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
10950                     if (mSavedHintLayout != null) {
10951                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
10952                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10953                                 hintBoring, mIncludePad, mEllipsize,
10954                                 ellipsisWidth);
10955                     } else {
10956                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
10957                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10958                                 hintBoring, mIncludePad, mEllipsize,
10959                                 ellipsisWidth);
10960                     }
10961                 }
10962             }
10963             // TODO: code duplication with makeSingleLayout()
10964             if (mHintLayout == null) {
10965                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
10966                         mHint.length(), mTextPaint, hintWidth)
10967                         .setAlignment(alignment)
10968                         .setTextDirection(mTextDir)
10969                         .setLineSpacing(mSpacingAdd, mSpacingMult)
10970                         .setIncludePad(mIncludePad)
10971                         .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
10972                         .setBreakStrategy(mBreakStrategy)
10973                         .setHyphenationFrequency(mHyphenationFrequency)
10974                         .setJustificationMode(mJustificationMode)
10975                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
10976                         .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
10977                                 mLineBreakStyle, mLineBreakWordStyle))
10978                         .setUseBoundsForWidth(mUseBoundsForWidth)
10979                         .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
10980 
10981                 if (shouldEllipsize) {
10982                     builder.setEllipsize(mEllipsize)
10983                             .setEllipsizedWidth(ellipsisWidth);
10984                 }
10985                 mHintLayout = builder.build();
10986             }
10987         }
10988 
10989         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
10990             registerForPreDraw();
10991         }
10992 
10993         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10994             if (!compressText(ellipsisWidth)) {
10995                 final int height = mLayoutParams.height;
10996                 // If the size of the view does not depend on the size of the text, try to
10997                 // start the marquee immediately
10998                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
10999                     startMarquee();
11000                 } else {
11001                     // Defer the start of the marquee until we know our width (see setFrame())
11002                     mRestartMarquee = true;
11003                 }
11004             }
11005         }
11006 
11007         // CursorControllers need a non-null mLayout
11008         if (mEditor != null) mEditor.prepareCursorControllers();
11009     }
11010 
11011     /**
11012      * Returns true if DynamicLayout is required
11013      *
11014      * @hide
11015      */
11016     @VisibleForTesting
11017     public boolean useDynamicLayout() {
11018         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
11019     }
11020 
11021     /**
11022      * @hide
11023      */
11024     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
11025             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
11026             boolean useSaved) {
11027         Layout result = null;
11028         if (useDynamicLayout()) {
11029             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
11030                     wantWidth)
11031                     .setDisplayText(mTransformed)
11032                     .setAlignment(alignment)
11033                     .setTextDirection(mTextDir)
11034                     .setLineSpacing(mSpacingAdd, mSpacingMult)
11035                     .setIncludePad(mIncludePad)
11036                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11037                     .setBreakStrategy(mBreakStrategy)
11038                     .setHyphenationFrequency(mHyphenationFrequency)
11039                     .setJustificationMode(mJustificationMode)
11040                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11041                             mLineBreakStyle, mLineBreakWordStyle))
11042                     .setUseBoundsForWidth(mUseBoundsForWidth)
11043                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
11044                     .setEllipsizedWidth(ellipsisWidth)
11045                     .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11046             result = builder.build();
11047         } else {
11048             if (boring == UNKNOWN_BORING) {
11049                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
11050                         isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
11051                         mBoring);
11052                 if (boring != null) {
11053                     mBoring = boring;
11054                 }
11055             }
11056 
11057             if (boring != null) {
11058                 if (boring.width <= wantWidth
11059                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
11060                     if (useSaved && mSavedLayout != null) {
11061                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
11062                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
11063                                 boring, mIncludePad, null, wantWidth,
11064                                 isFallbackLineSpacingForBoringLayout(),
11065                                 mUseBoundsForWidth, getResolvedMinimumFontMetrics());
11066                     } else {
11067                         result = new BoringLayout(
11068                                 mTransformed,
11069                                 mTextPaint,
11070                                 wantWidth,
11071                                 alignment,
11072                                 mSpacingMult,
11073                                 mSpacingAdd,
11074                                 mIncludePad,
11075                                 isFallbackLineSpacingForBoringLayout(),
11076                                 wantWidth,
11077                                 null,
11078                                 boring,
11079                                 mUseBoundsForWidth,
11080                                 mShiftDrawingOffsetForStartOverhang,
11081                                 getResolvedMinimumFontMetrics());
11082                     }
11083 
11084                     if (useSaved) {
11085                         mSavedLayout = (BoringLayout) result;
11086                     }
11087                 } else if (shouldEllipsize && boring.width <= wantWidth) {
11088                     if (useSaved && mSavedLayout != null) {
11089                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
11090                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
11091                                 boring, mIncludePad, effectiveEllipsize,
11092                                 ellipsisWidth, isFallbackLineSpacingForBoringLayout(),
11093                                 mUseBoundsForWidth, getResolvedMinimumFontMetrics());
11094                     } else {
11095                         result = new BoringLayout(
11096                                 mTransformed,
11097                                 mTextPaint,
11098                                 wantWidth,
11099                                 alignment,
11100                                 mSpacingMult,
11101                                 mSpacingAdd,
11102                                 mIncludePad,
11103                                 isFallbackLineSpacingForBoringLayout(),
11104                                 ellipsisWidth,
11105                                 effectiveEllipsize,
11106                                 boring,
11107                                 mUseBoundsForWidth,
11108                                 mShiftDrawingOffsetForStartOverhang,
11109                                 getResolvedMinimumFontMetrics());
11110                     }
11111                 }
11112             }
11113         }
11114         if (result == null) {
11115             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
11116                     0, mTransformed.length(), mTextPaint, wantWidth)
11117                     .setAlignment(alignment)
11118                     .setTextDirection(mTextDir)
11119                     .setLineSpacing(mSpacingAdd, mSpacingMult)
11120                     .setIncludePad(mIncludePad)
11121                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11122                     .setBreakStrategy(mBreakStrategy)
11123                     .setHyphenationFrequency(mHyphenationFrequency)
11124                     .setJustificationMode(mJustificationMode)
11125                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
11126                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11127                             mLineBreakStyle, mLineBreakWordStyle))
11128                     .setUseBoundsForWidth(mUseBoundsForWidth)
11129                     .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11130             if (shouldEllipsize) {
11131                 builder.setEllipsize(effectiveEllipsize)
11132                         .setEllipsizedWidth(ellipsisWidth);
11133             }
11134             result = builder.build();
11135         }
11136         return result;
11137     }
11138 
11139     @UnsupportedAppUsage
11140     private boolean compressText(float width) {
11141         if (isHardwareAccelerated()) return false;
11142 
11143         // Only compress the text if it hasn't been compressed by the previous pass
11144         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
11145                 && mTextPaint.getTextScaleX() == 1.0f) {
11146             final float textWidth = mLayout.getLineWidth(0);
11147             final float overflow = (textWidth + 1.0f - width) / width;
11148             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
11149                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
11150                 post(new Runnable() {
11151                     public void run() {
11152                         requestLayout();
11153                     }
11154                 });
11155                 return true;
11156             }
11157         }
11158 
11159         return false;
11160     }
11161 
11162     private static int desired(Layout layout, boolean useBoundsForWidth) {
11163         int n = layout.getLineCount();
11164         CharSequence text = layout.getText();
11165         float max = 0;
11166 
11167         // if any line was wrapped, we can't use it.
11168         // but it's ok for the last line not to have a newline
11169 
11170         for (int i = 0; i < n - 1; i++) {
11171             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
11172                 return -1;
11173             }
11174         }
11175 
11176         for (int i = 0; i < n; i++) {
11177             max = Math.max(max, layout.getLineMax(i));
11178         }
11179 
11180         if (useBoundsForWidth) {
11181             max = Math.max(max, layout.computeDrawingBoundingBox().width());
11182         }
11183 
11184         return (int) Math.ceil(max);
11185     }
11186 
11187     /**
11188      * Set whether the TextView includes extra top and bottom padding to make
11189      * room for accents that go above the normal ascent and descent.
11190      * The default is true.
11191      *
11192      * @see #getIncludeFontPadding()
11193      *
11194      * @attr ref android.R.styleable#TextView_includeFontPadding
11195      */
11196     public void setIncludeFontPadding(boolean includepad) {
11197         if (mIncludePad != includepad) {
11198             mIncludePad = includepad;
11199 
11200             if (mLayout != null) {
11201                 nullLayouts();
11202                 requestLayout();
11203                 invalidate();
11204             }
11205         }
11206     }
11207 
11208     /**
11209      * Gets whether the TextView includes extra top and bottom padding to make
11210      * room for accents that go above the normal ascent and descent.
11211      *
11212      * @see #setIncludeFontPadding(boolean)
11213      *
11214      * @attr ref android.R.styleable#TextView_includeFontPadding
11215      */
11216     @InspectableProperty
11217     public boolean getIncludeFontPadding() {
11218         return mIncludePad;
11219     }
11220 
11221     /** @hide */
11222     @VisibleForTesting
11223     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
11224 
11225     @Override
11226     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
11227         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
11228         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
11229         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
11230         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
11231 
11232         int width;
11233         int height;
11234 
11235         BoringLayout.Metrics boring = UNKNOWN_BORING;
11236         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
11237 
11238         if (mTextDir == null) {
11239             mTextDir = getTextDirectionHeuristic();
11240         }
11241 
11242         int des = -1;
11243         boolean fromexisting = false;
11244         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
11245                 ?  (float) widthSize : Float.MAX_VALUE;
11246 
11247         if (widthMode == MeasureSpec.EXACTLY) {
11248             // Parent has told us how big to be. So be it.
11249             width = widthSize;
11250         } else {
11251             if (mLayout != null && mEllipsize == null) {
11252                 des = desired(mLayout, mUseBoundsForWidth);
11253             }
11254 
11255             if (des < 0) {
11256                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
11257                         isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
11258                         mBoring);
11259                 if (boring != null) {
11260                     mBoring = boring;
11261                 }
11262             } else {
11263                 fromexisting = true;
11264             }
11265 
11266             if (boring == null || boring == UNKNOWN_BORING) {
11267                 if (des < 0) {
11268                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
11269                             mTransformed.length(), mTextPaint, mTextDir, widthLimit,
11270                             mUseBoundsForWidth));
11271                 }
11272                 width = des;
11273             } else {
11274                 if (mUseBoundsForWidth) {
11275                     RectF bbox = boring.getDrawingBoundingBox();
11276                     float rightMax = Math.max(bbox.right, boring.width);
11277                     float leftMin = Math.min(bbox.left, 0);
11278                     width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
11279                 } else {
11280                     width = boring.width;
11281                 }
11282             }
11283 
11284             final Drawables dr = mDrawables;
11285             if (dr != null) {
11286                 width = Math.max(width, dr.mDrawableWidthTop);
11287                 width = Math.max(width, dr.mDrawableWidthBottom);
11288             }
11289 
11290             if (mHint != null) {
11291                 int hintDes = -1;
11292                 int hintWidth;
11293 
11294                 if (mHintLayout != null && mEllipsize == null) {
11295                     hintDes = desired(mHintLayout, mUseBoundsForWidth);
11296                 }
11297 
11298                 if (hintDes < 0) {
11299                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
11300                             isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
11301                             mHintBoring);
11302                     if (hintBoring != null) {
11303                         mHintBoring = hintBoring;
11304                     }
11305                 }
11306 
11307                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
11308                     if (hintDes < 0) {
11309                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
11310                                 mHint.length(), mTextPaint, mTextDir, widthLimit,
11311                                 mUseBoundsForWidth));
11312                     }
11313                     hintWidth = hintDes;
11314                 } else {
11315                     hintWidth = hintBoring.width;
11316                 }
11317 
11318                 if (hintWidth > width) {
11319                     width = hintWidth;
11320                 }
11321             }
11322 
11323             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
11324 
11325             if (mMaxWidthMode == EMS) {
11326                 width = Math.min(width, mMaxWidth * getLineHeight());
11327             } else {
11328                 width = Math.min(width, mMaxWidth);
11329             }
11330 
11331             if (mMinWidthMode == EMS) {
11332                 width = Math.max(width, mMinWidth * getLineHeight());
11333             } else {
11334                 width = Math.max(width, mMinWidth);
11335             }
11336 
11337             // Check against our minimum width
11338             width = Math.max(width, getSuggestedMinimumWidth());
11339 
11340             if (widthMode == MeasureSpec.AT_MOST) {
11341                 width = Math.min(widthSize, width);
11342             }
11343         }
11344 
11345         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
11346         int unpaddedWidth = want;
11347 
11348         if (mHorizontallyScrolling) want = VERY_WIDE;
11349 
11350         int hintWant = want;
11351         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
11352 
11353         if (mLayout == null) {
11354             makeNewLayout(want, hintWant, boring, hintBoring,
11355                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
11356         } else {
11357             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
11358                     || (mLayout.getEllipsizedWidth()
11359                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
11360 
11361             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
11362                     && (want > mLayout.getWidth())
11363                     && (mLayout instanceof BoringLayout
11364                             || (fromexisting && des >= 0 && des <= want));
11365 
11366             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
11367 
11368             if (layoutChanged || maximumChanged) {
11369                 if (!maximumChanged && widthChanged) {
11370                     mLayout.increaseWidthTo(want);
11371                 } else {
11372                     makeNewLayout(want, hintWant, boring, hintBoring,
11373                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
11374                 }
11375             } else {
11376                 // Nothing has changed
11377             }
11378         }
11379 
11380         if (heightMode == MeasureSpec.EXACTLY) {
11381             // Parent has told us how big to be. So be it.
11382             height = heightSize;
11383             mDesiredHeightAtMeasure = -1;
11384         } else {
11385             int desired = getDesiredHeight();
11386 
11387             height = desired;
11388             mDesiredHeightAtMeasure = desired;
11389 
11390             if (heightMode == MeasureSpec.AT_MOST) {
11391                 height = Math.min(desired, heightSize);
11392             }
11393         }
11394 
11395         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
11396         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
11397             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
11398         }
11399 
11400         /*
11401          * We didn't let makeNewLayout() register to bring the cursor into view,
11402          * so do it here if there is any possibility that it is needed.
11403          */
11404         if (mMovement != null
11405                 || mLayout.getWidth() > unpaddedWidth
11406                 || mLayout.getHeight() > unpaddedHeight) {
11407             registerForPreDraw();
11408         } else {
11409             scrollTo(0, 0);
11410         }
11411 
11412         setMeasuredDimension(width, height);
11413     }
11414 
11415     /**
11416      * Automatically computes and sets the text size.
11417      */
11418     private void autoSizeText() {
11419         if (!isAutoSizeEnabled()) {
11420             return;
11421         }
11422 
11423         if (mNeedsAutoSizeText) {
11424             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
11425                 return;
11426             }
11427 
11428             final int availableWidth = mHorizontallyScrolling
11429                     ? VERY_WIDE
11430                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
11431             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
11432                     - getExtendedPaddingTop();
11433 
11434             if (availableWidth <= 0 || availableHeight <= 0) {
11435                 return;
11436             }
11437 
11438             synchronized (TEMP_RECTF) {
11439                 TEMP_RECTF.setEmpty();
11440                 TEMP_RECTF.right = availableWidth;
11441                 TEMP_RECTF.bottom = availableHeight;
11442                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
11443 
11444                 if (optimalTextSize != getTextSize()) {
11445                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
11446                             false /* shouldRequestLayout */);
11447 
11448                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
11449                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
11450                             false /* bringIntoView */);
11451                 }
11452             }
11453         }
11454         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
11455         // after the next layout pass should set this to false.
11456         mNeedsAutoSizeText = true;
11457     }
11458 
11459     /**
11460      * Performs a binary search to find the largest text size that will still fit within the size
11461      * available to this view.
11462      */
11463     private int findLargestTextSizeWhichFits(RectF availableSpace) {
11464         final int sizesCount = mAutoSizeTextSizesInPx.length;
11465         if (sizesCount == 0) {
11466             throw new IllegalStateException("No available text sizes to choose from.");
11467         }
11468 
11469         int bestSizeIndex = 0;
11470         int lowIndex = bestSizeIndex + 1;
11471         int highIndex = sizesCount - 1;
11472         int sizeToTryIndex;
11473         while (lowIndex <= highIndex) {
11474             sizeToTryIndex = (lowIndex + highIndex) / 2;
11475             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
11476                 bestSizeIndex = lowIndex;
11477                 lowIndex = sizeToTryIndex + 1;
11478             } else {
11479                 highIndex = sizeToTryIndex - 1;
11480                 bestSizeIndex = highIndex;
11481             }
11482         }
11483 
11484         return mAutoSizeTextSizesInPx[bestSizeIndex];
11485     }
11486 
11487     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
11488         final CharSequence text = mTransformed != null
11489                 ? mTransformed
11490                 : getText();
11491         final int maxLines = getMaxLines();
11492         if (mTempTextPaint == null) {
11493             mTempTextPaint = new TextPaint();
11494         } else {
11495             mTempTextPaint.reset();
11496         }
11497         mTempTextPaint.set(getPaint());
11498         mTempTextPaint.setTextSize(suggestedSizeInPx);
11499 
11500         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
11501                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
11502         layoutBuilder.setAlignment(getLayoutAlignment())
11503                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
11504                 .setIncludePad(getIncludeFontPadding())
11505                 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11506                 .setBreakStrategy(getBreakStrategy())
11507                 .setHyphenationFrequency(getHyphenationFrequency())
11508                 .setJustificationMode(getJustificationMode())
11509                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
11510                 .setTextDirection(getTextDirectionHeuristic())
11511                 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11512                         mLineBreakStyle, mLineBreakWordStyle))
11513                 .setUseBoundsForWidth(mUseBoundsForWidth)
11514                 .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11515 
11516         final StaticLayout layout = layoutBuilder.build();
11517 
11518         // Lines overflow.
11519         if (maxLines != -1 && layout.getLineCount() > maxLines) {
11520             return false;
11521         }
11522 
11523         // Height overflow.
11524         if (layout.getHeight() > availableSpace.bottom) {
11525             return false;
11526         }
11527 
11528         return true;
11529     }
11530 
11531     private int getDesiredHeight() {
11532         return Math.max(
11533                 getDesiredHeight(mLayout, true),
11534                 getDesiredHeight(mHintLayout, mEllipsize != null));
11535     }
11536 
11537     private int getDesiredHeight(Layout layout, boolean cap) {
11538         if (layout == null) {
11539             return 0;
11540         }
11541 
11542         /*
11543         * Don't cap the hint to a certain number of lines.
11544         * (Do cap it, though, if we have a maximum pixel height.)
11545         */
11546         int desired = layout.getHeight(cap);
11547 
11548         final Drawables dr = mDrawables;
11549         if (dr != null) {
11550             desired = Math.max(desired, dr.mDrawableHeightLeft);
11551             desired = Math.max(desired, dr.mDrawableHeightRight);
11552         }
11553 
11554         int linecount = layout.getLineCount();
11555         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
11556         desired += padding;
11557 
11558         if (mMaxMode != LINES) {
11559             desired = Math.min(desired, mMaximum);
11560         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
11561                 || layout instanceof BoringLayout)) {
11562             desired = layout.getLineTop(mMaximum);
11563 
11564             if (dr != null) {
11565                 desired = Math.max(desired, dr.mDrawableHeightLeft);
11566                 desired = Math.max(desired, dr.mDrawableHeightRight);
11567             }
11568 
11569             desired += padding;
11570             linecount = mMaximum;
11571         }
11572 
11573         if (mMinMode == LINES) {
11574             if (linecount < mMinimum) {
11575                 desired += getLineHeight() * (mMinimum - linecount);
11576             }
11577         } else {
11578             desired = Math.max(desired, mMinimum);
11579         }
11580 
11581         // Check against our minimum height
11582         desired = Math.max(desired, getSuggestedMinimumHeight());
11583 
11584         return desired;
11585     }
11586 
11587     /**
11588      * Check whether a change to the existing text layout requires a
11589      * new view layout.
11590      */
11591     private void checkForResize() {
11592         boolean sizeChanged = false;
11593 
11594         if (mLayout != null) {
11595             // Check if our width changed
11596             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
11597                 sizeChanged = true;
11598                 invalidate();
11599             }
11600 
11601             // Check if our height changed
11602             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
11603                 int desiredHeight = getDesiredHeight();
11604 
11605                 if (desiredHeight != this.getHeight()) {
11606                     sizeChanged = true;
11607                 }
11608             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
11609                 if (mDesiredHeightAtMeasure >= 0) {
11610                     int desiredHeight = getDesiredHeight();
11611 
11612                     if (desiredHeight != mDesiredHeightAtMeasure) {
11613                         sizeChanged = true;
11614                     }
11615                 }
11616             }
11617         }
11618 
11619         if (sizeChanged) {
11620             requestLayout();
11621             // caller will have already invalidated
11622         }
11623     }
11624 
11625     /**
11626      * Check whether entirely new text requires a new view layout
11627      * or merely a new text layout.
11628      */
11629     @UnsupportedAppUsage
11630     private void checkForRelayout() {
11631         // If we have a fixed width, we can just swap in a new text layout
11632         // if the text height stays the same or if the view height is fixed.
11633 
11634         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
11635                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
11636                 && (mHint == null || mHintLayout != null)
11637                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
11638             // Static width, so try making a new text layout.
11639 
11640             int oldht = mLayout.getHeight();
11641             int want = mLayout.getWidth();
11642             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
11643 
11644             /*
11645              * No need to bring the text into view, since the size is not
11646              * changing (unless we do the requestLayout(), in which case it
11647              * will happen at measure).
11648              */
11649             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
11650                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
11651                           false);
11652 
11653             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
11654                 // In a fixed-height view, so use our new text layout.
11655                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
11656                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
11657                     autoSizeText();
11658                     invalidate();
11659                     return;
11660                 }
11661 
11662                 // Dynamic height, but height has stayed the same,
11663                 // so use our new text layout.
11664                 if (mLayout.getHeight() == oldht
11665                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
11666                     autoSizeText();
11667                     invalidate();
11668                     return;
11669                 }
11670             }
11671 
11672             // We lose: the height has changed and we have a dynamic height.
11673             // Request a new view layout using our new text layout.
11674             requestLayout();
11675             invalidate();
11676         } else {
11677             // Dynamic width, so we have no choice but to request a new
11678             // view layout with a new text layout.
11679             nullLayouts();
11680             requestLayout();
11681             invalidate();
11682         }
11683     }
11684 
11685     @Override
11686     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
11687         super.onLayout(changed, left, top, right, bottom);
11688         if (mDeferScroll >= 0) {
11689             int curs = mDeferScroll;
11690             mDeferScroll = -1;
11691             bringPointIntoView(Math.min(curs, mText.length()));
11692         }
11693         // Call auto-size after the width and height have been calculated.
11694         autoSizeText();
11695     }
11696 
11697     private boolean isShowingHint() {
11698         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint) && !mHideHint;
11699     }
11700 
11701     /**
11702      * Returns true if anything changed.
11703      */
11704     @UnsupportedAppUsage
11705     private boolean bringTextIntoView() {
11706         Layout layout = isShowingHint() ? mHintLayout : mLayout;
11707         int line = 0;
11708         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
11709             line = layout.getLineCount() - 1;
11710         }
11711 
11712         Layout.Alignment a = layout.getParagraphAlignment(line);
11713         int dir = layout.getParagraphDirection(line);
11714         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
11715         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
11716         int ht = layout.getHeight();
11717 
11718         int scrollx, scrolly;
11719 
11720         // Convert to left, center, or right alignment.
11721         if (a == Layout.Alignment.ALIGN_NORMAL) {
11722             a = dir == Layout.DIR_LEFT_TO_RIGHT
11723                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
11724         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
11725             a = dir == Layout.DIR_LEFT_TO_RIGHT
11726                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
11727         }
11728 
11729         if (a == Layout.Alignment.ALIGN_CENTER) {
11730             /*
11731              * Keep centered if possible, or, if it is too wide to fit,
11732              * keep leading edge in view.
11733              */
11734 
11735             int left = (int) Math.floor(layout.getLineLeft(line));
11736             int right = (int) Math.ceil(layout.getLineRight(line));
11737 
11738             if (right - left < hspace) {
11739                 scrollx = (right + left) / 2 - hspace / 2;
11740             } else {
11741                 if (dir < 0) {
11742                     scrollx = right - hspace;
11743                 } else {
11744                     scrollx = left;
11745                 }
11746             }
11747         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
11748             int right = (int) Math.ceil(layout.getLineRight(line));
11749             scrollx = right - hspace;
11750         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
11751             scrollx = (int) Math.floor(layout.getLineLeft(line));
11752         }
11753 
11754         if (ht < vspace) {
11755             scrolly = 0;
11756         } else {
11757             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
11758                 scrolly = ht - vspace;
11759             } else {
11760                 scrolly = 0;
11761             }
11762         }
11763 
11764         if (scrollx != mScrollX || scrolly != mScrollY) {
11765             scrollTo(scrollx, scrolly);
11766             return true;
11767         } else {
11768             return false;
11769         }
11770     }
11771 
11772     /**
11773      * Move the point, specified by the offset, into the view if it is needed.
11774      * This has to be called after layout. Returns true if anything changed.
11775      */
11776     public boolean bringPointIntoView(int offset) {
11777         return bringPointIntoView(offset, false);
11778     }
11779 
11780     /**
11781      * Move the insertion position of the given offset into visible area of the View.
11782      *
11783      * If the View is focused or {@code requestRectWithoutFocus} is set to true, this API may call
11784      * {@link View#requestRectangleOnScreen(Rect)} to bring the point to the visible area if
11785      * necessary.
11786      *
11787      * @param offset an offset of the character.
11788      * @param requestRectWithoutFocus True for calling {@link View#requestRectangleOnScreen(Rect)}
11789      *                                in the unfocused state. False for calling it only the View has
11790      *                                the focus.
11791      * @return true if anything changed, otherwise false.
11792      *
11793      * @see #bringPointIntoView(int)
11794      */
11795     public boolean bringPointIntoView(@IntRange(from = 0) int offset,
11796             boolean requestRectWithoutFocus) {
11797         if (isLayoutRequested()) {
11798             mDeferScroll = offset;
11799             return false;
11800         }
11801         final int offsetTransformed =
11802                 originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
11803         boolean changed = false;
11804 
11805         Layout layout = isShowingHint() ? mHintLayout : mLayout;
11806 
11807         if (layout == null) return changed;
11808 
11809         int line = layout.getLineForOffset(offsetTransformed);
11810 
11811         int grav;
11812 
11813         switch (layout.getParagraphAlignment(line)) {
11814             case ALIGN_LEFT:
11815                 grav = 1;
11816                 break;
11817             case ALIGN_RIGHT:
11818                 grav = -1;
11819                 break;
11820             case ALIGN_NORMAL:
11821                 grav = layout.getParagraphDirection(line);
11822                 break;
11823             case ALIGN_OPPOSITE:
11824                 grav = -layout.getParagraphDirection(line);
11825                 break;
11826             case ALIGN_CENTER:
11827             default:
11828                 grav = 0;
11829                 break;
11830         }
11831 
11832         // We only want to clamp the cursor to fit within the layout width
11833         // in left-to-right modes, because in a right to left alignment,
11834         // we want to scroll to keep the line-right on the screen, as other
11835         // lines are likely to have text flush with the right margin, which
11836         // we want to keep visible.
11837         // A better long-term solution would probably be to measure both
11838         // the full line and a blank-trimmed version, and, for example, use
11839         // the latter measurement for centering and right alignment, but for
11840         // the time being we only implement the cursor clamping in left to
11841         // right where it is most likely to be annoying.
11842         final boolean clamped = grav > 0;
11843         // FIXME: Is it okay to truncate this, or should we round?
11844         final int x = (int) layout.getPrimaryHorizontal(offsetTransformed, clamped);
11845         final int top = layout.getLineTop(line);
11846         final int bottom = layout.getLineTop(line + 1);
11847 
11848         int left = (int) Math.floor(layout.getLineLeft(line));
11849         int right = (int) Math.ceil(layout.getLineRight(line));
11850         int ht = layout.getHeight();
11851 
11852         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
11853         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
11854         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
11855             // If cursor has been clamped, make sure we don't scroll.
11856             right = Math.max(x, left + hspace);
11857         }
11858 
11859         int hslack = (bottom - top) / 2;
11860         int vslack = hslack;
11861 
11862         if (vslack > vspace / 4) {
11863             vslack = vspace / 4;
11864         }
11865         if (hslack > hspace / 4) {
11866             hslack = hspace / 4;
11867         }
11868 
11869         int hs = mScrollX;
11870         int vs = mScrollY;
11871 
11872         if (top - vs < vslack) {
11873             vs = top - vslack;
11874         }
11875         if (bottom - vs > vspace - vslack) {
11876             vs = bottom - (vspace - vslack);
11877         }
11878         if (ht - vs < vspace) {
11879             vs = ht - vspace;
11880         }
11881         if (0 - vs > 0) {
11882             vs = 0;
11883         }
11884 
11885         if (grav != 0) {
11886             if (x - hs < hslack) {
11887                 hs = x - hslack;
11888             }
11889             if (x - hs > hspace - hslack) {
11890                 hs = x - (hspace - hslack);
11891             }
11892         }
11893 
11894         if (grav < 0) {
11895             if (left - hs > 0) {
11896                 hs = left;
11897             }
11898             if (right - hs < hspace) {
11899                 hs = right - hspace;
11900             }
11901         } else if (grav > 0) {
11902             if (right - hs < hspace) {
11903                 hs = right - hspace;
11904             }
11905             if (left - hs > 0) {
11906                 hs = left;
11907             }
11908         } else /* grav == 0 */ {
11909             if (right - left <= hspace) {
11910                 /*
11911                  * If the entire text fits, center it exactly.
11912                  */
11913                 hs = left - (hspace - (right - left)) / 2;
11914             } else if (x > right - hslack) {
11915                 /*
11916                  * If we are near the right edge, keep the right edge
11917                  * at the edge of the view.
11918                  */
11919                 hs = right - hspace;
11920             } else if (x < left + hslack) {
11921                 /*
11922                  * If we are near the left edge, keep the left edge
11923                  * at the edge of the view.
11924                  */
11925                 hs = left;
11926             } else if (left > hs) {
11927                 /*
11928                  * Is there whitespace visible at the left?  Fix it if so.
11929                  */
11930                 hs = left;
11931             } else if (right < hs + hspace) {
11932                 /*
11933                  * Is there whitespace visible at the right?  Fix it if so.
11934                  */
11935                 hs = right - hspace;
11936             } else {
11937                 /*
11938                  * Otherwise, float as needed.
11939                  */
11940                 if (x - hs < hslack) {
11941                     hs = x - hslack;
11942                 }
11943                 if (x - hs > hspace - hslack) {
11944                     hs = x - (hspace - hslack);
11945                 }
11946             }
11947         }
11948 
11949         if (hs != mScrollX || vs != mScrollY) {
11950             if (mScroller == null) {
11951                 scrollTo(hs, vs);
11952             } else {
11953                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
11954                 int dx = hs - mScrollX;
11955                 int dy = vs - mScrollY;
11956 
11957                 if (duration > ANIMATED_SCROLL_GAP) {
11958                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
11959                     awakenScrollBars(mScroller.getDuration());
11960                     invalidate();
11961                 } else {
11962                     if (!mScroller.isFinished()) {
11963                         mScroller.abortAnimation();
11964                     }
11965 
11966                     scrollBy(dx, dy);
11967                 }
11968 
11969                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
11970             }
11971 
11972             changed = true;
11973         }
11974 
11975         if (requestRectWithoutFocus || isFocused()) {
11976             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
11977             // requestRectangleOnScreen() is in terms of content coordinates.
11978 
11979             // The offsets here are to ensure the rectangle we are using is
11980             // within our view bounds, in case the cursor is on the far left
11981             // or right.  If it isn't withing the bounds, then this request
11982             // will be ignored.
11983             if (mTempRect == null) mTempRect = new Rect();
11984             mTempRect.set(x - 2, top, x + 2, bottom);
11985             getInterestingRect(mTempRect, line);
11986             mTempRect.offset(mScrollX, mScrollY);
11987 
11988             if (requestRectangleOnScreen(mTempRect)) {
11989                 changed = true;
11990             }
11991         }
11992 
11993         return changed;
11994     }
11995 
11996     /**
11997      * Move the cursor, if needed, so that it is at an offset that is visible
11998      * to the user.  This will not move the cursor if it represents more than
11999      * one character (a selection range).  This will only work if the
12000      * TextView contains spannable text; otherwise it will do nothing.
12001      *
12002      * @return True if the cursor was actually moved, false otherwise.
12003      */
12004     public boolean moveCursorToVisibleOffset() {
12005         if (!(mText instanceof Spannable)) {
12006             return false;
12007         }
12008         int start = getSelectionStartTransformed();
12009         int end = getSelectionEndTransformed();
12010         if (start != end) {
12011             return false;
12012         }
12013 
12014         // First: make sure the line is visible on screen:
12015 
12016         int line = mLayout.getLineForOffset(start);
12017 
12018         final int top = mLayout.getLineTop(line);
12019         final int bottom = mLayout.getLineTop(line + 1);
12020         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
12021         int vslack = (bottom - top) / 2;
12022         if (vslack > vspace / 4) {
12023             vslack = vspace / 4;
12024         }
12025         final int vs = mScrollY;
12026 
12027         if (top < (vs + vslack)) {
12028             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
12029         } else if (bottom > (vspace + vs - vslack)) {
12030             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
12031         }
12032 
12033         // Next: make sure the character is visible on screen:
12034 
12035         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
12036         final int hs = mScrollX;
12037         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
12038         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
12039 
12040         // line might contain bidirectional text
12041         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
12042         final int highChar = leftChar > rightChar ? leftChar : rightChar;
12043 
12044         int newStart = start;
12045         if (newStart < lowChar) {
12046             newStart = lowChar;
12047         } else if (newStart > highChar) {
12048             newStart = highChar;
12049         }
12050 
12051         if (newStart != start) {
12052             Selection.setSelection(mSpannable,
12053                     transformedToOriginal(newStart, OffsetMapping.MAP_STRATEGY_CURSOR));
12054             return true;
12055         }
12056 
12057         return false;
12058     }
12059 
12060     @Override
12061     public void computeScroll() {
12062         if (mScroller != null) {
12063             if (mScroller.computeScrollOffset()) {
12064                 mScrollX = mScroller.getCurrX();
12065                 mScrollY = mScroller.getCurrY();
12066                 invalidateParentCaches();
12067                 postInvalidate();  // So we draw again
12068             }
12069         }
12070     }
12071 
12072     private void getInterestingRect(Rect r, int line) {
12073         convertFromViewportToContentCoordinates(r);
12074 
12075         // Rectangle can can be expanded on first and last line to take
12076         // padding into account.
12077         // TODO Take left/right padding into account too?
12078         if (line == 0) r.top -= getExtendedPaddingTop();
12079         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
12080     }
12081 
12082     private void convertFromViewportToContentCoordinates(Rect r) {
12083         final int horizontalOffset = viewportToContentHorizontalOffset();
12084         r.left += horizontalOffset;
12085         r.right += horizontalOffset;
12086 
12087         final int verticalOffset = viewportToContentVerticalOffset();
12088         r.top += verticalOffset;
12089         r.bottom += verticalOffset;
12090     }
12091 
12092     private PointF convertFromScreenToContentCoordinates(PointF point) {
12093         int[] screenToViewport = getLocationOnScreen();
12094         PointF copy = new PointF(point);
12095         copy.offset(
12096                 -(screenToViewport[0] + viewportToContentHorizontalOffset()),
12097                 -(screenToViewport[1] + viewportToContentVerticalOffset()));
12098         return copy;
12099     }
12100 
12101     private RectF convertFromScreenToContentCoordinates(RectF rect) {
12102         int[] screenToViewport = getLocationOnScreen();
12103         RectF copy = new RectF(rect);
12104         copy.offset(
12105                 -(screenToViewport[0] + viewportToContentHorizontalOffset()),
12106                 -(screenToViewport[1] + viewportToContentVerticalOffset()));
12107         return copy;
12108     }
12109 
12110     int viewportToContentHorizontalOffset() {
12111         return getCompoundPaddingLeft() - mScrollX;
12112     }
12113 
12114     @UnsupportedAppUsage
12115     int viewportToContentVerticalOffset() {
12116         int offset = getExtendedPaddingTop() - mScrollY;
12117         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
12118             offset += getVerticalOffset(false);
12119         }
12120         return offset;
12121     }
12122 
12123     @Override
12124     public void debug(int depth) {
12125         super.debug(depth);
12126 
12127         String output = debugIndent(depth);
12128         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
12129                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
12130                 + "} ";
12131 
12132         if (mText != null) {
12133 
12134             output += "mText=\"" + mText + "\" ";
12135             if (mLayout != null) {
12136                 output += "mLayout width=" + mLayout.getWidth()
12137                         + " height=" + mLayout.getHeight();
12138             }
12139         } else {
12140             output += "mText=NULL";
12141         }
12142         Log.d(VIEW_LOG_TAG, output);
12143     }
12144 
12145     /**
12146      * Convenience for {@link Selection#getSelectionStart}.
12147      */
12148     @ViewDebug.ExportedProperty(category = "text")
12149     public int getSelectionStart() {
12150         return Selection.getSelectionStart(getText());
12151     }
12152 
12153     /**
12154      * Convenience for {@link Selection#getSelectionEnd}.
12155      */
12156     @ViewDebug.ExportedProperty(category = "text")
12157     public int getSelectionEnd() {
12158         return Selection.getSelectionEnd(getText());
12159     }
12160 
12161     /**
12162      * Calculates the rectangles which should be highlighted to indicate a selection between start
12163      * and end and feeds them into the given {@link Layout.SelectionRectangleConsumer}.
12164      *
12165      * @param start    the starting index of the selection
12166      * @param end      the ending index of the selection
12167      * @param consumer the {@link Layout.SelectionRectangleConsumer} which will receive the
12168      *                 generated rectangles. It will be called every time a rectangle is generated.
12169      * @hide
12170      */
12171     public void getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer) {
12172         final int transformedStart =
12173                 originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
12174         final int transformedEnd = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
12175         mLayout.getSelection(transformedStart, transformedEnd, consumer);
12176     }
12177 
12178     int getSelectionStartTransformed() {
12179         final int start = getSelectionStart();
12180         if (start < 0) return start;
12181         return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
12182     }
12183 
12184     int getSelectionEndTransformed() {
12185         final int end = getSelectionEnd();
12186         if (end < 0) return end;
12187         return originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
12188     }
12189 
12190     /**
12191      * Return true iff there is a selection of nonzero length inside this text view.
12192      */
12193     public boolean hasSelection() {
12194         final int selectionStart = getSelectionStart();
12195         final int selectionEnd = getSelectionEnd();
12196         final int selectionMin;
12197         final int selectionMax;
12198         if (selectionStart < selectionEnd) {
12199             selectionMin = selectionStart;
12200             selectionMax = selectionEnd;
12201         } else {
12202             selectionMin = selectionEnd;
12203             selectionMax = selectionStart;
12204         }
12205 
12206         return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax;
12207     }
12208 
12209     String getSelectedText() {
12210         if (!hasSelection()) {
12211             return null;
12212         }
12213 
12214         final int start = getSelectionStart();
12215         final int end = getSelectionEnd();
12216         return String.valueOf(
12217                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
12218     }
12219 
12220     /**
12221      * Sets the properties of this field (lines, horizontally scrolling,
12222      * transformation method) to be for a single-line input.
12223      *
12224      * @attr ref android.R.styleable#TextView_singleLine
12225      */
12226     public void setSingleLine() {
12227         setSingleLine(true);
12228     }
12229 
12230     /**
12231      * Sets the properties of this field to transform input to ALL CAPS
12232      * display. This may use a "small caps" formatting if available.
12233      * This setting will be ignored if this field is editable or selectable.
12234      *
12235      * This call replaces the current transformation method. Disabling this
12236      * will not necessarily restore the previous behavior from before this
12237      * was enabled.
12238      *
12239      * @see #setTransformationMethod(TransformationMethod)
12240      * @attr ref android.R.styleable#TextView_textAllCaps
12241      */
12242     @android.view.RemotableViewMethod
12243     public void setAllCaps(boolean allCaps) {
12244         if (allCaps) {
12245             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
12246         } else {
12247             setTransformationMethod(null);
12248         }
12249     }
12250 
12251     /**
12252      *
12253      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
12254      * @return Whether the current transformation method is for ALL CAPS.
12255      *
12256      * @see #setAllCaps(boolean)
12257      * @see #setTransformationMethod(TransformationMethod)
12258      */
12259     @InspectableProperty(name = "textAllCaps")
12260     public boolean isAllCaps() {
12261         final TransformationMethod method = getTransformationMethod();
12262         return method != null && method instanceof AllCapsTransformationMethod;
12263     }
12264 
12265     /**
12266      * If true, sets the properties of this field (number of lines, horizontally scrolling,
12267      * transformation method) to be for a single-line input; if false, restores these to the default
12268      * conditions.
12269      *
12270      * Note that the default conditions are not necessarily those that were in effect prior this
12271      * method, and you may want to reset these properties to your custom values.
12272      *
12273      * Note that due to performance reasons, by setting single line for the EditText, the maximum
12274      * text length is set to 5000 if no other character limitation are applied.
12275      *
12276      * @attr ref android.R.styleable#TextView_singleLine
12277      */
12278     @android.view.RemotableViewMethod
12279     public void setSingleLine(boolean singleLine) {
12280         // Could be used, but may break backward compatibility.
12281         // if (mSingleLine == singleLine) return;
12282         setInputTypeSingleLine(singleLine);
12283         applySingleLine(singleLine, true, true, true);
12284     }
12285 
12286     /**
12287      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
12288      * @param singleLine
12289      */
12290     private void setInputTypeSingleLine(boolean singleLine) {
12291         if (mEditor != null
12292                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
12293                         == EditorInfo.TYPE_CLASS_TEXT) {
12294             if (singleLine) {
12295                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
12296             } else {
12297                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
12298             }
12299         }
12300     }
12301 
12302     private void applySingleLine(boolean singleLine, boolean applyTransformation,
12303             boolean changeMaxLines, boolean changeMaxLength) {
12304         mSingleLine = singleLine;
12305 
12306         if (singleLine) {
12307             setLines(1);
12308             setHorizontallyScrolling(true);
12309             if (applyTransformation) {
12310                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
12311             }
12312 
12313             if (!changeMaxLength) return;
12314 
12315             // Single line length filter is only applicable editable text.
12316             if (mBufferType != BufferType.EDITABLE) return;
12317 
12318             final InputFilter[] prevFilters = getFilters();
12319             for (InputFilter filter: getFilters()) {
12320                 // We don't add LengthFilter if already there.
12321                 if (filter instanceof InputFilter.LengthFilter) return;
12322             }
12323 
12324             if (mSingleLineLengthFilter == null) {
12325                 mSingleLineLengthFilter = new InputFilter.LengthFilter(
12326                     MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
12327             }
12328 
12329             final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1];
12330             System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length);
12331             newFilters[prevFilters.length] = mSingleLineLengthFilter;
12332 
12333             setFilters(newFilters);
12334 
12335             // Since filter doesn't apply to existing text, trigger filter by setting text.
12336             setText(getText());
12337         } else {
12338             if (changeMaxLines) {
12339                 setMaxLines(Integer.MAX_VALUE);
12340             }
12341             setHorizontallyScrolling(false);
12342             if (applyTransformation) {
12343                 setTransformationMethod(null);
12344             }
12345 
12346             if (!changeMaxLength) return;
12347 
12348             // Single line length filter is only applicable editable text.
12349             if (mBufferType != BufferType.EDITABLE) return;
12350 
12351             final InputFilter[] prevFilters = getFilters();
12352             if (prevFilters.length == 0) return;
12353 
12354             // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated
12355             // single line char limit filter.
12356             if (mSingleLineLengthFilter == null) return;
12357 
12358             // If we need to remove mSingleLineLengthFilter, we need to allocate another array.
12359             // Since filter list is expected to be small and want to avoid unnecessary array
12360             // allocation, check if there is mSingleLengthFilter first.
12361             int targetIndex = -1;
12362             for (int i = 0; i < prevFilters.length; ++i) {
12363                 if (prevFilters[i] == mSingleLineLengthFilter) {
12364                     targetIndex = i;
12365                     break;
12366                 }
12367             }
12368             if (targetIndex == -1) return;  // not found. Do nothing.
12369 
12370             if (prevFilters.length == 1) {
12371                 setFilters(NO_FILTERS);
12372                 return;
12373             }
12374 
12375             // Create new array which doesn't include mSingleLengthFilter.
12376             final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1];
12377             System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex);
12378             System.arraycopy(
12379                     prevFilters,
12380                     targetIndex + 1,
12381                     newFilters,
12382                     targetIndex,
12383                     prevFilters.length - targetIndex - 1);
12384             setFilters(newFilters);
12385             mSingleLineLengthFilter = null;
12386         }
12387     }
12388 
12389     /**
12390      * Causes words in the text that are longer than the view's width
12391      * to be ellipsized instead of broken in the middle.  You may also
12392      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
12393      * to constrain the text to a single line.  Use <code>null</code>
12394      * to turn off ellipsizing.
12395      *
12396      * If {@link #setMaxLines} has been used to set two or more lines,
12397      * only {@link android.text.TextUtils.TruncateAt#END} and
12398      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
12399      * (other ellipsizing types will not do anything).
12400      *
12401      * @attr ref android.R.styleable#TextView_ellipsize
12402      */
12403     public void setEllipsize(TextUtils.TruncateAt where) {
12404         // TruncateAt is an enum. != comparison is ok between these singleton objects.
12405         if (mEllipsize != where) {
12406             mEllipsize = where;
12407 
12408             if (mLayout != null) {
12409                 nullLayouts();
12410                 requestLayout();
12411                 invalidate();
12412             }
12413         }
12414     }
12415 
12416     /**
12417      * Sets how many times to repeat the marquee animation. Only applied if the
12418      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
12419      *
12420      * @see #getMarqueeRepeatLimit()
12421      *
12422      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
12423      */
12424     public void setMarqueeRepeatLimit(int marqueeLimit) {
12425         mMarqueeRepeatLimit = marqueeLimit;
12426     }
12427 
12428     /**
12429      * Gets the number of times the marquee animation is repeated. Only meaningful if the
12430      * TextView has marquee enabled.
12431      *
12432      * @return the number of times the marquee animation is repeated. -1 if the animation
12433      * repeats indefinitely
12434      *
12435      * @see #setMarqueeRepeatLimit(int)
12436      *
12437      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
12438      */
12439     @InspectableProperty
12440     public int getMarqueeRepeatLimit() {
12441         return mMarqueeRepeatLimit;
12442     }
12443 
12444     /**
12445      * Returns where, if anywhere, words that are longer than the view
12446      * is wide should be ellipsized.
12447      */
12448     @InspectableProperty
12449     @ViewDebug.ExportedProperty
12450     public TextUtils.TruncateAt getEllipsize() {
12451         return mEllipsize;
12452     }
12453 
12454     /**
12455      * Set the TextView so that when it takes focus, all the text is
12456      * selected.
12457      *
12458      * @attr ref android.R.styleable#TextView_selectAllOnFocus
12459      */
12460     @android.view.RemotableViewMethod
12461     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
12462         createEditorIfNeeded();
12463         mEditor.mSelectAllOnFocus = selectAllOnFocus;
12464 
12465         if (selectAllOnFocus && !(mText instanceof Spannable)) {
12466             setText(mText, BufferType.SPANNABLE);
12467         }
12468     }
12469 
12470     /**
12471      * Set whether the cursor is visible. The default is true. Note that this property only
12472      * makes sense for editable TextView. If IME is consuming the input, the cursor will always be
12473      * invisible, visibility will be updated as the last state when IME does not consume
12474      * the input anymore.
12475      *
12476      * @see #isCursorVisible()
12477      *
12478      * @attr ref android.R.styleable#TextView_cursorVisible
12479      */
12480     @android.view.RemotableViewMethod
12481     public void setCursorVisible(boolean visible) {
12482         mCursorVisibleFromAttr = visible;
12483         updateCursorVisibleInternal();
12484     }
12485 
12486     /**
12487      * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput}
12488      * is {@code true}. Otherwise, make the cursor visible.
12489      *
12490      * @param imeConsumesInput {@code true} if IME is consuming the input
12491      *
12492      * @hide
12493      */
12494     public void setImeConsumesInput(boolean imeConsumesInput) {
12495         mImeIsConsumingInput = imeConsumesInput;
12496         updateCursorVisibleInternal();
12497     }
12498 
12499     private void updateCursorVisibleInternal()  {
12500         boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput;
12501         if (visible && mEditor == null) return; // visible is the default value with no edit data
12502         createEditorIfNeeded();
12503         if (mEditor.mCursorVisible != visible) {
12504             mEditor.mCursorVisible = visible;
12505             invalidate();
12506 
12507             mEditor.makeBlink();
12508 
12509             // InsertionPointCursorController depends on mCursorVisible
12510             mEditor.prepareCursorControllers();
12511         }
12512     }
12513 
12514     /**
12515      * @return whether or not the cursor is visible (assuming this TextView is editable). This
12516      * method may return {@code false} when the IME is consuming the input even if the
12517      * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)}
12518      * is called.
12519      *
12520      * @see #setCursorVisible(boolean)
12521      *
12522      * @attr ref android.R.styleable#TextView_cursorVisible
12523      */
12524     @InspectableProperty
12525     public boolean isCursorVisible() {
12526         // true is the default value
12527         return mEditor == null ? true : mEditor.mCursorVisible;
12528     }
12529 
12530     /**
12531      * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}.
12532      * {@code true} is the default value.
12533      *
12534      * @see #setCursorVisible(boolean)
12535      * @hide
12536      */
12537     public boolean isCursorVisibleFromAttr() {
12538         return mCursorVisibleFromAttr;
12539     }
12540 
12541     private boolean canMarquee() {
12542         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
12543         return width > 0 && (mLayout.getLineWidth(0) > width
12544                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
12545                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
12546     }
12547 
12548     /**
12549      * @hide
12550      */
12551     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
12552     protected void startMarquee() {
12553         // Do not ellipsize EditText
12554         if (getKeyListener() != null) return;
12555 
12556         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
12557             return;
12558         }
12559 
12560         if ((mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible()
12561                 && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) {
12562 
12563             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
12564                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
12565                 final Layout tmp = mLayout;
12566                 mLayout = mSavedMarqueeModeLayout;
12567                 mSavedMarqueeModeLayout = tmp;
12568                 setHorizontalFadingEdgeEnabled(true);
12569                 requestLayout();
12570                 invalidate();
12571             }
12572 
12573             if (mMarquee == null) mMarquee = new Marquee(this);
12574             mMarquee.start(mMarqueeRepeatLimit);
12575         }
12576     }
12577 
12578     /**
12579      * @hide
12580      */
12581     protected void stopMarquee() {
12582         if (mMarquee != null && !mMarquee.isStopped()) {
12583             mMarquee.stop();
12584         }
12585 
12586         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
12587             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
12588             final Layout tmp = mSavedMarqueeModeLayout;
12589             mSavedMarqueeModeLayout = mLayout;
12590             mLayout = tmp;
12591             setHorizontalFadingEdgeEnabled(false);
12592             requestLayout();
12593             invalidate();
12594         }
12595     }
12596 
12597     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
12598     private void startStopMarquee(boolean start) {
12599         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
12600             if (start) {
12601                 startMarquee();
12602             } else {
12603                 stopMarquee();
12604             }
12605         }
12606     }
12607 
12608     /**
12609      * This method is called when the text is changed, in case any subclasses
12610      * would like to know.
12611      *
12612      * Within <code>text</code>, the <code>lengthAfter</code> characters
12613      * beginning at <code>start</code> have just replaced old text that had
12614      * length <code>lengthBefore</code>. It is an error to attempt to make
12615      * changes to <code>text</code> from this callback.
12616      *
12617      * @param text The text the TextView is displaying
12618      * @param start The offset of the start of the range of the text that was
12619      * modified
12620      * @param lengthBefore The length of the former text that has been replaced
12621      * @param lengthAfter The length of the replacement modified text
12622      */
12623     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
12624         // intentionally empty, template pattern method can be overridden by subclasses
12625     }
12626 
12627     /**
12628      * This method is called when the selection has changed, in case any
12629      * subclasses would like to know.
12630      * </p>
12631      * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs
12632      * the accessibility subsystem about the selection change.
12633      * </p>
12634      *
12635      * @param selStart The new selection start location.
12636      * @param selEnd The new selection end location.
12637      */
12638     @CallSuper
12639     protected void onSelectionChanged(int selStart, int selEnd) {
12640         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
12641     }
12642 
12643     /**
12644      * Adds a TextWatcher to the list of those whose methods are called
12645      * whenever this TextView's text changes.
12646      * <p>
12647      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
12648      * not called after {@link #setText} calls.  Now, doing {@link #setText}
12649      * if there are any text changed listeners forces the buffer type to
12650      * Editable if it would not otherwise be and does call this method.
12651      */
12652     public void addTextChangedListener(TextWatcher watcher) {
12653         if (mListeners == null) {
12654             mListeners = new ArrayList<TextWatcher>();
12655         }
12656 
12657         mListeners.add(watcher);
12658     }
12659 
12660     /**
12661      * Removes the specified TextWatcher from the list of those whose
12662      * methods are called
12663      * whenever this TextView's text changes.
12664      */
12665     public void removeTextChangedListener(TextWatcher watcher) {
12666         if (mListeners != null) {
12667             int i = mListeners.indexOf(watcher);
12668 
12669             if (i >= 0) {
12670                 mListeners.remove(i);
12671             }
12672         }
12673     }
12674 
12675     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
12676         if (mListeners != null) {
12677             final ArrayList<TextWatcher> list = mListeners;
12678             final int count = list.size();
12679             for (int i = 0; i < count; i++) {
12680                 list.get(i).beforeTextChanged(text, start, before, after);
12681             }
12682         }
12683 
12684         // The spans that are inside or intersect the modified region no longer make sense
12685         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
12686         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
12687     }
12688 
12689     // Removes all spans that are inside or actually overlap the start..end range
12690     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
12691         if (!(mText instanceof Editable)) return;
12692         Editable text = (Editable) mText;
12693 
12694         T[] spans = text.getSpans(start, end, type);
12695         ArrayList<T> spansToRemove = new ArrayList<>();
12696         for (T span : spans) {
12697             final int spanStart = text.getSpanStart(span);
12698             final int spanEnd = text.getSpanEnd(span);
12699             if (spanEnd == start || spanStart == end) continue;
12700             spansToRemove.add(span);
12701         }
12702         for (T span : spansToRemove) {
12703             text.removeSpan(span);
12704         }
12705     }
12706 
12707     void removeAdjacentSuggestionSpans(final int pos) {
12708         if (!(mText instanceof Editable)) return;
12709         final Editable text = (Editable) mText;
12710 
12711         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
12712         final int length = spans.length;
12713         for (int i = 0; i < length; i++) {
12714             final int spanStart = text.getSpanStart(spans[i]);
12715             final int spanEnd = text.getSpanEnd(spans[i]);
12716             if (spanEnd == pos || spanStart == pos) {
12717                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
12718                     text.removeSpan(spans[i]);
12719                 }
12720             }
12721         }
12722     }
12723 
12724     /**
12725      * Not private so it can be called from an inner class without going
12726      * through a thunk.
12727      */
12728     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
12729         if (mListeners != null) {
12730             final ArrayList<TextWatcher> list = mListeners;
12731             final int count = list.size();
12732             for (int i = 0; i < count; i++) {
12733                 list.get(i).onTextChanged(text, start, before, after);
12734             }
12735         }
12736 
12737         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
12738     }
12739 
12740     /**
12741      * Not private so it can be called from an inner class without going
12742      * through a thunk.
12743      */
12744     void sendAfterTextChanged(Editable text) {
12745         if (mListeners != null) {
12746             final ArrayList<TextWatcher> list = mListeners;
12747             final int count = list.size();
12748             for (int i = 0; i < count; i++) {
12749                 list.get(i).afterTextChanged(text);
12750             }
12751         }
12752 
12753         notifyListeningManagersAfterTextChanged();
12754 
12755         hideErrorIfUnchanged();
12756     }
12757 
12758     /**
12759      * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
12760      * interested on text changes.
12761      */
12762     private void notifyListeningManagersAfterTextChanged() {
12763 
12764         // Autofill
12765         if (isAutofillable()) {
12766             // It is important to not check whether the view is important for autofill
12767             // since the user can trigger autofill manually on not important views.
12768             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
12769             if (afm != null) {
12770                 if (android.view.autofill.Helper.sVerbose) {
12771                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
12772                 }
12773                 afm.notifyValueChanged(TextView.this);
12774             }
12775         }
12776 
12777         notifyContentCaptureTextChanged();
12778     }
12779 
12780     /**
12781      * Notifies the ContentCapture service that the text of the view has changed (only if
12782      * ContentCapture has been notified of this view's existence already).
12783      *
12784      * @hide
12785      */
12786     public void notifyContentCaptureTextChanged() {
12787         // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
12788         // of using isLaidout(), so it's not called in cases where it's laid out but a
12789         // notifyAppeared was not sent.
12790         if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) {
12791             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
12792             if (cm != null && cm.isContentCaptureEnabled()) {
12793                 final ContentCaptureSession session = getContentCaptureSession();
12794                 if (session != null) {
12795                     // TODO(b/111276913): pass flags when edited by user / add CTS test
12796                     session.notifyViewTextChanged(getAutofillId(), getText());
12797                 }
12798             }
12799         }
12800     }
12801 
12802     private boolean isAutofillable() {
12803         // It is important to not check whether the view is important for autofill
12804         // since the user can trigger autofill manually on not important views.
12805         return getAutofillType() != AUTOFILL_TYPE_NONE;
12806     }
12807 
12808     void updateAfterEdit() {
12809         invalidate();
12810         int curs = getSelectionStart();
12811 
12812         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
12813             registerForPreDraw();
12814         }
12815 
12816         checkForResize();
12817 
12818         if (curs >= 0) {
12819             mHighlightPathBogus = true;
12820             if (mEditor != null) mEditor.makeBlink();
12821             bringPointIntoView(curs);
12822         }
12823     }
12824 
12825     /**
12826      * Not private so it can be called from an inner class without going
12827      * through a thunk.
12828      */
12829     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
12830         sLastCutCopyOrTextChangedTime = 0;
12831 
12832         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
12833         if (ims == null || ims.mBatchEditNesting == 0) {
12834             updateAfterEdit();
12835         }
12836         if (ims != null) {
12837             ims.mContentChanged = true;
12838             if (ims.mChangedStart < 0) {
12839                 ims.mChangedStart = start;
12840                 ims.mChangedEnd = start + before;
12841             } else {
12842                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
12843                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
12844             }
12845             ims.mChangedDelta += after - before;
12846         }
12847         resetErrorChangedFlag();
12848         sendOnTextChanged(buffer, start, before, after);
12849         onTextChanged(buffer, start, before, after);
12850 
12851         mHideHint = false;
12852         clearGesturePreviewHighlight();
12853     }
12854 
12855     /**
12856      * Not private so it can be called from an inner class without going
12857      * through a thunk.
12858      */
12859     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
12860         // XXX Make the start and end move together if this ends up
12861         // spending too much time invalidating.
12862 
12863         boolean selChanged = false;
12864         int newSelStart = -1, newSelEnd = -1;
12865 
12866         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
12867 
12868         if (what == Selection.SELECTION_END) {
12869             selChanged = true;
12870             newSelEnd = newStart;
12871 
12872             if (oldStart >= 0 || newStart >= 0) {
12873                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
12874                 checkForResize();
12875                 registerForPreDraw();
12876                 if (mEditor != null) mEditor.makeBlink();
12877             }
12878         }
12879 
12880         if (what == Selection.SELECTION_START) {
12881             selChanged = true;
12882             newSelStart = newStart;
12883 
12884             if (oldStart >= 0 || newStart >= 0) {
12885                 int end = Selection.getSelectionEnd(buf);
12886                 invalidateCursor(end, oldStart, newStart);
12887             }
12888         }
12889 
12890         if (selChanged) {
12891             clearGesturePreviewHighlight();
12892             mHighlightPathBogus = true;
12893             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
12894 
12895             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
12896                 if (newSelStart < 0) {
12897                     newSelStart = Selection.getSelectionStart(buf);
12898                 }
12899                 if (newSelEnd < 0) {
12900                     newSelEnd = Selection.getSelectionEnd(buf);
12901                 }
12902 
12903                 if (mEditor != null) {
12904                     mEditor.refreshTextActionMode();
12905                     if (!hasSelection()
12906                             && mEditor.getTextActionMode() == null && hasTransientState()) {
12907                         // User generated selection has been removed.
12908                         setHasTransientState(false);
12909                     }
12910                 }
12911                 onSelectionChanged(newSelStart, newSelEnd);
12912             }
12913         }
12914 
12915         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
12916                 || what instanceof CharacterStyle) {
12917             if (ims == null || ims.mBatchEditNesting == 0) {
12918                 invalidate();
12919                 mHighlightPathBogus = true;
12920                 checkForResize();
12921             } else {
12922                 ims.mContentChanged = true;
12923             }
12924             if (mEditor != null) {
12925                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
12926                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
12927                 mEditor.invalidateHandlesAndActionMode();
12928             }
12929         }
12930 
12931         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
12932             mHighlightPathBogus = true;
12933             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
12934                 ims.mSelectionModeChanged = true;
12935             }
12936 
12937             if (Selection.getSelectionStart(buf) >= 0) {
12938                 if (ims == null || ims.mBatchEditNesting == 0) {
12939                     invalidateCursor();
12940                 } else {
12941                     ims.mCursorChanged = true;
12942                 }
12943             }
12944         }
12945 
12946         if (what instanceof ParcelableSpan) {
12947             // If this is a span that can be sent to a remote process,
12948             // the current extract editor would be interested in it.
12949             if (ims != null && ims.mExtractedTextRequest != null) {
12950                 if (ims.mBatchEditNesting != 0) {
12951                     if (oldStart >= 0) {
12952                         if (ims.mChangedStart > oldStart) {
12953                             ims.mChangedStart = oldStart;
12954                         }
12955                         if (ims.mChangedStart > oldEnd) {
12956                             ims.mChangedStart = oldEnd;
12957                         }
12958                     }
12959                     if (newStart >= 0) {
12960                         if (ims.mChangedStart > newStart) {
12961                             ims.mChangedStart = newStart;
12962                         }
12963                         if (ims.mChangedStart > newEnd) {
12964                             ims.mChangedStart = newEnd;
12965                         }
12966                     }
12967                 } else {
12968                     if (DEBUG_EXTRACT) {
12969                         Log.v(LOG_TAG, "Span change outside of batch: "
12970                                 + oldStart + "-" + oldEnd + ","
12971                                 + newStart + "-" + newEnd + " " + what);
12972                     }
12973                     ims.mContentChanged = true;
12974                 }
12975             }
12976         }
12977 
12978         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
12979                 && what instanceof SpellCheckSpan) {
12980             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
12981         }
12982     }
12983 
12984     @Override
12985     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
12986         if (isTemporarilyDetached()) {
12987             // If we are temporarily in the detach state, then do nothing.
12988             super.onFocusChanged(focused, direction, previouslyFocusedRect);
12989             return;
12990         }
12991 
12992         mHideHint = false;
12993 
12994         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
12995 
12996         if (focused) {
12997             if (mSpannable != null) {
12998                 MetaKeyKeyListener.resetMetaState(mSpannable);
12999             }
13000         }
13001 
13002         startStopMarquee(focused);
13003 
13004         if (mTransformation != null) {
13005             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
13006         }
13007 
13008         super.onFocusChanged(focused, direction, previouslyFocusedRect);
13009     }
13010 
13011     @Override
13012     public void onWindowFocusChanged(boolean hasWindowFocus) {
13013         super.onWindowFocusChanged(hasWindowFocus);
13014 
13015         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
13016 
13017         startStopMarquee(hasWindowFocus);
13018     }
13019 
13020     @Override
13021     protected void onVisibilityChanged(View changedView, int visibility) {
13022         super.onVisibilityChanged(changedView, visibility);
13023         if (mEditor != null && visibility != VISIBLE) {
13024             mEditor.hideCursorAndSpanControllers();
13025             stopTextActionMode();
13026         }
13027     }
13028 
13029     @Override
13030     public void onVisibilityAggregated(boolean isVisible) {
13031         super.onVisibilityAggregated(isVisible);
13032         startStopMarquee(isVisible);
13033     }
13034 
13035     /**
13036      * Use {@link BaseInputConnection#removeComposingSpans
13037      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
13038      * state from this text view.
13039      */
13040     public void clearComposingText() {
13041         if (mText instanceof Spannable) {
13042             BaseInputConnection.removeComposingSpans(mSpannable);
13043         }
13044     }
13045 
13046     @Override
13047     public void setSelected(boolean selected) {
13048         boolean wasSelected = isSelected();
13049 
13050         super.setSelected(selected);
13051 
13052         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
13053             if (selected) {
13054                 startMarquee();
13055             } else {
13056                 stopMarquee();
13057             }
13058         }
13059     }
13060 
13061     /**
13062      * Called from onTouchEvent() to prevent the touches by secondary fingers.
13063      * Dragging on handles can revise cursor/selection, so can dragging on the text view.
13064      * This method is a lock to avoid processing multiple fingers on both text view and handles.
13065      * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work.
13066      *
13067      * @param event The motion event that is being handled and carries the pointer info.
13068      * @param fromHandleView true if the event is delivered to selection handle or insertion
13069      * handle; false if this event is delivered to TextView.
13070      * @return Returns true to indicate that onTouchEvent() can continue processing the motion
13071      * event, otherwise false.
13072      *  - Always returns true for the first finger.
13073      *  - For secondary fingers, if the first or current finger is from TextView, returns false.
13074      *    This is to make touch mutually exclusive between the TextView and the handles, but
13075      *    not among the handles.
13076      */
13077     boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) {
13078         boolean res = true;
13079         if (mPrimePointerId == NO_POINTER_ID)  {
13080             mPrimePointerId = event.getPointerId(0);
13081             mIsPrimePointerFromHandleView = fromHandleView;
13082         } else if (mPrimePointerId != event.getPointerId(0)) {
13083             res = mIsPrimePointerFromHandleView && fromHandleView;
13084         }
13085         if (event.getActionMasked() == MotionEvent.ACTION_UP
13086             || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
13087             mPrimePointerId = -1;
13088         }
13089         return res;
13090     }
13091 
13092     @Override
13093     public boolean onTouchEvent(MotionEvent event) {
13094         if (DEBUG_CURSOR) {
13095             logCursor("onTouchEvent", "%d: %s (%f,%f)",
13096                     event.getSequenceNumber(),
13097                     MotionEvent.actionToString(event.getActionMasked()),
13098                     event.getX(), event.getY());
13099         }
13100         mLastInputSource = event.getSource();
13101         final int action = event.getActionMasked();
13102         if (mEditor != null) {
13103             if (!isFromPrimePointer(event, false)) {
13104                 return true;
13105             }
13106 
13107             mEditor.onTouchEvent(event);
13108 
13109             if (mEditor.mInsertionPointCursorController != null
13110                     && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
13111                 return true;
13112             }
13113             if (mEditor.mSelectionModifierCursorController != null
13114                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
13115                 return true;
13116             }
13117         }
13118 
13119         final boolean superResult = super.onTouchEvent(event);
13120         if (DEBUG_CURSOR) {
13121             logCursor("onTouchEvent", "superResult=%s", superResult);
13122         }
13123 
13124         /*
13125          * Don't handle the release after a long press, because it will move the selection away from
13126          * whatever the menu action was trying to affect. If the long press should have triggered an
13127          * insertion action mode, we can now actually show it.
13128          */
13129         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
13130             mEditor.mDiscardNextActionUp = false;
13131             if (DEBUG_CURSOR) {
13132                 logCursor("onTouchEvent", "release after long press detected");
13133             }
13134             if (mEditor.mIsInsertionActionModeStartPending) {
13135                 mEditor.startInsertionActionMode();
13136                 mEditor.mIsInsertionActionModeStartPending = false;
13137             }
13138             return superResult;
13139         }
13140 
13141         // At this point, the event is not a long press, otherwise it would be handled above.
13142         if (Flags.handwritingEndOfLineTap() && action == MotionEvent.ACTION_UP
13143                 && shouldStartHandwritingForEndOfLineTap(event)) {
13144             InputMethodManager imm = getInputMethodManager();
13145             if (imm != null) {
13146                 imm.startStylusHandwriting(this);
13147                 return true;
13148             }
13149         }
13150 
13151         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
13152                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
13153 
13154         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
13155                 && mText instanceof Spannable && mLayout != null) {
13156             boolean handled = false;
13157 
13158             if (mMovement != null) {
13159                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
13160             }
13161 
13162             final boolean textIsSelectable = isTextSelectable();
13163             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
13164                 // The LinkMovementMethod which should handle taps on links has not been installed
13165                 // on non editable text that support text selection.
13166                 // We reproduce its behavior here to open links for these.
13167                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
13168                     getSelectionEnd(), ClickableSpan.class);
13169 
13170                 if (links.length > 0) {
13171                     links[0].onClick(this);
13172                     handled = true;
13173                 }
13174             }
13175 
13176             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
13177                 // Show the IME, except when selecting in read-only text.
13178                 final InputMethodManager imm = getInputMethodManager();
13179                 viewClicked(imm);
13180                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null
13181                         && !showAutofillDialog()) {
13182                     imm.showSoftInput(this, 0);
13183                 }
13184 
13185                 // The above condition ensures that the mEditor is not null
13186                 mEditor.onTouchUpEvent(event);
13187 
13188                 handled = true;
13189             }
13190 
13191             if (handled) {
13192                 return true;
13193             }
13194         }
13195 
13196         return superResult;
13197     }
13198 
13199     /**
13200      * If handwriting is supported, the TextView is already focused and not empty, and the cursor is
13201      * at the end of a line, a stylus tap after the end of the line will trigger handwriting.
13202      */
13203     private boolean shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent) {
13204         if (!onCheckIsTextEditor()
13205                 || !isEnabled()
13206                 || !isAutoHandwritingEnabled()
13207                 || TextUtils.isEmpty(mText)
13208                 || didTouchFocusSelect()
13209                 || mLayout == null
13210                 || !actionUpEvent.isStylusPointer()) {
13211             return false;
13212         }
13213         int cursorOffset = getSelectionStart();
13214         if (cursorOffset < 0 || getSelectionEnd() != cursorOffset) {
13215             return false;
13216         }
13217         int cursorLine = mLayout.getLineForOffset(cursorOffset);
13218         int cursorLineEnd = mLayout.getLineEnd(cursorLine);
13219         if (cursorLine != mLayout.getLineCount() - 1) {
13220             cursorLineEnd--;
13221         }
13222         if (cursorLineEnd != cursorOffset) {
13223             return false;
13224         }
13225         // Check that the stylus down point is within the same line as the cursor.
13226         if (getLineAtCoordinate(actionUpEvent.getY()) != cursorLine) {
13227             return false;
13228         }
13229         // Check that the stylus down point is after the end of the line.
13230         float localX = convertToLocalHorizontalCoordinate(actionUpEvent.getX());
13231         if (mLayout.getParagraphDirection(cursorLine) == Layout.DIR_RIGHT_TO_LEFT
13232                 ? localX >= mLayout.getLineLeft(cursorLine)
13233                 : localX <= mLayout.getLineRight(cursorLine)) {
13234             return false;
13235         }
13236         return isStylusHandwritingAvailable();
13237     }
13238 
13239     /**
13240      * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
13241      *
13242      * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
13243      * @hide
13244      */
13245     public final boolean showUIForTouchScreen() {
13246         return (mLastInputSource & InputDevice.SOURCE_TOUCHSCREEN)
13247                 == InputDevice.SOURCE_TOUCHSCREEN;
13248     }
13249 
13250     /**
13251      * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI.
13252      * If autofill suggestions are available when the user clicks on a field that supports filling
13253      * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area
13254      * to display the datasets, so it is easy for users to pay attention to the datasets and
13255      * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better
13256      * experience is not to show the IME if there is a fill dialog.
13257      */
13258     private boolean showAutofillDialog() {
13259         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
13260         if (afm != null) {
13261             return afm.showAutofillDialog(this);
13262         }
13263         return false;
13264     }
13265 
13266     @Override
13267     public boolean onGenericMotionEvent(MotionEvent event) {
13268         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
13269             try {
13270                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
13271                     return true;
13272                 }
13273             } catch (AbstractMethodError ex) {
13274                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
13275                 // Ignore its absence in case third party applications implemented the
13276                 // interface directly.
13277             }
13278         }
13279         return super.onGenericMotionEvent(event);
13280     }
13281 
13282     @Override
13283     protected void onCreateContextMenu(ContextMenu menu) {
13284         if (mEditor != null) {
13285             mEditor.onCreateContextMenu(menu);
13286         }
13287     }
13288 
13289     @Override
13290     public boolean showContextMenu() {
13291         if (mEditor != null) {
13292             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
13293         }
13294         return super.showContextMenu();
13295     }
13296 
13297     @Override
13298     public boolean showContextMenu(float x, float y) {
13299         if (mEditor != null) {
13300             mEditor.setContextMenuAnchor(x, y);
13301         }
13302         return super.showContextMenu(x, y);
13303     }
13304 
13305     /**
13306      * @return True iff this TextView contains a text that can be edited, or if this is
13307      * a selectable TextView.
13308      */
13309     @UnsupportedAppUsage
13310     boolean isTextEditable() {
13311         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
13312     }
13313 
13314     /**
13315      * @return true if this TextView could be filled by an Autofill service. Note that disabled
13316      * fields can still be filled.
13317      */
13318     @UnsupportedAppUsage
13319     boolean isTextAutofillable() {
13320         return mText instanceof Editable && onCheckIsTextEditor();
13321     }
13322 
13323     /**
13324      * Returns true, only while processing a touch gesture, if the initial
13325      * touch down event caused focus to move to the text view and as a result
13326      * its selection changed.  Only valid while processing the touch gesture
13327      * of interest, in an editable text view.
13328      */
13329     public boolean didTouchFocusSelect() {
13330         return mEditor != null && mEditor.mTouchFocusSelected;
13331     }
13332 
13333     @Override
13334     public void cancelLongPress() {
13335         super.cancelLongPress();
13336         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
13337     }
13338 
13339     @Override
13340     public boolean onTrackballEvent(MotionEvent event) {
13341         if (mMovement != null && mSpannable != null && mLayout != null) {
13342             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
13343                 return true;
13344             }
13345         }
13346 
13347         return super.onTrackballEvent(event);
13348     }
13349 
13350     /**
13351      * Sets the Scroller used for producing a scrolling animation
13352      *
13353      * @param s A Scroller instance
13354      */
13355     public void setScroller(Scroller s) {
13356         mScroller = s;
13357     }
13358 
13359     @Override
13360     protected float getLeftFadingEdgeStrength() {
13361         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
13362             final Marquee marquee = mMarquee;
13363             if (marquee.shouldDrawLeftFade()) {
13364                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
13365             } else {
13366                 return 0.0f;
13367             }
13368         } else if (getLineCount() == 1) {
13369             final float lineLeft = getLayout().getLineLeft(0);
13370             if (lineLeft > mScrollX) return 0.0f;
13371             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
13372         }
13373         return super.getLeftFadingEdgeStrength();
13374     }
13375 
13376     @Override
13377     protected float getRightFadingEdgeStrength() {
13378         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
13379             final Marquee marquee = mMarquee;
13380             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
13381         } else if (getLineCount() == 1) {
13382             final float rightEdge = mScrollX +
13383                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
13384             final float lineRight = getLayout().getLineRight(0);
13385             if (lineRight < rightEdge) return 0.0f;
13386             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
13387         }
13388         return super.getRightFadingEdgeStrength();
13389     }
13390 
13391     /**
13392      * Calculates the fading edge strength as the ratio of the distance between two
13393      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
13394      * value for the distance calculation.
13395      *
13396      * @param position1 A horizontal position.
13397      * @param position2 A horizontal position.
13398      * @return Fading edge strength between [0.0f, 1.0f].
13399      */
13400     @FloatRange(from = 0.0, to = 1.0)
13401     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
13402         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
13403         if (horizontalFadingEdgeLength == 0) return 0.0f;
13404         final float diff = Math.abs(position1 - position2);
13405         if (diff > horizontalFadingEdgeLength) return 1.0f;
13406         return diff / horizontalFadingEdgeLength;
13407     }
13408 
13409     private boolean isMarqueeFadeEnabled() {
13410         return mEllipsize == TextUtils.TruncateAt.MARQUEE
13411                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
13412     }
13413 
13414     @Override
13415     protected int computeHorizontalScrollRange() {
13416         if (mLayout != null) {
13417             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
13418                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
13419         }
13420 
13421         return super.computeHorizontalScrollRange();
13422     }
13423 
13424     @Override
13425     protected int computeVerticalScrollRange() {
13426         if (mLayout != null) {
13427             return mLayout.getHeight();
13428         }
13429         return super.computeVerticalScrollRange();
13430     }
13431 
13432     @Override
13433     protected int computeVerticalScrollExtent() {
13434         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
13435     }
13436 
13437     @Override
13438     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
13439         super.findViewsWithText(outViews, searched, flags);
13440         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
13441                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
13442             String searchedLowerCase = searched.toString().toLowerCase();
13443             String textLowerCase = mText.toString().toLowerCase();
13444             if (textLowerCase.contains(searchedLowerCase)) {
13445                 outViews.add(this);
13446             }
13447         }
13448     }
13449 
13450     /**
13451      * Type of the text buffer that defines the characteristics of the text such as static,
13452      * styleable, or editable.
13453      */
13454     public enum BufferType {
13455         NORMAL, SPANNABLE, EDITABLE
13456     }
13457 
13458     /**
13459      * Returns the TextView_textColor attribute from the TypedArray, if set, or
13460      * the TextAppearance_textColor from the TextView_textAppearance attribute,
13461      * if TextView_textColor was not set directly.
13462      *
13463      * @removed
13464      */
13465     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
13466         if (attrs == null) {
13467             // Preserve behavior prior to removal of this API.
13468             throw new NullPointerException();
13469         }
13470 
13471         // It's not safe to use this method from apps. The parameter 'attrs'
13472         // must have been obtained using the TextView filter array which is not
13473         // available to the SDK. As such, we grab a default TypedArray with the
13474         // right filter instead here.
13475         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
13476         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
13477         if (colors == null) {
13478             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
13479             if (ap != 0) {
13480                 final TypedArray appearance = context.obtainStyledAttributes(
13481                         ap, R.styleable.TextAppearance);
13482                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
13483                 appearance.recycle();
13484             }
13485         }
13486         a.recycle();
13487 
13488         return colors;
13489     }
13490 
13491     /**
13492      * Returns the default color from the TextView_textColor attribute from the
13493      * AttributeSet, if set, or the default color from the
13494      * TextAppearance_textColor from the TextView_textAppearance attribute, if
13495      * TextView_textColor was not set directly.
13496      *
13497      * @removed
13498      */
13499     public static int getTextColor(Context context, TypedArray attrs, int def) {
13500         final ColorStateList colors = getTextColors(context, attrs);
13501         if (colors == null) {
13502             return def;
13503         } else {
13504             return colors.getDefaultColor();
13505         }
13506     }
13507 
13508     @Override
13509     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
13510         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
13511             // Handle Ctrl-only shortcuts.
13512             switch (keyCode) {
13513                 case KeyEvent.KEYCODE_A:
13514                     if (canSelectText()) {
13515                         return onTextContextMenuItem(ID_SELECT_ALL);
13516                     }
13517                     break;
13518                 case KeyEvent.KEYCODE_Z:
13519                     if (canUndo()) {
13520                         return onTextContextMenuItem(ID_UNDO);
13521                     }
13522                     break;
13523                 case KeyEvent.KEYCODE_X:
13524                     if (canCut()) {
13525                         return onTextContextMenuItem(ID_CUT);
13526                     }
13527                     break;
13528                 case KeyEvent.KEYCODE_C:
13529                     if (canCopy()) {
13530                         return onTextContextMenuItem(ID_COPY);
13531                     }
13532                     break;
13533                 case KeyEvent.KEYCODE_V:
13534                     if (canPaste()) {
13535                         return onTextContextMenuItem(ID_PASTE);
13536                     }
13537                     break;
13538                 case KeyEvent.KEYCODE_Y:
13539                     if (canRedo()) {
13540                         return onTextContextMenuItem(ID_REDO);
13541                     }
13542                     break;
13543             }
13544         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
13545             // Handle Ctrl-Shift shortcuts.
13546             switch (keyCode) {
13547                 case KeyEvent.KEYCODE_Z:
13548                     if (canRedo()) {
13549                         return onTextContextMenuItem(ID_REDO);
13550                     }
13551                     break;
13552                 case KeyEvent.KEYCODE_V:
13553                     if (canPaste()) {
13554                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
13555                     }
13556             }
13557         }
13558         return super.onKeyShortcut(keyCode, event);
13559     }
13560 
13561     /**
13562      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
13563      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
13564      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
13565      * sufficient.
13566      */
13567     boolean canSelectText() {
13568         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
13569     }
13570 
13571     /**
13572      * Test based on the <i>intrinsic</i> charateristics of the TextView.
13573      * The text must be spannable and the movement method must allow for arbitary selection.
13574      *
13575      * See also {@link #canSelectText()}.
13576      */
13577     boolean textCanBeSelected() {
13578         // prepareCursorController() relies on this method.
13579         // If you change this condition, make sure prepareCursorController is called anywhere
13580         // the value of this condition might be changed.
13581         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
13582         return isTextEditable()
13583                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
13584     }
13585 
13586     @UnsupportedAppUsage
13587     private Locale getTextServicesLocale(boolean allowNullLocale) {
13588         // Start fetching the text services locale asynchronously.
13589         updateTextServicesLocaleAsync();
13590         // If !allowNullLocale and there is no cached text services locale, just return the default
13591         // locale.
13592         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
13593                 : mCurrentSpellCheckerLocaleCache;
13594     }
13595 
13596     /**
13597      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
13598      * this {@link TextView}.
13599      *
13600      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
13601      * other apps may need to set this so that the system can user right user's resources and
13602      * services such as input methods and spell checkers.</p>
13603      *
13604      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
13605      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
13606      * @hide
13607      */
13608     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
13609     public final void setTextOperationUser(@Nullable UserHandle user) {
13610         if (Objects.equals(mTextOperationUser, user)) {
13611             return;
13612         }
13613         if (user != null && !Process.myUserHandle().equals(user)) {
13614             // Just for preventing people from accidentally using this hidden API without
13615             // the required permission.  The same permission is also checked in the system server.
13616             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
13617                     != PackageManager.PERMISSION_GRANTED) {
13618                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
13619                         + " userId=" + user.getIdentifier()
13620                         + " callingUserId" + UserHandle.myUserId());
13621             }
13622         }
13623         mTextOperationUser = user;
13624         // Invalidate some resources
13625         mCurrentSpellCheckerLocaleCache = null;
13626         if (mEditor != null) {
13627             mEditor.onTextOperationUserChanged();
13628         }
13629     }
13630 
13631     @Override
13632     public boolean isAutoHandwritingEnabled() {
13633         return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType();
13634     }
13635 
13636     /** @hide */
13637     @Override
13638     public boolean shouldTrackHandwritingArea() {
13639         // The handwriting initiator tracks all editable TextViews regardless of whether handwriting
13640         // is supported, so that it can show an error message for unsupported editable TextViews.
13641         return super.shouldTrackHandwritingArea()
13642                 || (Flags.handwritingUnsupportedMessage() && onCheckIsTextEditor());
13643     }
13644 
13645     /** @hide */
13646     @Override
13647     public boolean isStylusHandwritingAvailable() {
13648         if (mTextOperationUser == null) {
13649             return super.isStylusHandwritingAvailable();
13650         }
13651         final InputMethodManager imm = getInputMethodManager();
13652         return imm.isStylusHandwritingAvailableAsUser(mTextOperationUser);
13653     }
13654 
13655     @Nullable
13656     final TextServicesManager getTextServicesManagerForUser() {
13657         return getServiceManagerForUser("android", TextServicesManager.class);
13658     }
13659 
13660     @Nullable
13661     final ClipboardManager getClipboardManagerForUser() {
13662         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
13663     }
13664 
13665     @Nullable
13666     final TextClassificationManager getTextClassificationManagerForUser() {
13667         return getServiceManagerForUser(
13668                 getContext().getPackageName(), TextClassificationManager.class);
13669     }
13670 
13671     @Nullable
13672     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
13673         if (mTextOperationUser == null) {
13674             return getContext().getSystemService(managerClazz);
13675         }
13676         try {
13677             Context context = getContext().createPackageContextAsUser(
13678                     packageName, 0 /* flags */, mTextOperationUser);
13679             return context.getSystemService(managerClazz);
13680         } catch (PackageManager.NameNotFoundException e) {
13681             return null;
13682         }
13683     }
13684 
13685     /**
13686      * Starts {@link Activity} as a text-operation user if it is specified with
13687      * {@link #setTextOperationUser(UserHandle)}.
13688      *
13689      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
13690      *
13691      * @param intent The description of the activity to start.
13692      */
13693     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
13694         if (mTextOperationUser != null) {
13695             getContext().startActivityAsUser(intent, mTextOperationUser);
13696         } else {
13697             getContext().startActivity(intent);
13698         }
13699     }
13700 
13701     /**
13702      * This is a temporary method. Future versions may support multi-locale text.
13703      * Caveat: This method may not return the latest text services locale, but this should be
13704      * acceptable and it's more important to make this method asynchronous.
13705      *
13706      * @return The locale that should be used for a word iterator
13707      * in this TextView, based on the current spell checker settings,
13708      * the current IME's locale, or the system default locale.
13709      * Please note that a word iterator in this TextView is different from another word iterator
13710      * used by SpellChecker.java of TextView. This method should be used for the former.
13711      * @hide
13712      */
13713     // TODO: Support multi-locale
13714     // TODO: Update the text services locale immediately after the keyboard locale is switched
13715     // by catching intent of keyboard switch event
13716     public Locale getTextServicesLocale() {
13717         return getTextServicesLocale(false /* allowNullLocale */);
13718     }
13719 
13720     /**
13721      * @return {@code true} if this TextView is specialized for showing and interacting with the
13722      * extracted text in a full-screen input method.
13723      * @hide
13724      */
13725     public boolean isInExtractedMode() {
13726         return false;
13727     }
13728 
13729     /**
13730      * @return {@code true} if this widget supports auto-sizing text and has been configured to
13731      * auto-size.
13732      */
13733     private boolean isAutoSizeEnabled() {
13734         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
13735     }
13736 
13737     /**
13738      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
13739      * @hide
13740      */
13741     protected boolean supportsAutoSizeText() {
13742         return true;
13743     }
13744 
13745     /**
13746      * This is a temporary method. Future versions may support multi-locale text.
13747      * Caveat: This method may not return the latest spell checker locale, but this should be
13748      * acceptable and it's more important to make this method asynchronous.
13749      *
13750      * @return The locale that should be used for a spell checker in this TextView,
13751      * based on the current spell checker settings, the current IME's locale, or the system default
13752      * locale.
13753      * @hide
13754      */
13755     public Locale getSpellCheckerLocale() {
13756         return getTextServicesLocale(true /* allowNullLocale */);
13757     }
13758 
13759     private void updateTextServicesLocaleAsync() {
13760         // AsyncTask.execute() uses a serial executor which means we don't have
13761         // to lock around updateTextServicesLocaleLocked() to prevent it from
13762         // being executed n times in parallel.
13763         AsyncTask.execute(new Runnable() {
13764             @Override
13765             public void run() {
13766                 updateTextServicesLocaleLocked();
13767             }
13768         });
13769     }
13770 
13771     @UnsupportedAppUsage
13772     private void updateTextServicesLocaleLocked() {
13773         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
13774         if (textServicesManager == null) {
13775             return;
13776         }
13777         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
13778         final Locale locale;
13779         if (subtype != null) {
13780             locale = subtype.getLocaleObject();
13781         } else {
13782             locale = null;
13783         }
13784         mCurrentSpellCheckerLocaleCache = locale;
13785     }
13786 
13787     void onLocaleChanged() {
13788         mEditor.onLocaleChanged();
13789     }
13790 
13791     /**
13792      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
13793      * Made available to achieve a consistent behavior.
13794      * @hide
13795      */
13796     public WordIterator getWordIterator() {
13797         if (mEditor != null) {
13798             return mEditor.getWordIterator();
13799         } else {
13800             return null;
13801         }
13802     }
13803 
13804     /** @hide */
13805     @Override
13806     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
13807         super.onPopulateAccessibilityEventInternal(event);
13808 
13809         if (this.isAccessibilityDataSensitive() && !event.isAccessibilityDataSensitive()) {
13810             // This view's accessibility data is sensitive, but another view that generated this
13811             // event is not, so don't append this view's text to the event in order to prevent
13812             // sharing this view's contents with non-accessibility-tool services.
13813             return;
13814         }
13815 
13816         final CharSequence text = getTextForAccessibility();
13817         if (!TextUtils.isEmpty(text)) {
13818             event.getText().add(text);
13819         }
13820     }
13821 
13822     @Override
13823     public CharSequence getAccessibilityClassName() {
13824         return TextView.class.getName();
13825     }
13826 
13827     /** @hide */
13828     @Override
13829     protected void onProvideStructure(@NonNull ViewStructure structure,
13830             @ViewStructureType int viewFor, int flags) {
13831         super.onProvideStructure(structure, viewFor, flags);
13832 
13833         final boolean isPassword = hasPasswordTransformationMethod()
13834                 || isPasswordInputType(getInputType());
13835         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
13836                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13837             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13838                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
13839             }
13840             if (mTextId != Resources.ID_NULL) {
13841                 try {
13842                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
13843                 } catch (Resources.NotFoundException e) {
13844                     if (android.view.autofill.Helper.sVerbose) {
13845                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
13846                                 + mTextId + ": " + e.getMessage());
13847                     }
13848                 }
13849             }
13850             String[] mimeTypes = getReceiveContentMimeTypes();
13851             if (mimeTypes == null && mEditor != null) {
13852                 // If the app hasn't set a listener for receiving content on this view (ie,
13853                 // getReceiveContentMimeTypes() returns null), check if it implements the
13854                 // keyboard image API and, if possible, use those MIME types as fallback.
13855                 // This fallback is only in place for autofill, not other mechanisms for
13856                 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER
13857                 // in TextViewOnReceiveContentListener for more info.
13858                 mimeTypes = mEditor.getDefaultOnReceiveContentListener()
13859                         .getFallbackMimeTypesForAutofill(this);
13860             }
13861             structure.setReceiveContentMimeTypes(mimeTypes);
13862         }
13863 
13864         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
13865                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13866             if (mLayout == null) {
13867                 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13868                     Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
13869                 }
13870                 assumeLayout();
13871             }
13872             Layout layout = mLayout;
13873             final int lineCount = layout.getLineCount();
13874             if (lineCount <= 1) {
13875                 // Simple case: this is a single line.
13876                 final CharSequence text = getText();
13877                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13878                     structure.setText(text);
13879                 } else {
13880                     structure.setText(text, getSelectionStart(), getSelectionEnd());
13881                 }
13882             } else {
13883                 // Complex case: multi-line, could be scrolled or within a scroll container
13884                 // so some lines are not visible.
13885                 final int[] tmpCords = new int[2];
13886                 getLocationInWindow(tmpCords);
13887                 final int topWindowLocation = tmpCords[1];
13888                 View root = this;
13889                 ViewParent viewParent = getParent();
13890                 while (viewParent instanceof View) {
13891                     root = (View) viewParent;
13892                     viewParent = root.getParent();
13893                 }
13894                 final int windowHeight = root.getHeight();
13895                 final int topLine;
13896                 final int bottomLine;
13897                 if (topWindowLocation >= 0) {
13898                     // The top of the view is fully within its window; start text at line 0.
13899                     topLine = getLineAtCoordinateUnclamped(0);
13900                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
13901                 } else {
13902                     // The top of hte window has scrolled off the top of the window; figure out
13903                     // the starting line for this.
13904                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
13905                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
13906                 }
13907                 // We want to return some contextual lines above/below the lines that are
13908                 // actually visible.
13909                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
13910                 if (expandedTopLine < 0) {
13911                     expandedTopLine = 0;
13912                 }
13913                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
13914                 if (expandedBottomLine >= lineCount) {
13915                     expandedBottomLine = lineCount - 1;
13916                 }
13917 
13918                 // Convert lines into character offsets.
13919                 int expandedTopChar = transformedToOriginal(
13920                         layout.getLineStart(expandedTopLine),
13921                         OffsetMapping.MAP_STRATEGY_CHARACTER);
13922                 int expandedBottomChar = transformedToOriginal(
13923                         layout.getLineEnd(expandedBottomLine),
13924                         OffsetMapping.MAP_STRATEGY_CHARACTER);
13925 
13926                 // Take into account selection -- if there is a selection, we need to expand
13927                 // the text we are returning to include that selection.
13928                 final int selStart = getSelectionStart();
13929                 final int selEnd = getSelectionEnd();
13930                 if (selStart < selEnd) {
13931                     if (selStart < expandedTopChar) {
13932                         expandedTopChar = selStart;
13933                     }
13934                     if (selEnd > expandedBottomChar) {
13935                         expandedBottomChar = selEnd;
13936                     }
13937                 }
13938 
13939                 // Get the text and trim it to the range we are reporting.
13940                 CharSequence text = getText();
13941 
13942                 if (text != null) {
13943                     if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
13944                         // Cap the offsets to avoid an OOB exception. That can happen if the
13945                         // displayed/layout text, on which these offsets are calculated, is longer
13946                         // than the original text (such as when the view is translated by the
13947                         // platform intelligence).
13948                         // TODO(b/196433694): Figure out how to better handle the offset
13949                         // calculations for this case (so we don't unnecessarily cutoff the original
13950                         // text, for example).
13951                         expandedTopChar = Math.min(expandedTopChar, text.length());
13952                         expandedBottomChar = Math.min(expandedBottomChar, text.length());
13953                         text = text.subSequence(expandedTopChar, expandedBottomChar);
13954                     }
13955 
13956                     if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13957                         structure.setText(text);
13958                     } else {
13959                         structure.setText(text,
13960                                 selStart - expandedTopChar,
13961                                 selEnd - expandedTopChar);
13962 
13963                         final int[] lineOffsets = new int[bottomLine - topLine + 1];
13964                         final int[] lineBaselines = new int[bottomLine - topLine + 1];
13965                         final int baselineOffset = getBaselineOffset();
13966                         for (int i = topLine; i <= bottomLine; i++) {
13967                             lineOffsets[i - topLine] = transformedToOriginal(layout.getLineStart(i),
13968                                     OffsetMapping.MAP_STRATEGY_CHARACTER);
13969                             lineBaselines[i - topLine] =
13970                                     layout.getLineBaseline(i) + baselineOffset;
13971                         }
13972                         structure.setTextLines(lineOffsets, lineBaselines);
13973                     }
13974                 }
13975             }
13976 
13977             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
13978                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13979                 // Extract style information that applies to the TextView as a whole.
13980                 int style = 0;
13981                 int typefaceStyle = getTypefaceStyle();
13982                 if ((typefaceStyle & Typeface.BOLD) != 0) {
13983                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
13984                 }
13985                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
13986                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
13987                 }
13988 
13989                 // Global styles can also be set via TextView.setPaintFlags().
13990                 int paintFlags = mTextPaint.getFlags();
13991                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
13992                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
13993                 }
13994                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
13995                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
13996                 }
13997                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
13998                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
13999                 }
14000 
14001                 // TextView does not have its own text background color. A background is either part
14002                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
14003                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
14004                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
14005             }
14006             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
14007                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
14008                 structure.setMinTextEms(getMinEms());
14009                 structure.setMaxTextEms(getMaxEms());
14010                 int maxLength = -1;
14011                 for (InputFilter filter: getFilters()) {
14012                     if (filter instanceof InputFilter.LengthFilter) {
14013                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
14014                         break;
14015                     }
14016                 }
14017                 structure.setMaxTextLength(maxLength);
14018             }
14019         }
14020         if (mHintId != Resources.ID_NULL) {
14021             try {
14022                 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId));
14023             } catch (Resources.NotFoundException e) {
14024                 if (android.view.autofill.Helper.sVerbose) {
14025                     Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id "
14026                             + mHintId + ": " + e.getMessage());
14027                 }
14028             }
14029         }
14030         structure.setHint(getHint());
14031         structure.setInputType(getInputType());
14032     }
14033 
14034     boolean canRequestAutofill() {
14035         if (!isAutofillable()) {
14036             return false;
14037         }
14038         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
14039         if (afm != null) {
14040             return afm.isEnabled();
14041         }
14042         return false;
14043     }
14044 
14045     private void requestAutofill() {
14046         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
14047         if (afm != null) {
14048             afm.requestAutofill(this);
14049         }
14050     }
14051 
14052     @Override
14053     public void autofill(AutofillValue value) {
14054         if (android.view.autofill.Helper.sVerbose) {
14055             Log.v(LOG_TAG, "autofill() called on textview for id:" + getAutofillId());
14056         }
14057         if (!isTextAutofillable()) {
14058             Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
14059             return;
14060         }
14061         if (!value.isText()) {
14062             Log.w(LOG_TAG, "value of type " + value.describeContents()
14063                     + " cannot be autofilled into " + this);
14064             return;
14065         }
14066         final ClipData clip = ClipData.newPlainText("", value.getTextValue());
14067         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build();
14068         performReceiveContent(payload);
14069     }
14070 
14071     @Override
14072     public @AutofillType int getAutofillType() {
14073         return isTextAutofillable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
14074     }
14075 
14076     /**
14077      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
14078      * {@code char}s if longer.
14079      *
14080      * @return current text, {@code null} if the text is not editable
14081      *
14082      * @see View#getAutofillValue()
14083      */
14084     @Override
14085     @Nullable
14086     public AutofillValue getAutofillValue() {
14087         if (isTextAutofillable()) {
14088             final CharSequence text = TextUtils.trimToParcelableSize(getText());
14089             return AutofillValue.forText(text);
14090         }
14091         return null;
14092     }
14093 
14094     /** @hide */
14095     @Override
14096     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
14097         super.onInitializeAccessibilityEventInternal(event);
14098 
14099         final boolean isPassword = hasPasswordTransformationMethod();
14100         event.setPassword(isPassword);
14101 
14102         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
14103             event.setFromIndex(Selection.getSelectionStart(mText));
14104             event.setToIndex(Selection.getSelectionEnd(mText));
14105             event.setItemCount(mText.length());
14106         }
14107     }
14108 
14109     /** @hide */
14110     @Override
14111     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
14112         super.onInitializeAccessibilityNodeInfoInternal(info);
14113 
14114         final boolean isPassword = hasPasswordTransformationMethod();
14115         info.setPassword(isPassword);
14116         info.setText(getTextForAccessibility());
14117         info.setHintText(mHint);
14118         info.setShowingHintText(isShowingHint());
14119 
14120         if (mBufferType == BufferType.EDITABLE) {
14121             info.setEditable(true);
14122             if (isEnabled()) {
14123                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
14124             }
14125         }
14126 
14127         if (mEditor != null) {
14128             info.setInputType(mEditor.mInputType);
14129 
14130             if (mEditor.mError != null) {
14131                 info.setContentInvalid(true);
14132                 info.setError(mEditor.mError);
14133             }
14134             // TextView will expose this action if it is editable and has focus.
14135             if (isTextEditable() && isFocused()) {
14136                 CharSequence imeActionLabel = mContext.getResources().getString(
14137                         com.android.internal.R.string.keyboardview_keycode_enter);
14138                 if (getImeActionLabel() != null) {
14139                     imeActionLabel = getImeActionLabel();
14140                 }
14141                 AccessibilityNodeInfo.AccessibilityAction action =
14142                         new AccessibilityNodeInfo.AccessibilityAction(
14143                                 R.id.accessibilityActionImeEnter, imeActionLabel);
14144                 info.addAction(action);
14145             }
14146         }
14147 
14148         if (!TextUtils.isEmpty(mText)) {
14149             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
14150             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
14151             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
14152                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
14153                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
14154                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
14155                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
14156             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
14157             info.setAvailableExtraData(Arrays.asList(
14158                     EXTRA_DATA_RENDERING_INFO_KEY,
14159                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
14160             ));
14161             info.setTextSelectable(isTextSelectable() || isTextEditable());
14162         } else {
14163             info.setAvailableExtraData(Arrays.asList(
14164                     EXTRA_DATA_RENDERING_INFO_KEY
14165             ));
14166         }
14167 
14168         if (isFocused()) {
14169             if (canCopy()) {
14170                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
14171             }
14172             if (canPaste()) {
14173                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
14174             }
14175             if (canCut()) {
14176                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
14177             }
14178             if (canReplace()) {
14179                 info.addAction(
14180                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS);
14181             }
14182             if (canShare()) {
14183                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
14184                         ACCESSIBILITY_ACTION_SHARE,
14185                         getResources().getString(com.android.internal.R.string.share)));
14186             }
14187             if (canProcessText()) {  // also implies mEditor is not null.
14188                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
14189                 mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info);
14190             }
14191         }
14192 
14193         // Check for known input filter types.
14194         final int numFilters = mFilters.length;
14195         for (int i = 0; i < numFilters; i++) {
14196             final InputFilter filter = mFilters[i];
14197             if (filter instanceof InputFilter.LengthFilter) {
14198                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
14199             }
14200         }
14201 
14202         if (!isSingleLine()) {
14203             info.setMultiLine(true);
14204         }
14205 
14206         // A view should not be exposed as clickable/long-clickable to a service because of a
14207         // LinkMovementMethod or because it has selectable and non-editable text.
14208         if ((info.isClickable() || info.isLongClickable())
14209                 && (mMovement instanceof LinkMovementMethod
14210                 || (isTextSelectable() && !isTextEditable()))) {
14211             if (!hasOnClickListeners()) {
14212                 info.setClickable(false);
14213                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
14214             }
14215             if (!hasOnLongClickListeners()) {
14216                 info.setLongClickable(false);
14217                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
14218             }
14219         }
14220     }
14221 
14222     @Override
14223     public void addExtraDataToAccessibilityNodeInfo(
14224             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
14225         if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
14226             int positionInfoStartIndex = arguments.getInt(
14227                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
14228             int positionInfoLength = arguments.getInt(
14229                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
14230             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
14231                     || (positionInfoStartIndex >= mText.length())) {
14232                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
14233                 return;
14234             }
14235             RectF[] boundingRects = new RectF[positionInfoLength];
14236             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
14237             populateCharacterBounds(builder, positionInfoStartIndex,
14238                     Math.min(positionInfoStartIndex + positionInfoLength, length()),
14239                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
14240             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
14241             for (int i = 0; i < positionInfoLength; i++) {
14242                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
14243                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
14244                     RectF bounds = cursorAnchorInfo
14245                             .getCharacterBounds(positionInfoStartIndex + i);
14246                     if (bounds != null) {
14247                         mapRectFromViewToScreenCoords(bounds, true);
14248                         boundingRects[i] = bounds;
14249                     }
14250                 }
14251             }
14252             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
14253             return;
14254         }
14255         if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) {
14256             final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo =
14257                     AccessibilityNodeInfo.ExtraRenderingInfo.obtain();
14258             extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height);
14259             extraRenderingInfo.setTextSizeInPx(getTextSize());
14260             extraRenderingInfo.setTextSizeUnit(getTextSizeUnit());
14261             info.setExtraRenderingInfo(extraRenderingInfo);
14262         }
14263     }
14264 
14265     /**
14266      * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
14267      * This method obtains the view's visible rectangle whereas the method
14268      * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
14269      *
14270      * @return true if at least part of the text content is visible; false if the text content is
14271      * completely clipped or translated out of the visible area.
14272      */
14273     private boolean getViewVisibleRect(Rect rect) {
14274         if (!getLocalVisibleRect(rect)) {
14275             return false;
14276         }
14277         // getLocalVisibleRect returns a rect relative to the unscrolled left top corner of the
14278         // view. In other words, the returned rectangle's origin point is (-scrollX, -scrollY) in
14279         // view's coordinates. So we need to offset it with the negative scrolled amount to convert
14280         // it to view's coordinate.
14281         rect.offset(-getScrollX(), -getScrollY());
14282         return true;
14283     }
14284 
14285     /**
14286      * Helper method to set {@code rect} to the text content's non-clipped area in the view's
14287      * coordinates.
14288      *
14289      * @return true if at least part of the text content is visible; false if the text content is
14290      * completely clipped or translated out of the visible area.
14291      */
14292     private boolean getContentVisibleRect(Rect rect) {
14293         if (!getViewVisibleRect(rect)) {
14294             return false;
14295         }
14296         // Clip the view's visible rect with the text layout's visible rect.
14297         return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
14298                 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
14299     }
14300 
14301     /**
14302      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
14303      *
14304      * @param builder The builder to populate
14305      * @param startIndex The starting character index to populate
14306      * @param endIndex The ending character index to populate
14307      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
14308      * content
14309      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
14310      * @hide
14311      */
14312     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
14313             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
14314             float viewportToContentVerticalOffset) {
14315         if (isOffsetMappingAvailable()) {
14316             // The text is transformed, and has different length, we don't support
14317             // character bounds in this case yet.
14318             return;
14319         }
14320         final Rect rect = new Rect();
14321         getContentVisibleRect(rect);
14322         final RectF visibleRect = new RectF(rect);
14323 
14324         final float[] characterBounds = getCharacterBounds(startIndex, endIndex,
14325                 viewportToContentHorizontalOffset, viewportToContentVerticalOffset);
14326         final int limit = endIndex - startIndex;
14327         for (int offset = 0; offset < limit; ++offset) {
14328             final float left = characterBounds[offset * 4];
14329             final float top = characterBounds[offset * 4 + 1];
14330             final float right = characterBounds[offset * 4 + 2];
14331             final float bottom = characterBounds[offset * 4 + 3];
14332 
14333             final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom);
14334             final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom);
14335             int characterBoundsFlags = 0;
14336             if (hasVisibleRegion) {
14337                 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
14338             }
14339             if (hasInVisibleRegion) {
14340                 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
14341             }
14342 
14343             if (mLayout.isRtlCharAt(offset)) {
14344                 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
14345             }
14346             builder.addCharacterBounds(offset + startIndex, left, top, right, bottom,
14347                     characterBoundsFlags);
14348         }
14349     }
14350 
14351     /**
14352      * Return the bounds of the characters in the given range, in TextView's coordinates.
14353      *
14354      * @param start the start index of the interested text range, inclusive.
14355      * @param end the end index of the interested text range, exclusive.
14356      * @param layoutLeft the left of the given {@code layout} in the editor view's coordinates.
14357      * @param layoutTop  the top of the given {@code layout} in the editor view's coordinates.
14358      * @return the character bounds stored in a flattened array, in the editor view's coordinates.
14359      */
14360     private float[] getCharacterBounds(int start, int end, float layoutLeft, float layoutTop) {
14361         final float[] characterBounds = new float[4 * (end - start)];
14362         mLayout.fillCharacterBounds(start, end, characterBounds, 0);
14363         for (int offset = 0; offset < end - start; ++offset) {
14364             characterBounds[4 * offset] += layoutLeft;
14365             characterBounds[4 * offset + 1] += layoutTop;
14366             characterBounds[4 * offset + 2] += layoutLeft;
14367             characterBounds[4 * offset + 3] += layoutTop;
14368         }
14369         return characterBounds;
14370     }
14371 
14372     /**
14373      * Compute {@link CursorAnchorInfo} from this {@link TextView}.
14374      *
14375      * @param filter the {@link CursorAnchorInfo} update filter which specified the needed
14376      *               information from IME.
14377      * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build
14378      *                                the result {@link CursorAnchorInfo}.
14379      * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen
14380      *                           matrix.
14381      * @return the result {@link CursorAnchorInfo} to be passed to IME.
14382      * @hide
14383      */
14384     @VisibleForTesting
14385     @Nullable
14386     public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter,
14387             @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder,
14388             @NonNull Matrix viewToScreenMatrix) {
14389         Layout layout = getLayout();
14390         if (layout == null) {
14391             return null;
14392         }
14393         boolean includeEditorBounds =
14394                 (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0;
14395         boolean includeCharacterBounds =
14396                 (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0;
14397         boolean includeInsertionMarker =
14398                 (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
14399         boolean includeVisibleLineBounds =
14400                 (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0;
14401         boolean includeTextAppearance =
14402                 (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0;
14403         boolean includeAll =
14404                 (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker
14405                         && !includeVisibleLineBounds && !includeTextAppearance);
14406 
14407         includeEditorBounds |= includeAll;
14408         includeCharacterBounds |= includeAll;
14409         includeInsertionMarker |= includeAll;
14410         includeVisibleLineBounds |= includeAll;
14411         includeTextAppearance |= includeAll;
14412 
14413         final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder;
14414         builder.reset();
14415 
14416         final int selectionStart = getSelectionStart();
14417         builder.setSelectionRange(selectionStart, getSelectionEnd());
14418 
14419         // Construct transformation matrix from view local coordinates to screen coordinates.
14420         viewToScreenMatrix.reset();
14421         transformMatrixToGlobal(viewToScreenMatrix);
14422         builder.setMatrix(viewToScreenMatrix);
14423 
14424         if (includeEditorBounds) {
14425             if (mTempRect == null) {
14426                 mTempRect = new Rect();
14427             }
14428             final Rect bounds = mTempRect;
14429             final RectF editorBounds;
14430             final RectF handwritingBounds;
14431             if (getViewVisibleRect(bounds)) {
14432                 editorBounds = new RectF(bounds);
14433                 handwritingBounds = new RectF(editorBounds);
14434                 handwritingBounds.top -= getHandwritingBoundsOffsetTop();
14435                 handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
14436                 handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
14437                 handwritingBounds.right += getHandwritingBoundsOffsetRight();
14438             } else {
14439                 // The editor is not visible at all, return empty rectangles. We still need to
14440                 // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
14441                 editorBounds = new RectF();
14442                 handwritingBounds = new RectF();
14443             }
14444             EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
14445             EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
14446                     .setHandwritingBounds(handwritingBounds).build();
14447             builder.setEditorBoundsInfo(editorBoundsInfo);
14448         }
14449 
14450         if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) {
14451             final float viewportToContentHorizontalOffset =
14452                     viewportToContentHorizontalOffset();
14453             final float viewportToContentVerticalOffset =
14454                     viewportToContentVerticalOffset();
14455             final boolean isTextTransformed = (getTransformationMethod() != null
14456                     && getTransformed() instanceof OffsetMapping);
14457             if (includeCharacterBounds && !isTextTransformed) {
14458                 final CharSequence text = getText();
14459                 if (text instanceof Spannable) {
14460                     final Spannable sp = (Spannable) text;
14461                     int composingTextStart = EditableInputConnection.getComposingSpanStart(sp);
14462                     int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp);
14463                     if (composingTextEnd < composingTextStart) {
14464                         final int temp = composingTextEnd;
14465                         composingTextEnd = composingTextStart;
14466                         composingTextStart = temp;
14467                     }
14468                     final boolean hasComposingText =
14469                             (0 <= composingTextStart) && (composingTextStart
14470                                     < composingTextEnd);
14471                     if (hasComposingText) {
14472                         final CharSequence composingText = text.subSequence(composingTextStart,
14473                                 composingTextEnd);
14474                         builder.setComposingText(composingTextStart, composingText);
14475                         populateCharacterBounds(builder, composingTextStart,
14476                                 composingTextEnd, viewportToContentHorizontalOffset,
14477                                 viewportToContentVerticalOffset);
14478                     }
14479                 }
14480             }
14481 
14482             if (includeInsertionMarker) {
14483                 // Treat selectionStart as the insertion point.
14484                 if (0 <= selectionStart) {
14485                     final int offsetTransformed = originalToTransformed(
14486                             selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR);
14487                     final int line = layout.getLineForOffset(offsetTransformed);
14488                     final float insertionMarkerX =
14489                             layout.getPrimaryHorizontal(
14490                                             offsetTransformed, layout.shouldClampCursor(line))
14491                                     + viewportToContentHorizontalOffset;
14492                     final float insertionMarkerTop = layout.getLineTop(line)
14493                             + viewportToContentVerticalOffset;
14494                     final float insertionMarkerBaseline = layout.getLineBaseline(line)
14495                             + viewportToContentVerticalOffset;
14496                     final float insertionMarkerBottom =
14497                             layout.getLineBottom(line, /* includeLineSpacing= */ false)
14498                                     + viewportToContentVerticalOffset;
14499                     final boolean isTopVisible =
14500                             isPositionVisible(insertionMarkerX, insertionMarkerTop);
14501                     final boolean isBottomVisible =
14502                             isPositionVisible(insertionMarkerX, insertionMarkerBottom);
14503                     int insertionMarkerFlags = 0;
14504                     if (isTopVisible || isBottomVisible) {
14505                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
14506                     }
14507                     if (!isTopVisible || !isBottomVisible) {
14508                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
14509                     }
14510                     if (layout.isRtlCharAt(offsetTransformed)) {
14511                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
14512                     }
14513                     builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
14514                             insertionMarkerBaseline, insertionMarkerBottom,
14515                             insertionMarkerFlags);
14516                 }
14517             }
14518 
14519             if (includeVisibleLineBounds) {
14520                 final Rect visibleRect = new Rect();
14521                 if (getContentVisibleRect(visibleRect)) {
14522                     // Subtract the viewportToContentVerticalOffset to convert the view
14523                     // coordinates to layout coordinates.
14524                     final float visibleTop =
14525                             visibleRect.top - viewportToContentVerticalOffset;
14526                     final float visibleBottom =
14527                             visibleRect.bottom - viewportToContentVerticalOffset;
14528                     final int firstLine =
14529                             layout.getLineForVertical((int) Math.floor(visibleTop));
14530                     final int lastLine =
14531                             layout.getLineForVertical((int) Math.ceil(visibleBottom));
14532 
14533                     for (int line = firstLine; line <= lastLine; ++line) {
14534                         final float left = layout.getLineLeft(line)
14535                                 + viewportToContentHorizontalOffset;
14536                         final float top = layout.getLineTop(line)
14537                                 + viewportToContentVerticalOffset;
14538                         final float right = layout.getLineRight(line)
14539                                 + viewportToContentHorizontalOffset;
14540                         final float bottom = layout.getLineBottom(line, false)
14541                                 + viewportToContentVerticalOffset;
14542                         builder.addVisibleLineBounds(left, top, right, bottom);
14543                     }
14544                 }
14545             }
14546         }
14547 
14548         if (includeTextAppearance) {
14549             builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this));
14550         }
14551         return builder.build();
14552     }
14553 
14554     /**
14555      * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}.
14556      * @hide
14557      */
14558     public TextBoundsInfo getTextBoundsInfo(@NonNull RectF bounds) {
14559         final Layout layout = getLayout();
14560         if (layout == null) {
14561             // No valid text layout, return null.
14562             return null;
14563         }
14564         final CharSequence text = layout.getText();
14565         if (text == null || isOffsetMappingAvailable()) {
14566             // The text is Null or the text has been transformed. Can't provide TextBoundsInfo.
14567             return null;
14568         }
14569 
14570         final Matrix localToGlobalMatrix = new Matrix();
14571         transformMatrixToGlobal(localToGlobalMatrix);
14572         final Matrix globalToLocalMatrix = new Matrix();
14573         if (!localToGlobalMatrix.invert(globalToLocalMatrix)) {
14574             // Can't map global rectF to local coordinates, this is almost impossible in practice.
14575             return null;
14576         }
14577 
14578         final float layoutLeft = viewportToContentHorizontalOffset();
14579         final float layoutTop = viewportToContentVerticalOffset();
14580 
14581         final RectF localBounds = new RectF(bounds);
14582         globalToLocalMatrix.mapRect(localBounds);
14583         localBounds.offset(-layoutLeft, -layoutTop);
14584 
14585         // Text length is 0. There is no character bounds, return empty TextBoundsInfo.
14586         // rectF doesn't intersect with the layout, return empty TextBoundsInfo.
14587         if (!localBounds.intersects(0f, 0f, layout.getWidth(), layout.getHeight())
14588                 || text.length() == 0) {
14589             final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 0);
14590             final SegmentFinder emptySegmentFinder =
14591                     new SegmentFinder.PrescribedSegmentFinder(new int[0]);
14592             builder.setMatrix(localToGlobalMatrix)
14593                     .setCharacterBounds(new float[0])
14594                     .setCharacterBidiLevel(new int[0])
14595                     .setCharacterFlags(new int[0])
14596                     .setGraphemeSegmentFinder(emptySegmentFinder)
14597                     .setLineSegmentFinder(emptySegmentFinder)
14598                     .setWordSegmentFinder(emptySegmentFinder);
14599             return  builder.build();
14600         }
14601 
14602         final int startLine = layout.getLineForVertical((int) Math.floor(localBounds.top));
14603         final int endLine = layout.getLineForVertical((int) Math.floor(localBounds.bottom));
14604         final int start = layout.getLineStart(startLine);
14605         final int end = layout.getLineEnd(endLine);
14606 
14607         // Compute character bounds.
14608         final float[] characterBounds = getCharacterBounds(start, end, layoutLeft, layoutTop);
14609 
14610         // Compute character flags and BiDi levels.
14611         final int[] characterFlags = new int[end - start];
14612         final int[] characterBidiLevels = new int[end - start];
14613         for (int line = startLine; line <= endLine; ++line) {
14614             final int lineStart = layout.getLineStart(line);
14615             final int lineEnd = layout.getLineEnd(line);
14616             final Layout.Directions directions = layout.getLineDirections(line);
14617             for (int i = 0; i < directions.getRunCount(); ++i) {
14618                 final int runStart = directions.getRunStart(i) + lineStart;
14619                 final int runEnd = Math.min(runStart + directions.getRunLength(i), lineEnd);
14620                 final int runLevel = directions.getRunLevel(i);
14621                 Arrays.fill(characterBidiLevels, runStart - start, runEnd - start, runLevel);
14622             }
14623 
14624             final boolean lineIsRtl =
14625                     layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT;
14626             for (int index = lineStart; index < lineEnd; ++index) {
14627                 int flags = 0;
14628                 if (TextUtils.isWhitespace(text.charAt(index))) {
14629                     flags |= TextBoundsInfo.FLAG_CHARACTER_WHITESPACE;
14630                 }
14631                 if (TextUtils.isPunctuation(Character.codePointAt(text, index))) {
14632                     flags |= TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION;
14633                 }
14634                 if (TextUtils.isNewline(Character.codePointAt(text, index))) {
14635                     flags |= TextBoundsInfo.FLAG_CHARACTER_LINEFEED;
14636                 }
14637                 if (lineIsRtl) {
14638                     flags |= TextBoundsInfo.FLAG_LINE_IS_RTL;
14639                 }
14640                 characterFlags[index - start] = flags;
14641             }
14642         }
14643 
14644         // Create grapheme SegmentFinder.
14645         final SegmentFinder graphemeSegmentFinder =
14646                 new GraphemeClusterSegmentFinder(text, layout.getPaint());
14647 
14648         // Create word SegmentFinder.
14649         final WordIterator wordIterator = getWordIterator();
14650         wordIterator.setCharSequence(text, 0, text.length());
14651         final SegmentFinder wordSegmentFinder = new WordSegmentFinder(text, wordIterator);
14652 
14653         // Create line SegmentFinder.
14654         final int lineCount = endLine - startLine + 1;
14655         final int[] lineRanges = new int[2 * lineCount];
14656         for (int line = startLine; line <= endLine; ++line) {
14657             final int offset = line - startLine;
14658             lineRanges[2 * offset] = layout.getLineStart(line);
14659             lineRanges[2 * offset + 1] = layout.getLineEnd(line);
14660         }
14661         final SegmentFinder lineSegmentFinder =
14662                 new SegmentFinder.PrescribedSegmentFinder(lineRanges);
14663 
14664         return new TextBoundsInfo.Builder(start, end)
14665                 .setMatrix(localToGlobalMatrix)
14666                 .setCharacterBounds(characterBounds)
14667                 .setCharacterBidiLevel(characterBidiLevels)
14668                 .setCharacterFlags(characterFlags)
14669                 .setGraphemeSegmentFinder(graphemeSegmentFinder)
14670                 .setLineSegmentFinder(lineSegmentFinder)
14671                 .setWordSegmentFinder(wordSegmentFinder)
14672                 .build();
14673     }
14674 
14675     /**
14676      * @hide
14677      */
14678     public boolean isPositionVisible(final float positionX, final float positionY) {
14679         synchronized (TEMP_POSITION) {
14680             final float[] position = TEMP_POSITION;
14681             position[0] = positionX;
14682             position[1] = positionY;
14683             View view = this;
14684 
14685             while (view != null) {
14686                 if (view != this) {
14687                     // Local scroll is already taken into account in positionX/Y
14688                     position[0] -= view.getScrollX();
14689                     position[1] -= view.getScrollY();
14690                 }
14691 
14692                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
14693                         || position[1] > view.getHeight()) {
14694                     return false;
14695                 }
14696 
14697                 if (!view.getMatrix().isIdentity()) {
14698                     view.getMatrix().mapPoints(position);
14699                 }
14700 
14701                 position[0] += view.getLeft();
14702                 position[1] += view.getTop();
14703 
14704                 final ViewParent parent = view.getParent();
14705                 if (parent instanceof View) {
14706                     view = (View) parent;
14707                 } else {
14708                     // We've reached the ViewRoot, stop iterating
14709                     view = null;
14710                 }
14711             }
14712         }
14713 
14714         // We've been able to walk up the view hierarchy and the position was never clipped
14715         return true;
14716     }
14717 
14718     /**
14719      * Performs an accessibility action after it has been offered to the
14720      * delegate.
14721      *
14722      * @hide
14723      */
14724     @Override
14725     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
14726         if (mEditor != null) {
14727             if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)
14728                     || mEditor.performSmartActionsAccessibilityAction(action)) {
14729                 return true;
14730             }
14731         }
14732         switch (action) {
14733             case AccessibilityNodeInfo.ACTION_CLICK: {
14734                 return performAccessibilityActionClick(arguments);
14735             }
14736             case AccessibilityNodeInfo.ACTION_COPY: {
14737                 if (isFocused() && canCopy()) {
14738                     if (onTextContextMenuItem(ID_COPY)) {
14739                         return true;
14740                     }
14741                 }
14742             } return false;
14743             case AccessibilityNodeInfo.ACTION_PASTE: {
14744                 if (isFocused() && canPaste()) {
14745                     if (onTextContextMenuItem(ID_PASTE)) {
14746                         return true;
14747                     }
14748                 }
14749             } return false;
14750             case AccessibilityNodeInfo.ACTION_CUT: {
14751                 if (isFocused() && canCut()) {
14752                     if (onTextContextMenuItem(ID_CUT)) {
14753                         return true;
14754                     }
14755                 }
14756             } return false;
14757             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
14758                 ensureIterableTextForAccessibilitySelectable();
14759                 CharSequence text = getIterableTextForAccessibility();
14760                 if (text == null) {
14761                     return false;
14762                 }
14763                 final int start = (arguments != null) ? arguments.getInt(
14764                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
14765                 final int end = (arguments != null) ? arguments.getInt(
14766                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
14767                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
14768                     // No arguments clears the selection.
14769                     if (start == end && end == -1) {
14770                         Selection.removeSelection((Spannable) text);
14771                         return true;
14772                     }
14773                     if (start >= 0 && start <= end && end <= text.length()) {
14774                         requestFocusOnNonEditableSelectableText();
14775                         Selection.setSelection((Spannable) text, start, end);
14776                         // Make sure selection mode is engaged.
14777                         if (mEditor != null) {
14778                             mEditor.startSelectionActionModeAsync(false);
14779                         }
14780                         return true;
14781                     }
14782                 }
14783             } return false;
14784             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
14785             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
14786                 ensureIterableTextForAccessibilitySelectable();
14787                 return super.performAccessibilityActionInternal(action, arguments);
14788             }
14789             case ACCESSIBILITY_ACTION_SHARE: {
14790                 if (isFocused() && canShare()) {
14791                     if (onTextContextMenuItem(ID_SHARE)) {
14792                         return true;
14793                     }
14794                 }
14795             } return false;
14796             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
14797                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
14798                     return false;
14799                 }
14800                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
14801                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
14802                 setText(text);
14803                 if (mText != null) {
14804                     int updatedTextLength = mText.length();
14805                     if (updatedTextLength > 0) {
14806                         Selection.setSelection(mSpannable, updatedTextLength);
14807                     }
14808                 }
14809             } return true;
14810             case R.id.accessibilityActionImeEnter: {
14811                 if (isFocused() && isTextEditable()) {
14812                     onEditorAction(getImeActionId());
14813                 }
14814             } return true;
14815             case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
14816                 if (isLongClickable()) {
14817                     boolean handled;
14818                     if (isEnabled() && (mBufferType == BufferType.EDITABLE)) {
14819                         mEditor.mIsBeingLongClickedByAccessibility = true;
14820                         try {
14821                             handled = performLongClick();
14822                         } finally {
14823                             mEditor.mIsBeingLongClickedByAccessibility = false;
14824                         }
14825                     } else {
14826                         handled = performLongClick();
14827                     }
14828                     return handled;
14829                 }
14830             }
14831             return false;
14832             default: {
14833                 // New ids have static blocks to assign values, so they can't be used in a case
14834                 // block.
14835                 if (action == R.id.accessibilityActionShowTextSuggestions) {
14836                     return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE);
14837                 }
14838                 return super.performAccessibilityActionInternal(action, arguments);
14839             }
14840         }
14841     }
14842 
14843     private boolean performAccessibilityActionClick(Bundle arguments) {
14844         boolean handled = false;
14845 
14846         if (!isEnabled()) {
14847             return false;
14848         }
14849 
14850         if (isClickable() || isLongClickable()) {
14851             // Simulate View.onTouchEvent for an ACTION_UP event
14852             if (isFocusable() && !isFocused()) {
14853                 requestFocus();
14854             }
14855 
14856             performClick();
14857             handled = true;
14858         }
14859 
14860         // Show the IME, except when selecting in read-only text.
14861         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
14862                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
14863             final InputMethodManager imm = getInputMethodManager();
14864             viewClicked(imm);
14865             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
14866                 handled |= imm.showSoftInput(this, 0);
14867             }
14868         }
14869 
14870         return handled;
14871     }
14872 
14873     private void requestFocusOnNonEditableSelectableText() {
14874         if (!isTextEditable() && isTextSelectable()) {
14875             if (!isEnabled()) {
14876                 return;
14877             }
14878 
14879             if (isFocusable() && !isFocused()) {
14880                 requestFocus();
14881             }
14882         }
14883     }
14884 
14885     private boolean hasSpannableText() {
14886         return mText != null && mText instanceof Spannable;
14887     }
14888 
14889     /** @hide */
14890     @Override
14891     public void sendAccessibilityEventInternal(int eventType) {
14892         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
14893             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
14894         }
14895 
14896         super.sendAccessibilityEventInternal(eventType);
14897     }
14898 
14899     @Override
14900     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
14901         // Do not send scroll events since first they are not interesting for
14902         // accessibility and second such events a generated too frequently.
14903         // For details see the implementation of bringTextIntoView().
14904         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
14905             return;
14906         }
14907         super.sendAccessibilityEventUnchecked(event);
14908     }
14909 
14910     /**
14911      * Returns the text that should be exposed to accessibility services.
14912      * <p>
14913      * This approximates what is displayed visually.
14914      *
14915      * @return the text that should be exposed to accessibility services, may
14916      *         be {@code null} if no text is set
14917      */
14918     @Nullable
14919     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
14920     private CharSequence getTextForAccessibility() {
14921         // If the text is empty, we must be showing the hint text.
14922         if (TextUtils.isEmpty(mText)) {
14923             return mHint;
14924         }
14925 
14926         // Otherwise, return whatever text is being displayed.
14927         return TextUtils.trimToParcelableSize(mTransformed);
14928     }
14929 
14930     boolean isVisibleToAccessibility() {
14931         return AccessibilityManager.getInstance(mContext).isEnabled()
14932                 && (isFocused() || (isSelected() && isShown()));
14933     }
14934 
14935     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
14936             int fromIndex, int removedCount, int addedCount) {
14937         AccessibilityEvent event =
14938                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
14939         event.setFromIndex(fromIndex);
14940         event.setRemovedCount(removedCount);
14941         event.setAddedCount(addedCount);
14942         event.setBeforeText(beforeText);
14943         sendAccessibilityEventUnchecked(event);
14944     }
14945 
14946     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
14947             int fromIndex, int toIndex) {
14948         AccessibilityEvent event =
14949                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
14950         event.setFromIndex(fromIndex);
14951         event.setToIndex(toIndex);
14952         event.setBeforeText(beforeText);
14953         sendAccessibilityEventUnchecked(event);
14954     }
14955 
14956     private InputMethodManager getInputMethodManager() {
14957         return getContext().getSystemService(InputMethodManager.class);
14958     }
14959 
14960     /**
14961      * Returns whether this text view is a current input method target.  The
14962      * default implementation just checks with {@link InputMethodManager}.
14963      * @return True if the TextView is a current input method target; false otherwise.
14964      */
14965     public boolean isInputMethodTarget() {
14966         InputMethodManager imm = getInputMethodManager();
14967         return imm != null && imm.isActive(this);
14968     }
14969 
14970     static final int ID_SELECT_ALL = android.R.id.selectAll;
14971     static final int ID_UNDO = android.R.id.undo;
14972     static final int ID_REDO = android.R.id.redo;
14973     static final int ID_CUT = android.R.id.cut;
14974     static final int ID_COPY = android.R.id.copy;
14975     static final int ID_PASTE = android.R.id.paste;
14976     static final int ID_SHARE = android.R.id.shareText;
14977     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
14978     static final int ID_REPLACE = android.R.id.replaceText;
14979     static final int ID_ASSIST = android.R.id.textAssist;
14980     static final int ID_AUTOFILL = android.R.id.autofill;
14981 
14982     /**
14983      * Called when a context menu option for the text view is selected.  Currently
14984      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
14985      * {@link android.R.id#copy}, {@link android.R.id#paste},
14986      * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or
14987      * {@link android.R.id#shareText}.
14988      *
14989      * @return true if the context menu item action was performed.
14990      */
14991     public boolean onTextContextMenuItem(int id) {
14992         int min = 0;
14993         int max = mText.length();
14994 
14995         if (isFocused()) {
14996             final int selStart = getSelectionStart();
14997             final int selEnd = getSelectionEnd();
14998 
14999             min = Math.max(0, Math.min(selStart, selEnd));
15000             max = Math.max(0, Math.max(selStart, selEnd));
15001         }
15002 
15003         switch (id) {
15004             case ID_SELECT_ALL:
15005                 final boolean hadSelection = hasSelection();
15006                 selectAllText();
15007                 if (mEditor != null && hadSelection) {
15008                     mEditor.invalidateActionModeAsync();
15009                 }
15010                 return true;
15011 
15012             case ID_UNDO:
15013                 if (mEditor != null) {
15014                     mEditor.undo();
15015                 }
15016                 return true;  // Returns true even if nothing was undone.
15017 
15018             case ID_REDO:
15019                 if (mEditor != null) {
15020                     mEditor.redo();
15021                 }
15022                 return true;  // Returns true even if nothing was undone.
15023 
15024             case ID_PASTE:
15025                 paste(true /* withFormatting */);
15026                 return true;
15027 
15028             case ID_PASTE_AS_PLAIN_TEXT:
15029                 paste(false /* withFormatting */);
15030                 return true;
15031 
15032             case ID_CUT:
15033                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
15034                 if (setPrimaryClip(cutData)) {
15035                     deleteText_internal(min, max);
15036                 } else {
15037                     Toast.makeText(getContext(),
15038                             com.android.internal.R.string.failed_to_copy_to_clipboard,
15039                             Toast.LENGTH_SHORT).show();
15040                 }
15041                 return true;
15042 
15043             case ID_COPY:
15044                 // For link action mode in a non-selectable/non-focusable TextView,
15045                 // make sure that we set the appropriate min/max.
15046                 final int selStart = getSelectionStart();
15047                 final int selEnd = getSelectionEnd();
15048                 min = Math.max(0, Math.min(selStart, selEnd));
15049                 max = Math.max(0, Math.max(selStart, selEnd));
15050                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
15051                 if (setPrimaryClip(copyData)) {
15052                     stopTextActionMode();
15053                 } else {
15054                     Toast.makeText(getContext(),
15055                             com.android.internal.R.string.failed_to_copy_to_clipboard,
15056                             Toast.LENGTH_SHORT).show();
15057                 }
15058                 return true;
15059 
15060             case ID_REPLACE:
15061                 if (mEditor != null) {
15062                     mEditor.replace();
15063                 }
15064                 return true;
15065 
15066             case ID_SHARE:
15067                 shareSelectedText();
15068                 return true;
15069 
15070             case ID_AUTOFILL:
15071                 requestAutofill();
15072                 stopTextActionMode();
15073                 return true;
15074         }
15075         return false;
15076     }
15077 
15078     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
15079     CharSequence getTransformedText(int start, int end) {
15080         return removeSuggestionSpans(mTransformed.subSequence(start, end));
15081     }
15082 
15083     @Override
15084     public boolean performLongClick() {
15085         if (DEBUG_CURSOR) {
15086             logCursor("performLongClick", null);
15087         }
15088 
15089         boolean handled = false;
15090         boolean performedHapticFeedback = false;
15091 
15092         if (mEditor != null) {
15093             mEditor.mIsBeingLongClicked = true;
15094         }
15095 
15096         if (super.performLongClick()) {
15097             handled = true;
15098             performedHapticFeedback = true;
15099         }
15100 
15101         if (mEditor != null) {
15102             handled |= mEditor.performLongClick(handled);
15103             mEditor.mIsBeingLongClicked = false;
15104         }
15105 
15106         if (handled) {
15107             if (!performedHapticFeedback) {
15108               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
15109             }
15110             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
15111         } else {
15112             MetricsLogger.action(
15113                     mContext,
15114                     MetricsEvent.TEXT_LONGPRESS,
15115                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
15116         }
15117 
15118         return handled;
15119     }
15120 
15121     @Override
15122     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
15123         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
15124         if (mEditor != null) {
15125             mEditor.onScrollChanged();
15126         }
15127     }
15128 
15129     /**
15130      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
15131      * by the IME or by the spell checker as the user types. This is done by adding
15132      * {@link SuggestionSpan}s to the text.
15133      *
15134      * When suggestions are enabled (default), this list of suggestions will be displayed when the
15135      * user asks for them on these parts of the text. This value depends on the inputType of this
15136      * TextView.
15137      *
15138      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
15139      *
15140      * In addition, the type variation must be one of
15141      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
15142      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
15143      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
15144      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
15145      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
15146      *
15147      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
15148      *
15149      * @return true if the suggestions popup window is enabled, based on the inputType.
15150      */
15151     public boolean isSuggestionsEnabled() {
15152         if (mEditor == null) return false;
15153         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
15154             return false;
15155         }
15156         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
15157 
15158         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
15159         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
15160                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
15161                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
15162                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
15163                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
15164     }
15165 
15166     /**
15167      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
15168      * selection is initiated in this View.
15169      *
15170      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
15171      * Paste, Replace and Share actions, depending on what this View supports.
15172      *
15173      * <p>A custom implementation can add new entries in the default menu in its
15174      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
15175      * method. The default actions can also be removed from the menu using
15176      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
15177      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
15178      * {@link android.R.id#pasteAsPlainText} (starting at API level 23),
15179      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
15180      *
15181      * <p>Returning false from
15182      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
15183      * will prevent the action mode from being started.
15184      *
15185      * <p>Action click events should be handled by the custom implementation of
15186      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
15187      * android.view.MenuItem)}.
15188      *
15189      * <p>Note that text selection mode is not started when a TextView receives focus and the
15190      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
15191      * that case, to allow for quick replacement.
15192      */
15193     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
15194         createEditorIfNeeded();
15195         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
15196     }
15197 
15198     /**
15199      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
15200      *
15201      * @return The current custom selection callback.
15202      */
15203     public ActionMode.Callback getCustomSelectionActionModeCallback() {
15204         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
15205     }
15206 
15207     /**
15208      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
15209      * insertion is initiated in this View.
15210      * The standard implementation populates the menu with a subset of Select All,
15211      * Paste and Replace actions, depending on what this View supports.
15212      *
15213      * <p>A custom implementation can add new entries in the default menu in its
15214      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
15215      * android.view.Menu)} method. The default actions can also be removed from the menu using
15216      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
15217      * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API
15218      * level 23) or {@link android.R.id#replaceText} ids as parameters.</p>
15219      *
15220      * <p>Returning false from
15221      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
15222      * android.view.Menu)} will prevent the action mode from being started.</p>
15223      *
15224      * <p>Action click events should be handled by the custom implementation of
15225      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
15226      * android.view.MenuItem)}.</p>
15227      *
15228      * <p>Note that text insertion mode is not started when a TextView receives focus and the
15229      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
15230      */
15231     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
15232         createEditorIfNeeded();
15233         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
15234     }
15235 
15236     /**
15237      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
15238      *
15239      * @return The current custom insertion callback.
15240      */
15241     public ActionMode.Callback getCustomInsertionActionModeCallback() {
15242         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
15243     }
15244 
15245     /**
15246      * Sets the {@link TextClassifier} for this TextView.
15247      */
15248     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
15249         mTextClassifier = textClassifier;
15250     }
15251 
15252     /**
15253      * Returns the {@link TextClassifier} used by this TextView.
15254      * If no TextClassifier has been set, this TextView uses the default set by the
15255      * {@link TextClassificationManager}.
15256      */
15257     @NonNull
15258     public TextClassifier getTextClassifier() {
15259         if (mTextClassifier == null) {
15260             final TextClassificationManager tcm = getTextClassificationManagerForUser();
15261             if (tcm != null) {
15262                 return tcm.getTextClassifier();
15263             }
15264             return TextClassifier.NO_OP;
15265         }
15266         return mTextClassifier;
15267     }
15268 
15269     /**
15270      * Returns a session-aware text classifier.
15271      * This method creates one if none already exists or the current one is destroyed.
15272      */
15273     @NonNull
15274     TextClassifier getTextClassificationSession() {
15275         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
15276             final TextClassificationManager tcm = getTextClassificationManagerForUser();
15277             if (tcm != null) {
15278                 final String widgetType;
15279                 if (isTextEditable()) {
15280                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
15281                 } else if (isTextSelectable()) {
15282                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
15283                 } else {
15284                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
15285                 }
15286                 mTextClassificationContext = new TextClassificationContext.Builder(
15287                         mContext.getPackageName(), widgetType)
15288                         .build();
15289                 if (mTextClassifier != null) {
15290                     mTextClassificationSession = tcm.createTextClassificationSession(
15291                             mTextClassificationContext, mTextClassifier);
15292                 } else {
15293                     mTextClassificationSession = tcm.createTextClassificationSession(
15294                             mTextClassificationContext);
15295                 }
15296             } else {
15297                 mTextClassificationSession = TextClassifier.NO_OP;
15298             }
15299         }
15300         return mTextClassificationSession;
15301     }
15302 
15303     /**
15304      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
15305      * @see #getTextClassificationSession()
15306      */
15307     @Nullable
15308     TextClassificationContext getTextClassificationContext() {
15309         return mTextClassificationContext;
15310     }
15311 
15312     /**
15313      * Returns true if this TextView uses a no-op TextClassifier.
15314      */
15315     boolean usesNoOpTextClassifier() {
15316         return getTextClassifier() == TextClassifier.NO_OP;
15317     }
15318 
15319     /**
15320      * Starts an ActionMode for the specified TextLinkSpan.
15321      *
15322      * @return Whether or not we're attempting to start the action mode.
15323      * @hide
15324      */
15325     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
15326         Preconditions.checkNotNull(clickedSpan);
15327 
15328         if (!(mText instanceof Spanned)) {
15329             return false;
15330         }
15331 
15332         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
15333         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
15334 
15335         if (start < 0 || end > mText.length() || start >= end) {
15336             return false;
15337         }
15338 
15339         createEditorIfNeeded();
15340         mEditor.startLinkActionModeAsync(start, end);
15341         return true;
15342     }
15343 
15344     /**
15345      * Handles a click on the specified TextLinkSpan.
15346      *
15347      * @return Whether or not the click is being handled.
15348      * @hide
15349      */
15350     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
15351         Preconditions.checkNotNull(clickedSpan);
15352         if (mText instanceof Spanned) {
15353             final Spanned spanned = (Spanned) mText;
15354             final int start = spanned.getSpanStart(clickedSpan);
15355             final int end = spanned.getSpanEnd(clickedSpan);
15356             if (start >= 0 && end <= mText.length() && start < end) {
15357                 final TextClassification.Request request = new TextClassification.Request.Builder(
15358                         mText, start, end)
15359                         .setDefaultLocales(getTextLocales())
15360                         .build();
15361                 final Supplier<TextClassification> supplier = () ->
15362                         getTextClassificationSession().classifyText(request);
15363                 final Consumer<TextClassification> consumer = classification -> {
15364                     if (classification != null) {
15365                         if (!classification.getActions().isEmpty()) {
15366                             try {
15367                                 classification.getActions().get(0).getActionIntent().send();
15368                             } catch (PendingIntent.CanceledException e) {
15369                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
15370                             }
15371                         } else {
15372                             Log.d(LOG_TAG, "No link action to perform");
15373                         }
15374                     } else {
15375                         // classification == null
15376                         Log.d(LOG_TAG, "Timeout while classifying text");
15377                     }
15378                 };
15379                 CompletableFuture.supplyAsync(supplier)
15380                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
15381                         .thenAccept(consumer);
15382                 return true;
15383             }
15384         }
15385         return false;
15386     }
15387 
15388     /**
15389      * @hide
15390      */
15391     @UnsupportedAppUsage
15392     protected void stopTextActionMode() {
15393         if (mEditor != null) {
15394             mEditor.stopTextActionMode();
15395         }
15396     }
15397 
15398     /** @hide */
15399     public void hideFloatingToolbar(int durationMs) {
15400         if (mEditor != null) {
15401             mEditor.hideFloatingToolbar(durationMs);
15402         }
15403     }
15404 
15405     boolean canUndo() {
15406         return mEditor != null && mEditor.canUndo();
15407     }
15408 
15409     boolean canRedo() {
15410         return mEditor != null && mEditor.canRedo();
15411     }
15412 
15413     boolean canCut() {
15414         if (hasPasswordTransformationMethod()) {
15415             return false;
15416         }
15417 
15418         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
15419                 && mEditor.mKeyListener != null) {
15420             return true;
15421         }
15422 
15423         return false;
15424     }
15425 
15426     boolean canCopy() {
15427         if (hasPasswordTransformationMethod()) {
15428             return false;
15429         }
15430 
15431         if (mText.length() > 0 && hasSelection() && mEditor != null) {
15432             return true;
15433         }
15434 
15435         return false;
15436     }
15437 
15438     boolean canReplace() {
15439         if (hasPasswordTransformationMethod()) {
15440             return false;
15441         }
15442 
15443         return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null)
15444                 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions();
15445     }
15446 
15447     boolean canShare() {
15448         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()
15449                 || !getContext().getResources().getBoolean(
15450                 com.android.internal.R.bool.config_textShareSupported)) {
15451             return false;
15452         }
15453         return canCopy();
15454     }
15455 
15456     boolean isDeviceProvisioned() {
15457         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
15458             mDeviceProvisionedState = Settings.Global.getInt(
15459                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
15460                     ? DEVICE_PROVISIONED_YES
15461                     : DEVICE_PROVISIONED_NO;
15462         }
15463         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
15464     }
15465 
15466     @UnsupportedAppUsage
15467     boolean canPaste() {
15468         return (mText instanceof Editable
15469                 && mEditor != null && mEditor.mKeyListener != null
15470                 && getSelectionStart() >= 0
15471                 && getSelectionEnd() >= 0
15472                 && getClipboardManagerForUser().hasPrimaryClip());
15473     }
15474 
15475     boolean canPasteAsPlainText() {
15476         if (!canPaste()) {
15477             return false;
15478         }
15479 
15480         final ClipDescription description =
15481                 getClipboardManagerForUser().getPrimaryClipDescription();
15482         if (description == null) {
15483             return false;
15484         }
15485         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
15486         return (isPlainType && description.isStyledText())
15487                 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
15488     }
15489 
15490     boolean canProcessText() {
15491         if (getId() == View.NO_ID) {
15492             return false;
15493         }
15494         return canShare();
15495     }
15496 
15497     boolean canSelectAllText() {
15498         return canSelectText() && !hasPasswordTransformationMethod()
15499                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
15500     }
15501 
15502     boolean selectAllText() {
15503         if (mEditor != null) {
15504             // Hide the toolbar before changing the selection to avoid flickering.
15505             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
15506         }
15507         final int length = mText.length();
15508         Selection.setSelection(mSpannable, 0, length);
15509         return length > 0;
15510     }
15511 
15512     private void paste(boolean withFormatting) {
15513         ClipboardManager clipboard = getClipboardManagerForUser();
15514         ClipData clip = clipboard.getPrimaryClip();
15515         if (clip == null) {
15516             return;
15517         }
15518         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD)
15519                 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT)
15520                 .build();
15521         performReceiveContent(payload);
15522         sLastCutCopyOrTextChangedTime = 0;
15523     }
15524 
15525     private void shareSelectedText() {
15526         String selectedText = getSelectedText();
15527         if (selectedText != null && !selectedText.isEmpty()) {
15528             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
15529             sharingIntent.setType("text/plain");
15530             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
15531             selectedText = TextUtils.trimToParcelableSize(selectedText);
15532             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
15533             getContext().startActivity(Intent.createChooser(sharingIntent, null));
15534             Selection.setSelection(mSpannable, getSelectionEnd());
15535         }
15536     }
15537 
15538     @CheckResult
15539     private boolean setPrimaryClip(ClipData clip) {
15540         ClipboardManager clipboard = getClipboardManagerForUser();
15541         try {
15542             clipboard.setPrimaryClip(clip);
15543         } catch (Throwable t) {
15544             return false;
15545         }
15546         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
15547         return true;
15548     }
15549 
15550     /**
15551      * Get the character offset closest to the specified absolute position. A typical use case is to
15552      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
15553      *
15554      * @param x The horizontal absolute position of a point on screen
15555      * @param y The vertical absolute position of a point on screen
15556      * @return the character offset for the character whose position is closest to the specified
15557      *  position. Returns -1 if there is no layout.
15558      */
15559     public int getOffsetForPosition(float x, float y) {
15560         if (getLayout() == null) return -1;
15561         final int line = getLineAtCoordinate(y);
15562         final int offset = getOffsetAtCoordinate(line, x);
15563         return offset;
15564     }
15565 
15566     float convertToLocalHorizontalCoordinate(float x) {
15567         x -= getTotalPaddingLeft();
15568         // Clamp the position to inside of the view.
15569         x = Math.max(0.0f, x);
15570         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
15571         x += getScrollX();
15572         return x;
15573     }
15574 
15575     /** @hide */
15576     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
15577     public int getLineAtCoordinate(float y) {
15578         y -= getTotalPaddingTop();
15579         // Clamp the position to inside of the view.
15580         y = Math.max(0.0f, y);
15581         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
15582         y += getScrollY();
15583         return getLayout().getLineForVertical((int) y);
15584     }
15585 
15586     int getLineAtCoordinateUnclamped(float y) {
15587         y -= getTotalPaddingTop();
15588         y += getScrollY();
15589         return getLayout().getLineForVertical((int) y);
15590     }
15591 
15592     int getOffsetAtCoordinate(int line, float x) {
15593         x = convertToLocalHorizontalCoordinate(x);
15594         final int offset = getLayout().getOffsetForHorizontal(line, x);
15595         return transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
15596     }
15597 
15598     /**
15599      * Convenient method to convert an offset on the transformed text to the original text.
15600      * @hide
15601      */
15602     public int transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy) {
15603         if (getTransformationMethod() == null) {
15604             return offset;
15605         }
15606         if (mTransformed instanceof OffsetMapping) {
15607             final OffsetMapping transformedText = (OffsetMapping) mTransformed;
15608             return transformedText.transformedToOriginal(offset, strategy);
15609         }
15610         return offset;
15611     }
15612 
15613     /**
15614      * Convenient method to convert an offset on the original text to the transformed text.
15615      * @hide
15616      */
15617     public int originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy) {
15618         if (getTransformationMethod() == null) {
15619             return offset;
15620         }
15621         if (mTransformed instanceof OffsetMapping) {
15622             final OffsetMapping transformedText = (OffsetMapping) mTransformed;
15623             return transformedText.originalToTransformed(offset, strategy);
15624         }
15625         return offset;
15626     }
15627     /**
15628      * Handles drag events sent by the system following a call to
15629      * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
15630      * startDragAndDrop()}.
15631      *
15632      * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent}
15633      * implementation.
15634      *
15635      * <p>If this text view is editable, accepts all drag actions (returns true for an
15636      * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all
15637      * subsequent drag events). While the drag is in progress, updates the cursor position
15638      * to follow the touch location. Once a drop event is received, handles content insertion
15639      * via {@link #performReceiveContent}.
15640      *
15641      * @param event The {@link android.view.DragEvent} sent by the system.
15642      * The {@link android.view.DragEvent#getAction()} method returns an action type constant
15643      * defined in DragEvent, indicating the type of drag event represented by this object.
15644      * @return Returns true if this text view is editable and delegates to super otherwise.
15645      * See {@link View#onDragEvent}.
15646      */
15647     @Override
15648     public boolean onDragEvent(DragEvent event) {
15649         if (mEditor == null || !mEditor.hasInsertionController()) {
15650             // If this TextView is not editable, defer to the default View implementation. This
15651             // will check for the presence of an OnReceiveContentListener and accept/reject
15652             // drag events depending on whether the listener is/isn't set.
15653             return super.onDragEvent(event);
15654         }
15655         switch (event.getAction()) {
15656             case DragEvent.ACTION_DRAG_STARTED:
15657                 return true;
15658 
15659             case DragEvent.ACTION_DRAG_ENTERED:
15660                 TextView.this.requestFocus();
15661                 return true;
15662 
15663             case DragEvent.ACTION_DRAG_LOCATION:
15664                 if (mText instanceof Spannable) {
15665                     final int offset = getOffsetForPosition(event.getX(), event.getY());
15666                     Selection.setSelection(mSpannable, offset);
15667                 }
15668                 return true;
15669 
15670             case DragEvent.ACTION_DROP:
15671                 if (mEditor != null) mEditor.onDrop(event);
15672                 return true;
15673 
15674             case DragEvent.ACTION_DRAG_ENDED:
15675             case DragEvent.ACTION_DRAG_EXITED:
15676             default:
15677                 return true;
15678         }
15679     }
15680 
15681     boolean isInBatchEditMode() {
15682         if (mEditor == null) return false;
15683         final Editor.InputMethodState ims = mEditor.mInputMethodState;
15684         if (ims != null) {
15685             return ims.mBatchEditNesting > 0;
15686         }
15687         return mEditor.mInBatchEditControllers;
15688     }
15689 
15690     @Override
15691     public void onRtlPropertiesChanged(int layoutDirection) {
15692         super.onRtlPropertiesChanged(layoutDirection);
15693 
15694         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
15695         if (mTextDir != newTextDir) {
15696             mTextDir = newTextDir;
15697             if (mLayout != null) {
15698                 checkForRelayout();
15699             }
15700         }
15701     }
15702 
15703     /**
15704      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
15705      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
15706      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
15707      * return value may not be the same as the one TextView uses if the View's layout direction is
15708      * not resolved or detached from parent root view.
15709      */
15710     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
15711         if (hasPasswordTransformationMethod()) {
15712             // passwords fields should be LTR
15713             return TextDirectionHeuristics.LTR;
15714         }
15715 
15716         if (mEditor != null
15717                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
15718                     == EditorInfo.TYPE_CLASS_PHONE) {
15719             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
15720             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
15721             // RTL digits.
15722             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
15723             final String zero = symbols.getDigitStrings()[0];
15724             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
15725             // direction.
15726             final int firstCodepoint = zero.codePointAt(0);
15727             final byte digitDirection = Character.getDirectionality(firstCodepoint);
15728             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
15729                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
15730                 return TextDirectionHeuristics.RTL;
15731             } else {
15732                 return TextDirectionHeuristics.LTR;
15733             }
15734         }
15735 
15736         // Always need to resolve layout direction first
15737         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
15738 
15739         // Now, we can select the heuristic
15740         switch (getTextDirection()) {
15741             default:
15742             case TEXT_DIRECTION_FIRST_STRONG:
15743                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
15744                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
15745             case TEXT_DIRECTION_ANY_RTL:
15746                 return TextDirectionHeuristics.ANYRTL_LTR;
15747             case TEXT_DIRECTION_LTR:
15748                 return TextDirectionHeuristics.LTR;
15749             case TEXT_DIRECTION_RTL:
15750                 return TextDirectionHeuristics.RTL;
15751             case TEXT_DIRECTION_LOCALE:
15752                 return TextDirectionHeuristics.LOCALE;
15753             case TEXT_DIRECTION_FIRST_STRONG_LTR:
15754                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
15755             case TEXT_DIRECTION_FIRST_STRONG_RTL:
15756                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
15757         }
15758     }
15759 
15760     /**
15761      * @hide
15762      */
15763     @Override
15764     public void onResolveDrawables(int layoutDirection) {
15765         // No need to resolve twice
15766         if (mLastLayoutDirection == layoutDirection) {
15767             return;
15768         }
15769         mLastLayoutDirection = layoutDirection;
15770 
15771         // Resolve drawables
15772         if (mDrawables != null) {
15773             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
15774                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
15775                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
15776                 applyCompoundDrawableTint();
15777             }
15778         }
15779     }
15780 
15781     /**
15782      * Prepares a drawable for display by propagating layout direction and
15783      * drawable state.
15784      *
15785      * @param dr the drawable to prepare
15786      */
15787     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
15788         if (dr == null) {
15789             return;
15790         }
15791 
15792         dr.setLayoutDirection(getLayoutDirection());
15793 
15794         if (dr.isStateful()) {
15795             dr.setState(getDrawableState());
15796             dr.jumpToCurrentState();
15797         }
15798     }
15799 
15800     /**
15801      * @hide
15802      */
15803     protected void resetResolvedDrawables() {
15804         super.resetResolvedDrawables();
15805         mLastLayoutDirection = -1;
15806     }
15807 
15808     /**
15809      * @hide
15810      */
15811     protected void viewClicked(InputMethodManager imm) {
15812         if (imm != null) {
15813             imm.viewClicked(this);
15814         }
15815     }
15816 
15817     /**
15818      * Deletes the range of text [start, end[.
15819      * @hide
15820      */
15821     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
15822     protected void deleteText_internal(int start, int end) {
15823         ((Editable) mText).delete(start, end);
15824     }
15825 
15826     /**
15827      * Replaces the range of text [start, end[ by replacement text
15828      * @hide
15829      */
15830     protected void replaceText_internal(int start, int end, CharSequence text) {
15831         ((Editable) mText).replace(start, end, text);
15832     }
15833 
15834     /**
15835      * Sets a span on the specified range of text
15836      * @hide
15837      */
15838     protected void setSpan_internal(Object span, int start, int end, int flags) {
15839         ((Editable) mText).setSpan(span, start, end, flags);
15840     }
15841 
15842     /**
15843      * Moves the cursor to the specified offset position in text
15844      * @hide
15845      */
15846     protected void setCursorPosition_internal(int start, int end) {
15847         Selection.setSelection(((Editable) mText), start, end);
15848     }
15849 
15850     /**
15851      * An Editor should be created as soon as any of the editable-specific fields (grouped
15852      * inside the Editor object) is assigned to a non-default value.
15853      * This method will create the Editor if needed.
15854      *
15855      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
15856      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
15857      * Editor for backward compatibility, as soon as one of these fields is assigned.
15858      *
15859      * Also note that for performance reasons, the mEditor is created when needed, but not
15860      * reset when no more edit-specific fields are needed.
15861      */
15862     @UnsupportedAppUsage
15863     private void createEditorIfNeeded() {
15864         if (mEditor == null) {
15865             mEditor = new Editor(this);
15866         }
15867     }
15868 
15869     /**
15870      * @hide
15871      */
15872     @Override
15873     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
15874     public CharSequence getIterableTextForAccessibility() {
15875         return mText;
15876     }
15877 
15878     private void ensureIterableTextForAccessibilitySelectable() {
15879         if (!(mText instanceof Spannable)) {
15880             setText(mText, BufferType.SPANNABLE);
15881             if (getLayout() == null) {
15882                 assumeLayout();
15883             }
15884         }
15885     }
15886 
15887     /**
15888      * @hide
15889      */
15890     @Override
15891     public TextSegmentIterator getIteratorForGranularity(int granularity) {
15892         switch (granularity) {
15893             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
15894                 Spannable text = (Spannable) getIterableTextForAccessibility();
15895                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
15896                     AccessibilityIterators.LineTextSegmentIterator iterator =
15897                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
15898                     iterator.initialize(text, getLayout());
15899                     return iterator;
15900                 }
15901             } break;
15902             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
15903                 Spannable text = (Spannable) getIterableTextForAccessibility();
15904                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
15905                     AccessibilityIterators.PageTextSegmentIterator iterator =
15906                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
15907                     iterator.initialize(this);
15908                     return iterator;
15909                 }
15910             } break;
15911         }
15912         return super.getIteratorForGranularity(granularity);
15913     }
15914 
15915     /**
15916      * @hide
15917      */
15918     @Override
15919     public int getAccessibilitySelectionStart() {
15920         return getSelectionStart();
15921     }
15922 
15923     /**
15924      * @hide
15925      */
15926     public boolean isAccessibilitySelectionExtendable() {
15927         return true;
15928     }
15929 
15930     /**
15931      * @hide
15932      */
15933     public void prepareForExtendedAccessibilitySelection() {
15934         requestFocusOnNonEditableSelectableText();
15935     }
15936 
15937     /**
15938      * @hide
15939      */
15940     @Override
15941     public int getAccessibilitySelectionEnd() {
15942         return getSelectionEnd();
15943     }
15944 
15945     /**
15946      * @hide
15947      */
15948     @Override
15949     public void setAccessibilitySelection(int start, int end) {
15950         if (getAccessibilitySelectionStart() == start
15951                 && getAccessibilitySelectionEnd() == end) {
15952             return;
15953         }
15954         CharSequence text = getIterableTextForAccessibility();
15955         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
15956             Selection.setSelection((Spannable) text, start, end);
15957         } else {
15958             Selection.removeSelection((Spannable) text);
15959         }
15960         // Hide all selection controllers used for adjusting selection
15961         // since we are doing so explicitlty by other means and these
15962         // controllers interact with how selection behaves.
15963         if (mEditor != null) {
15964             mEditor.hideCursorAndSpanControllers();
15965             mEditor.stopTextActionMode();
15966         }
15967     }
15968 
15969     /** @hide */
15970     @Override
15971     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
15972         super.encodeProperties(stream);
15973 
15974         TruncateAt ellipsize = getEllipsize();
15975         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
15976         stream.addProperty("text:textSize", getTextSize());
15977         stream.addProperty("text:scaledTextSize", getScaledTextSize());
15978         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
15979         stream.addProperty("text:selectionStart", getSelectionStart());
15980         stream.addProperty("text:selectionEnd", getSelectionEnd());
15981         stream.addProperty("text:curTextColor", mCurTextColor);
15982         stream.addUserProperty("text:text", mText == null ? null : mText.toString());
15983         stream.addProperty("text:gravity", mGravity);
15984     }
15985 
15986     /**
15987      * User interface state that is stored by TextView for implementing
15988      * {@link View#onSaveInstanceState}.
15989      */
15990     public static class SavedState extends BaseSavedState {
15991         int selStart = -1;
15992         int selEnd = -1;
15993         @UnsupportedAppUsage
15994         CharSequence text;
15995         boolean frozenWithFocus;
15996         CharSequence error;
15997         ParcelableParcel editorState;  // Optional state from Editor.
15998 
15999         SavedState(Parcelable superState) {
16000             super(superState);
16001         }
16002 
16003         @Override
16004         public void writeToParcel(Parcel out, int flags) {
16005             super.writeToParcel(out, flags);
16006             out.writeInt(selStart);
16007             out.writeInt(selEnd);
16008             out.writeInt(frozenWithFocus ? 1 : 0);
16009             TextUtils.writeToParcel(text, out, flags);
16010 
16011             if (error == null) {
16012                 out.writeInt(0);
16013             } else {
16014                 out.writeInt(1);
16015                 TextUtils.writeToParcel(error, out, flags);
16016             }
16017 
16018             if (editorState == null) {
16019                 out.writeInt(0);
16020             } else {
16021                 out.writeInt(1);
16022                 editorState.writeToParcel(out, flags);
16023             }
16024         }
16025 
16026         @Override
16027         public String toString() {
16028             String str = "TextView.SavedState{"
16029                     + Integer.toHexString(System.identityHashCode(this))
16030                     + " start=" + selStart + " end=" + selEnd;
16031             if (text != null) {
16032                 str += " text=" + text;
16033             }
16034             return str + "}";
16035         }
16036 
16037         @SuppressWarnings("hiding")
16038         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
16039                 new Parcelable.Creator<SavedState>() {
16040                     public SavedState createFromParcel(Parcel in) {
16041                         return new SavedState(in);
16042                     }
16043 
16044                     public SavedState[] newArray(int size) {
16045                         return new SavedState[size];
16046                     }
16047                 };
16048 
16049         private SavedState(Parcel in) {
16050             super(in);
16051             selStart = in.readInt();
16052             selEnd = in.readInt();
16053             frozenWithFocus = (in.readInt() != 0);
16054             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
16055 
16056             if (in.readInt() != 0) {
16057                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
16058             }
16059 
16060             if (in.readInt() != 0) {
16061                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
16062             }
16063         }
16064     }
16065 
16066     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
16067         @NonNull
16068         private char[] mChars;
16069         private int mStart, mLength;
16070 
16071         CharWrapper(@NonNull char[] chars, int start, int len) {
16072             mChars = chars;
16073             mStart = start;
16074             mLength = len;
16075         }
16076 
16077         /* package */ void set(@NonNull char[] chars, int start, int len) {
16078             mChars = chars;
16079             mStart = start;
16080             mLength = len;
16081         }
16082 
16083         public int length() {
16084             return mLength;
16085         }
16086 
16087         public char charAt(int off) {
16088             return mChars[off + mStart];
16089         }
16090 
16091         @Override
16092         public String toString() {
16093             return new String(mChars, mStart, mLength);
16094         }
16095 
16096         public CharSequence subSequence(int start, int end) {
16097             if (start < 0 || end < 0 || start > mLength || end > mLength) {
16098                 throw new IndexOutOfBoundsException(start + ", " + end);
16099             }
16100 
16101             return new String(mChars, start + mStart, end - start);
16102         }
16103 
16104         public void getChars(int start, int end, char[] buf, int off) {
16105             if (start < 0 || end < 0 || start > mLength || end > mLength) {
16106                 throw new IndexOutOfBoundsException(start + ", " + end);
16107             }
16108 
16109             System.arraycopy(mChars, start + mStart, buf, off, end - start);
16110         }
16111 
16112         @Override
16113         public void drawText(BaseCanvas c, int start, int end,
16114                              float x, float y, Paint p) {
16115             c.drawText(mChars, start + mStart, end - start, x, y, p);
16116         }
16117 
16118         @Override
16119         public void drawTextRun(BaseCanvas c, int start, int end,
16120                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
16121             int count = end - start;
16122             int contextCount = contextEnd - contextStart;
16123             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
16124                     contextCount, x, y, isRtl, p);
16125         }
16126 
16127         public float measureText(int start, int end, Paint p) {
16128             return p.measureText(mChars, start + mStart, end - start);
16129         }
16130 
16131         public int getTextWidths(int start, int end, float[] widths, Paint p) {
16132             return p.getTextWidths(mChars, start + mStart, end - start, widths);
16133         }
16134 
16135         public float getTextRunAdvances(int start, int end, int contextStart,
16136                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
16137                 Paint p) {
16138             int count = end - start;
16139             int contextCount = contextEnd - contextStart;
16140             return p.getTextRunAdvances(mChars, start + mStart, count,
16141                     contextStart + mStart, contextCount, isRtl, advances,
16142                     advancesIndex);
16143         }
16144 
16145         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
16146                 int offset, int cursorOpt, Paint p) {
16147             int contextCount = contextEnd - contextStart;
16148             return p.getTextRunCursor(mChars, contextStart + mStart,
16149                     contextCount, isRtl, offset + mStart, cursorOpt);
16150         }
16151     }
16152 
16153     private static final class Marquee {
16154         // TODO: Add an option to configure this
16155         private static final float MARQUEE_DELTA_MAX = 0.07f;
16156         private static final int MARQUEE_DELAY = 1200;
16157         private static final int MARQUEE_DP_PER_SECOND = 30;
16158 
16159         private static final byte MARQUEE_STOPPED = 0x0;
16160         private static final byte MARQUEE_STARTING = 0x1;
16161         private static final byte MARQUEE_RUNNING = 0x2;
16162 
16163         private final WeakReference<TextView> mView;
16164         private final Choreographer mChoreographer;
16165 
16166         private byte mStatus = MARQUEE_STOPPED;
16167         private final float mPixelsPerMs;
16168         private float mMaxScroll;
16169         private float mMaxFadeScroll;
16170         private float mGhostStart;
16171         private float mGhostOffset;
16172         private float mFadeStop;
16173         private int mRepeatLimit;
16174 
16175         private float mScroll;
16176         private long mLastAnimationMs;
16177 
16178         Marquee(TextView v) {
16179             final float density = v.getContext().getResources().getDisplayMetrics().density;
16180             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
16181             mView = new WeakReference<TextView>(v);
16182             mChoreographer = Choreographer.getInstance();
16183         }
16184 
16185         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
16186             @Override
16187             public void doFrame(long frameTimeNanos) {
16188                 tick();
16189             }
16190         };
16191 
16192         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
16193             @Override
16194             public void doFrame(long frameTimeNanos) {
16195                 mStatus = MARQUEE_RUNNING;
16196                 mLastAnimationMs = mChoreographer.getFrameTime();
16197                 tick();
16198             }
16199         };
16200 
16201         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
16202             @Override
16203             public void doFrame(long frameTimeNanos) {
16204                 if (mStatus == MARQUEE_RUNNING) {
16205                     if (mRepeatLimit >= 0) {
16206                         mRepeatLimit--;
16207                     }
16208                     start(mRepeatLimit);
16209                 }
16210             }
16211         };
16212 
16213         void tick() {
16214             if (mStatus != MARQUEE_RUNNING) {
16215                 return;
16216             }
16217 
16218             mChoreographer.removeFrameCallback(mTickCallback);
16219 
16220             final TextView textView = mView.get();
16221             if (textView != null && textView.isAggregatedVisible()
16222                     && (textView.isFocused() || textView.isSelected())) {
16223                 long currentMs = mChoreographer.getFrameTime();
16224                 long deltaMs = currentMs - mLastAnimationMs;
16225                 mLastAnimationMs = currentMs;
16226                 float deltaPx = deltaMs * mPixelsPerMs;
16227                 mScroll += deltaPx;
16228                 if (mScroll > mMaxScroll) {
16229                     mScroll = mMaxScroll;
16230                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
16231                 } else {
16232                     mChoreographer.postFrameCallback(mTickCallback);
16233                 }
16234                 textView.invalidate();
16235             }
16236         }
16237 
16238         void stop() {
16239             mStatus = MARQUEE_STOPPED;
16240             mChoreographer.removeFrameCallback(mStartCallback);
16241             mChoreographer.removeFrameCallback(mRestartCallback);
16242             mChoreographer.removeFrameCallback(mTickCallback);
16243             resetScroll();
16244         }
16245 
16246         private void resetScroll() {
16247             mScroll = 0.0f;
16248             final TextView textView = mView.get();
16249             if (textView != null) textView.invalidate();
16250         }
16251 
16252         void start(int repeatLimit) {
16253             if (repeatLimit == 0) {
16254                 stop();
16255                 return;
16256             }
16257             mRepeatLimit = repeatLimit;
16258             final TextView textView = mView.get();
16259             if (textView != null && textView.mLayout != null) {
16260                 mStatus = MARQUEE_STARTING;
16261                 mScroll = 0.0f;
16262                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
16263                         - textView.getCompoundPaddingRight();
16264                 final float lineWidth = textView.mLayout.getLineWidth(0);
16265                 final float gap = textWidth / 3.0f;
16266                 mGhostStart = lineWidth - textWidth + gap;
16267                 mMaxScroll = mGhostStart + textWidth;
16268                 mGhostOffset = lineWidth + gap;
16269                 mFadeStop = lineWidth + textWidth / 6.0f;
16270                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
16271 
16272                 textView.invalidate();
16273                 mChoreographer.postFrameCallback(mStartCallback);
16274             }
16275         }
16276 
16277         float getGhostOffset() {
16278             return mGhostOffset;
16279         }
16280 
16281         float getScroll() {
16282             return mScroll;
16283         }
16284 
16285         float getMaxFadeScroll() {
16286             return mMaxFadeScroll;
16287         }
16288 
16289         boolean shouldDrawLeftFade() {
16290             return mScroll <= mFadeStop;
16291         }
16292 
16293         boolean shouldDrawGhost() {
16294             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
16295         }
16296 
16297         boolean isRunning() {
16298             return mStatus == MARQUEE_RUNNING;
16299         }
16300 
16301         boolean isStopped() {
16302             return mStatus == MARQUEE_STOPPED;
16303         }
16304     }
16305 
16306     private class ChangeWatcher implements TextWatcher, SpanWatcher {
16307 
16308         private CharSequence mBeforeText;
16309 
16310         public void beforeTextChanged(CharSequence buffer, int start,
16311                                       int before, int after) {
16312             if (DEBUG_EXTRACT) {
16313                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
16314                         + " before=" + before + " after=" + after + ": " + buffer);
16315             }
16316 
16317             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
16318                 mBeforeText = mTransformed.toString();
16319             }
16320 
16321             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
16322         }
16323 
16324         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
16325             if (DEBUG_EXTRACT) {
16326                 Log.v(LOG_TAG, "onTextChanged start=" + start
16327                         + " before=" + before + " after=" + after + ": " + buffer);
16328             }
16329             TextView.this.handleTextChanged(buffer, start, before, after);
16330 
16331             if (isVisibleToAccessibility()) {
16332                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
16333                 mBeforeText = null;
16334             }
16335         }
16336 
16337         public void afterTextChanged(Editable buffer) {
16338             if (DEBUG_EXTRACT) {
16339                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
16340             }
16341             TextView.this.sendAfterTextChanged(buffer);
16342 
16343             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
16344                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
16345             }
16346         }
16347 
16348         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
16349             if (DEBUG_EXTRACT) {
16350                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
16351                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
16352             }
16353             TextView.this.spanChange(buf, what, s, st, e, en);
16354         }
16355 
16356         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
16357             if (DEBUG_EXTRACT) {
16358                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
16359             }
16360             TextView.this.spanChange(buf, what, -1, s, -1, e);
16361         }
16362 
16363         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
16364             if (DEBUG_EXTRACT) {
16365                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
16366             }
16367             TextView.this.spanChange(buf, what, s, -1, e, -1);
16368         }
16369     }
16370 
16371     /** @hide */
16372     @Override
16373     public void onInputConnectionOpenedInternal(@NonNull InputConnection ic,
16374             @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
16375         if (mEditor != null) {
16376             mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic,
16377                     editorInfo);
16378         }
16379     }
16380 
16381     /** @hide */
16382     @Override
16383     public void onInputConnectionClosedInternal() {
16384         if (mEditor != null) {
16385             mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo();
16386         }
16387     }
16388 
16389     /**
16390      * Default {@link TextView} implementation for receiving content. Apps wishing to provide
16391      * custom behavior should configure a listener via {@link #setOnReceiveContentListener}.
16392      *
16393      * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in
16394      * content without acting on it).
16395      *
16396      * <p>For editable TextViews the default behavior is to insert text into the view, coercing
16397      * non-text content to text as needed. The MIME types "text/plain" and "text/html" have
16398      * well-defined behavior for this, while other MIME types have reasonable fallback behavior
16399      * (see {@link ClipData.Item#coerceToStyledText}).
16400      *
16401      * @param payload The content to insert and related metadata.
16402      *
16403      * @return The portion of the passed-in content that was not handled (may be all, some, or none
16404      * of the passed-in content).
16405      */
16406     @Nullable
16407     @Override
16408     public ContentInfo onReceiveContent(@NonNull ContentInfo payload) {
16409         if (mEditor != null) {
16410             return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload);
16411         }
16412         return payload;
16413     }
16414 
16415     private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
16416         if (msgFormat == null) {
16417             Log.d(LOG_TAG, location);
16418         } else {
16419             Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs));
16420         }
16421     }
16422 
16423     /**
16424      * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
16425      * the view.
16426      *
16427      * <p>NOTE: When overriding the method, it should not collect a request to translate this
16428      * TextView if it is displaying a password.
16429      *
16430      * @param supportedFormats the supported translation format. The value could be {@link
16431      *                         android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
16432      * @param requestsCollector {@link Consumer} to receiver the {@link ViewTranslationRequest}
16433      *                                         which contains the information to be translated.
16434      */
16435     @Override
16436     public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats,
16437             @NonNull Consumer<ViewTranslationRequest> requestsCollector) {
16438         if (supportedFormats == null || supportedFormats.length == 0) {
16439             if (UiTranslationController.DEBUG) {
16440                 Log.w(LOG_TAG, "Do not provide the support translation formats.");
16441             }
16442             return;
16443         }
16444         ViewTranslationRequest.Builder requestBuilder =
16445                 new ViewTranslationRequest.Builder(getAutofillId());
16446         // Support Text translation
16447         if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) {
16448             if (mText == null || mText.length() == 0) {
16449                 if (UiTranslationController.DEBUG) {
16450                     Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
16451                 }
16452                 return;
16453             }
16454             boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
16455             if (isTextEditable() || isPassword) {
16456                 Log.w(LOG_TAG, "Cannot create translation request. editable = "
16457                         + isTextEditable() + ", isPassword = " + isPassword);
16458                 return;
16459             }
16460             // TODO(b/176488462): apply the view's important for translation
16461             requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
16462                     TranslationRequestValue.forText(mText));
16463             if (!TextUtils.isEmpty(getContentDescription())) {
16464                 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION,
16465                         TranslationRequestValue.forText(getContentDescription()));
16466             }
16467         }
16468         requestsCollector.accept(requestBuilder.build());
16469     }
16470 }
16471