• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
20 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
21 import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT;
22 import static android.view.ContentInfo.SOURCE_AUTOFILL;
23 import static android.view.ContentInfo.SOURCE_CLIPBOARD;
24 import static android.view.ContentInfo.SOURCE_PROCESS_TEXT;
25 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY;
26 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
27 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
28 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
29 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY;
30 import static android.view.accessibility.Flags.FLAG_A11Y_CHARACTER_IN_WINDOW_API;
31 import static android.view.accessibility.Flags.a11yCharacterInWindowApi;
32 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
33 import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY;
34 import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
35 
36 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
37 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
38 
39 import android.R;
40 import android.annotation.CallSuper;
41 import android.annotation.CheckResult;
42 import android.annotation.ColorInt;
43 import android.annotation.DrawableRes;
44 import android.annotation.FlaggedApi;
45 import android.annotation.FloatRange;
46 import android.annotation.IntDef;
47 import android.annotation.IntRange;
48 import android.annotation.NonNull;
49 import android.annotation.Nullable;
50 import android.annotation.Px;
51 import android.annotation.RequiresPermission;
52 import android.annotation.Size;
53 import android.annotation.StringRes;
54 import android.annotation.StyleRes;
55 import android.annotation.TestApi;
56 import android.annotation.XmlRes;
57 import android.app.Activity;
58 import android.app.PendingIntent;
59 import android.app.assist.AssistStructure;
60 import android.app.compat.CompatChanges;
61 import android.compat.annotation.ChangeId;
62 import android.compat.annotation.EnabledSince;
63 import android.compat.annotation.UnsupportedAppUsage;
64 import android.content.ClipData;
65 import android.content.ClipDescription;
66 import android.content.ClipboardManager;
67 import android.content.Context;
68 import android.content.Intent;
69 import android.content.UndoManager;
70 import android.content.pm.PackageManager;
71 import android.content.res.ColorStateList;
72 import android.content.res.CompatibilityInfo;
73 import android.content.res.Configuration;
74 import android.content.res.FontScaleConverterFactory;
75 import android.content.res.Resources;
76 import android.content.res.TypedArray;
77 import android.content.res.XmlResourceParser;
78 import android.graphics.BaseCanvas;
79 import android.graphics.BlendMode;
80 import android.graphics.Canvas;
81 import android.graphics.Color;
82 import android.graphics.Insets;
83 import android.graphics.Matrix;
84 import android.graphics.Paint;
85 import android.graphics.Paint.FontMetricsInt;
86 import android.graphics.Path;
87 import android.graphics.PointF;
88 import android.graphics.PorterDuff;
89 import android.graphics.Rect;
90 import android.graphics.RectF;
91 import android.graphics.Typeface;
92 import android.graphics.drawable.Drawable;
93 import android.graphics.fonts.FontStyle;
94 import android.graphics.fonts.FontVariationAxis;
95 import android.graphics.text.LineBreakConfig;
96 import android.icu.text.DecimalFormatSymbols;
97 import android.os.AsyncTask;
98 import android.os.Build;
99 import android.os.Build.VERSION_CODES;
100 import android.os.Bundle;
101 import android.os.CancellationSignal;
102 import android.os.Handler;
103 import android.os.LocaleList;
104 import android.os.Parcel;
105 import android.os.Parcelable;
106 import android.os.ParcelableParcel;
107 import android.os.Process;
108 import android.os.SystemClock;
109 import android.os.UserHandle;
110 import android.provider.Settings;
111 import android.text.BoringLayout;
112 import android.text.DynamicLayout;
113 import android.text.Editable;
114 import android.text.GetChars;
115 import android.text.GraphemeClusterSegmentFinder;
116 import android.text.GraphicsOperations;
117 import android.text.Highlights;
118 import android.text.InputFilter;
119 import android.text.InputType;
120 import android.text.Layout;
121 import android.text.NoCopySpan;
122 import android.text.ParcelableSpan;
123 import android.text.PrecomputedText;
124 import android.text.SegmentFinder;
125 import android.text.Selection;
126 import android.text.SpanWatcher;
127 import android.text.Spannable;
128 import android.text.SpannableStringBuilder;
129 import android.text.Spanned;
130 import android.text.SpannedString;
131 import android.text.StaticLayout;
132 import android.text.TextDirectionHeuristic;
133 import android.text.TextDirectionHeuristics;
134 import android.text.TextPaint;
135 import android.text.TextUtils;
136 import android.text.TextUtils.TruncateAt;
137 import android.text.TextWatcher;
138 import android.text.WordSegmentFinder;
139 import android.text.method.AllCapsTransformationMethod;
140 import android.text.method.ArrowKeyMovementMethod;
141 import android.text.method.DateKeyListener;
142 import android.text.method.DateTimeKeyListener;
143 import android.text.method.DialerKeyListener;
144 import android.text.method.DigitsKeyListener;
145 import android.text.method.KeyListener;
146 import android.text.method.LinkMovementMethod;
147 import android.text.method.MetaKeyKeyListener;
148 import android.text.method.MovementMethod;
149 import android.text.method.OffsetMapping;
150 import android.text.method.PasswordTransformationMethod;
151 import android.text.method.SingleLineTransformationMethod;
152 import android.text.method.TextKeyListener;
153 import android.text.method.TimeKeyListener;
154 import android.text.method.TransformationMethod;
155 import android.text.method.TransformationMethod2;
156 import android.text.method.WordIterator;
157 import android.text.style.CharacterStyle;
158 import android.text.style.ClickableSpan;
159 import android.text.style.ParagraphStyle;
160 import android.text.style.SpellCheckSpan;
161 import android.text.style.SuggestionSpan;
162 import android.text.style.URLSpan;
163 import android.text.style.UpdateAppearance;
164 import android.text.util.Linkify;
165 import android.util.ArraySet;
166 import android.util.AttributeSet;
167 import android.util.DisplayMetrics;
168 import android.util.IntArray;
169 import android.util.Log;
170 import android.util.SparseIntArray;
171 import android.util.TypedValue;
172 import android.view.AccessibilityIterators.TextSegmentIterator;
173 import android.view.ActionMode;
174 import android.view.Choreographer;
175 import android.view.ContentInfo;
176 import android.view.ContextMenu;
177 import android.view.DragEvent;
178 import android.view.Gravity;
179 import android.view.HapticFeedbackConstants;
180 import android.view.InputDevice;
181 import android.view.KeyCharacterMap;
182 import android.view.KeyEvent;
183 import android.view.MotionEvent;
184 import android.view.PointerIcon;
185 import android.view.View;
186 import android.view.ViewConfiguration;
187 import android.view.ViewDebug;
188 import android.view.ViewGroup;
189 import android.view.ViewGroup.LayoutParams;
190 import android.view.ViewHierarchyEncoder;
191 import android.view.ViewParent;
192 import android.view.ViewRootImpl;
193 import android.view.ViewStructure;
194 import android.view.ViewTreeObserver;
195 import android.view.accessibility.AccessibilityEvent;
196 import android.view.accessibility.AccessibilityManager;
197 import android.view.accessibility.AccessibilityNodeInfo;
198 import android.view.animation.AnimationUtils;
199 import android.view.autofill.AutofillManager;
200 import android.view.autofill.AutofillValue;
201 import android.view.contentcapture.ContentCaptureManager;
202 import android.view.contentcapture.ContentCaptureSession;
203 import android.view.inputmethod.BaseInputConnection;
204 import android.view.inputmethod.CompletionInfo;
205 import android.view.inputmethod.CorrectionInfo;
206 import android.view.inputmethod.CursorAnchorInfo;
207 import android.view.inputmethod.DeleteGesture;
208 import android.view.inputmethod.DeleteRangeGesture;
209 import android.view.inputmethod.EditorBoundsInfo;
210 import android.view.inputmethod.EditorInfo;
211 import android.view.inputmethod.ExtractedText;
212 import android.view.inputmethod.ExtractedTextRequest;
213 import android.view.inputmethod.HandwritingGesture;
214 import android.view.inputmethod.InputConnection;
215 import android.view.inputmethod.InputMethodManager;
216 import android.view.inputmethod.InsertGesture;
217 import android.view.inputmethod.InsertModeGesture;
218 import android.view.inputmethod.JoinOrSplitGesture;
219 import android.view.inputmethod.PreviewableHandwritingGesture;
220 import android.view.inputmethod.RemoveSpaceGesture;
221 import android.view.inputmethod.SelectGesture;
222 import android.view.inputmethod.SelectRangeGesture;
223 import android.view.inputmethod.TextAppearanceInfo;
224 import android.view.inputmethod.TextBoundsInfo;
225 import android.view.inspector.InspectableProperty;
226 import android.view.inspector.InspectableProperty.EnumEntry;
227 import android.view.inspector.InspectableProperty.FlagEntry;
228 import android.view.textclassifier.TextClassification;
229 import android.view.textclassifier.TextClassificationContext;
230 import android.view.textclassifier.TextClassificationManager;
231 import android.view.textclassifier.TextClassifier;
232 import android.view.textclassifier.TextLinks;
233 import android.view.textservice.SpellCheckerSubtype;
234 import android.view.textservice.TextServicesManager;
235 import android.view.translation.TranslationRequestValue;
236 import android.view.translation.TranslationSpec;
237 import android.view.translation.UiTranslationController;
238 import android.view.translation.ViewTranslationCallback;
239 import android.view.translation.ViewTranslationRequest;
240 import android.widget.RemoteViews.RemoteView;
241 
242 import com.android.internal.accessibility.util.AccessibilityUtils;
243 import com.android.internal.annotations.VisibleForTesting;
244 import com.android.internal.graphics.ColorUtils;
245 import com.android.internal.inputmethod.EditableInputConnection;
246 import com.android.internal.logging.MetricsLogger;
247 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
248 import com.android.internal.util.ArrayUtils;
249 import com.android.internal.util.FastMath;
250 import com.android.internal.util.Preconditions;
251 import com.android.text.flags.Flags;
252 
253 import libcore.util.EmptyArray;
254 
255 import org.xmlpull.v1.XmlPullParserException;
256 
257 import java.io.IOException;
258 import java.lang.annotation.Retention;
259 import java.lang.annotation.RetentionPolicy;
260 import java.lang.ref.WeakReference;
261 import java.util.ArrayList;
262 import java.util.Arrays;
263 import java.util.List;
264 import java.util.Locale;
265 import java.util.Objects;
266 import java.util.Set;
267 import java.util.concurrent.CompletableFuture;
268 import java.util.concurrent.TimeUnit;
269 import java.util.function.Consumer;
270 import java.util.function.Supplier;
271 import java.util.regex.Matcher;
272 import java.util.regex.Pattern;
273 
274 /**
275  * A user interface element that displays text to the user.
276  * To provide user-editable text, see {@link EditText}.
277  * <p>
278  * The following code sample shows a typical use, with an XML layout
279  * and code to modify the contents of the text view:
280  * </p>
281 
282  * <pre>
283  * &lt;LinearLayout
284        xmlns:android="http://schemas.android.com/apk/res/android"
285        android:layout_width="match_parent"
286        android:layout_height="match_parent"&gt;
287  *    &lt;TextView
288  *        android:id="@+id/text_view_id"
289  *        android:layout_height="wrap_content"
290  *        android:layout_width="wrap_content"
291  *        android:text="@string/hello" /&gt;
292  * &lt;/LinearLayout&gt;
293  * </pre>
294  * <p>
295  * This code sample demonstrates how to modify the contents of the text view
296  * defined in the previous XML layout:
297  * </p>
298  * <pre>
299  * public class MainActivity extends Activity {
300  *
301  *    protected void onCreate(Bundle savedInstanceState) {
302  *         super.onCreate(savedInstanceState);
303  *         setContentView(R.layout.activity_main);
304  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
305  *         helloTextView.setText(R.string.user_greeting);
306  *     }
307  * }
308  * </pre>
309  * <p>
310  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
311  * </p>
312  * <p>
313  * <b>XML attributes</b>
314  * <p>
315  * See {@link android.R.styleable#TextView TextView Attributes},
316  * {@link android.R.styleable#View View Attributes}
317  *
318  * @attr ref android.R.styleable#TextView_text
319  * @attr ref android.R.styleable#TextView_bufferType
320  * @attr ref android.R.styleable#TextView_hint
321  * @attr ref android.R.styleable#TextView_textColor
322  * @attr ref android.R.styleable#TextView_textColorHighlight
323  * @attr ref android.R.styleable#TextView_textColorHint
324  * @attr ref android.R.styleable#TextView_textAppearance
325  * @attr ref android.R.styleable#TextView_textColorLink
326  * @attr ref android.R.styleable#TextView_textFontWeight
327  * @attr ref android.R.styleable#TextView_textSize
328  * @attr ref android.R.styleable#TextView_textScaleX
329  * @attr ref android.R.styleable#TextView_fontFamily
330  * @attr ref android.R.styleable#TextView_typeface
331  * @attr ref android.R.styleable#TextView_textStyle
332  * @attr ref android.R.styleable#TextView_cursorVisible
333  * @attr ref android.R.styleable#TextView_maxLines
334  * @attr ref android.R.styleable#TextView_maxHeight
335  * @attr ref android.R.styleable#TextView_lines
336  * @attr ref android.R.styleable#TextView_height
337  * @attr ref android.R.styleable#TextView_minLines
338  * @attr ref android.R.styleable#TextView_minHeight
339  * @attr ref android.R.styleable#TextView_maxEms
340  * @attr ref android.R.styleable#TextView_maxWidth
341  * @attr ref android.R.styleable#TextView_ems
342  * @attr ref android.R.styleable#TextView_width
343  * @attr ref android.R.styleable#TextView_minEms
344  * @attr ref android.R.styleable#TextView_minWidth
345  * @attr ref android.R.styleable#TextView_gravity
346  * @attr ref android.R.styleable#TextView_scrollHorizontally
347  * @attr ref android.R.styleable#TextView_password
348  * @attr ref android.R.styleable#TextView_singleLine
349  * @attr ref android.R.styleable#TextView_selectAllOnFocus
350  * @attr ref android.R.styleable#TextView_includeFontPadding
351  * @attr ref android.R.styleable#TextView_maxLength
352  * @attr ref android.R.styleable#TextView_shadowColor
353  * @attr ref android.R.styleable#TextView_shadowDx
354  * @attr ref android.R.styleable#TextView_shadowDy
355  * @attr ref android.R.styleable#TextView_shadowRadius
356  * @attr ref android.R.styleable#TextView_autoLink
357  * @attr ref android.R.styleable#TextView_linksClickable
358  * @attr ref android.R.styleable#TextView_numeric
359  * @attr ref android.R.styleable#TextView_digits
360  * @attr ref android.R.styleable#TextView_phoneNumber
361  * @attr ref android.R.styleable#TextView_inputMethod
362  * @attr ref android.R.styleable#TextView_capitalize
363  * @attr ref android.R.styleable#TextView_autoText
364  * @attr ref android.R.styleable#TextView_editable
365  * @attr ref android.R.styleable#TextView_freezesText
366  * @attr ref android.R.styleable#TextView_ellipsize
367  * @attr ref android.R.styleable#TextView_drawableTop
368  * @attr ref android.R.styleable#TextView_drawableBottom
369  * @attr ref android.R.styleable#TextView_drawableRight
370  * @attr ref android.R.styleable#TextView_drawableLeft
371  * @attr ref android.R.styleable#TextView_drawableStart
372  * @attr ref android.R.styleable#TextView_drawableEnd
373  * @attr ref android.R.styleable#TextView_drawablePadding
374  * @attr ref android.R.styleable#TextView_drawableTint
375  * @attr ref android.R.styleable#TextView_drawableTintMode
376  * @attr ref android.R.styleable#TextView_lineSpacingExtra
377  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
378  * @attr ref android.R.styleable#TextView_justificationMode
379  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
380  * @attr ref android.R.styleable#TextView_inputType
381  * @attr ref android.R.styleable#TextView_imeOptions
382  * @attr ref android.R.styleable#TextView_privateImeOptions
383  * @attr ref android.R.styleable#TextView_imeActionLabel
384  * @attr ref android.R.styleable#TextView_imeActionId
385  * @attr ref android.R.styleable#TextView_editorExtras
386  * @attr ref android.R.styleable#TextView_elegantTextHeight
387  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
388  * @attr ref android.R.styleable#TextView_letterSpacing
389  * @attr ref android.R.styleable#TextView_fontFeatureSettings
390  * @attr ref android.R.styleable#TextView_fontVariationSettings
391  * @attr ref android.R.styleable#TextView_breakStrategy
392  * @attr ref android.R.styleable#TextView_hyphenationFrequency
393  * @attr ref android.R.styleable#TextView_lineBreakStyle
394  * @attr ref android.R.styleable#TextView_lineBreakWordStyle
395  * @attr ref android.R.styleable#TextView_autoSizeTextType
396  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
397  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
398  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
399  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
400  * @attr ref android.R.styleable#TextView_textCursorDrawable
401  * @attr ref android.R.styleable#TextView_textSelectHandle
402  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
403  * @attr ref android.R.styleable#TextView_textSelectHandleRight
404  * @attr ref android.R.styleable#TextView_allowUndo
405  * @attr ref android.R.styleable#TextView_enabled
406  */
407 @RemoteView
408 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
409     static final String LOG_TAG = "TextView";
410     static final boolean DEBUG_EXTRACT = false;
411     static final boolean DEBUG_CURSOR = false;
412 
413     private static final float[] TEMP_POSITION = new float[2];
414 
415     // Enum for the "typeface" XML parameter.
416     // TODO: How can we get this from the XML instead of hardcoding it here?
417     /** @hide */
418     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
419     @Retention(RetentionPolicy.SOURCE)
420     public @interface XMLTypefaceAttr{}
421     private static final int DEFAULT_TYPEFACE = -1;
422     private static final int SANS = 1;
423     private static final int SERIF = 2;
424     private static final int MONOSPACE = 3;
425 
426     // Enum for the "ellipsize" XML parameter.
427     private static final int ELLIPSIZE_NOT_SET = -1;
428     private static final int ELLIPSIZE_NONE = 0;
429     private static final int ELLIPSIZE_START = 1;
430     private static final int ELLIPSIZE_MIDDLE = 2;
431     private static final int ELLIPSIZE_END = 3;
432     private static final int ELLIPSIZE_MARQUEE = 4;
433 
434     // Bitfield for the "numeric" XML parameter.
435     // TODO: How can we get this from the XML instead of hardcoding it here?
436     private static final int SIGNED = 2;
437     private static final int DECIMAL = 4;
438 
439     /**
440      * Draw marquee text with fading edges as usual
441      */
442     private static final int MARQUEE_FADE_NORMAL = 0;
443 
444     /**
445      * Draw marquee text as ellipsize end while inactive instead of with the fade.
446      * (Useful for devices where the fade can be expensive if overdone)
447      */
448     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
449 
450     /**
451      * Draw marquee text with fading edges because it is currently active/animating.
452      */
453     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
454 
455     @UnsupportedAppUsage
456     private static final int LINES = 1;
457     private static final int EMS = LINES;
458     private static final int PIXELS = 2;
459 
460     // Maximum text length for single line input.
461     private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
462     private InputFilter.LengthFilter mSingleLineLengthFilter = null;
463 
464     private static final RectF TEMP_RECTF = new RectF();
465 
466     /** @hide */
467     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
468     private static final int ANIMATED_SCROLL_GAP = 250;
469 
470     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
471     private static final Spanned EMPTY_SPANNED = new SpannedString("");
472 
473     private static final int CHANGE_WATCHER_PRIORITY = 100;
474 
475     /**
476      * The span priority of the {@link OffsetMapping} that is set on the text. It must be
477      * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is
478      * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered
479      * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}.
480      */
481     private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200;
482 
483     // New state used to change background based on whether this TextView is multiline.
484     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
485 
486     // Accessibility action to share selected text.
487     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
488 
489     /**
490      * @hide
491      */
492     // Accessibility action start id for "process text" actions.
493     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
494 
495     /** Accessibility action start id for "smart" actions. @hide */
496     static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000;
497 
498     // Stable extra data keys supported by TextView.
499     private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS = List.of(
500             EXTRA_DATA_RENDERING_INFO_KEY,
501             EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
502     );
503 
504     // Flagged and stable extra data keys supported by TextView.
505     @FlaggedApi(FLAG_A11Y_CHARACTER_IN_WINDOW_API)
506     private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED = List.of(
507             EXTRA_DATA_RENDERING_INFO_KEY,
508             EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY,
509             EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY
510     );
511 
512     /**
513      * @hide
514      */
515     @TestApi
516     public static final int PROCESS_TEXT_REQUEST_CODE = 100;
517 
518     /**
519      *  Return code of {@link #doKeyDown}.
520      */
521     private static final int KEY_EVENT_NOT_HANDLED = 0;
522     private static final int KEY_EVENT_HANDLED = -1;
523     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
524     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
525 
526     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
527 
528     // The default value of the line break style.
529     private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE;
530 
531     // The default value of the line break word style.
532     private static final int DEFAULT_LINE_BREAK_WORD_STYLE =
533             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
534 
535     /**
536      * This change ID enables the fallback text line spacing (line height) for BoringLayout.
537      * @hide
538      */
539     @ChangeId
540     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
541     public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id
542 
543     /**
544      * This change ID enables the fallback text line spacing (line height) for StaticLayout.
545      * @hide
546      */
547     @ChangeId
548     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P)
549     public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id
550 
551 
552     /**
553      * This change ID enables the bounding box based layout.
554      * @hide
555      */
556     @ChangeId
557     @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
558     public static final long USE_BOUNDS_FOR_WIDTH = 63938206;  // buganizer id
559 
560     // System wide time for last cut, copy or text changed action.
561     static long sLastCutCopyOrTextChangedTime;
562     private ColorStateList mTextColor;
563     private ColorStateList mHintTextColor;
564     private ColorStateList mLinkTextColor;
565     @ViewDebug.ExportedProperty(category = "text")
566 
567     /**
568      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
569      */
570     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
571     private int mCurTextColor;
572 
573     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
574     private int mCurHintTextColor;
575     private boolean mFreezesText;
576 
577     @UnsupportedAppUsage
578     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
579     @UnsupportedAppUsage
580     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
581 
582     @UnsupportedAppUsage
583     private float mShadowRadius;
584     @UnsupportedAppUsage
585     private float mShadowDx;
586     @UnsupportedAppUsage
587     private float mShadowDy;
588     private int mShadowColor;
589 
590     private int mLastOrientation;
591 
592     private boolean mPreDrawRegistered;
593     private boolean mPreDrawListenerDetached;
594 
595     private TextClassifier mTextClassifier;
596     private TextClassifier mTextClassificationSession;
597     private TextClassificationContext mTextClassificationContext;
598 
599     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
600     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
601     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
602     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
603     // into by the user holding the movement key down) then we shouldn't prevent the focus from
604     // changing.
605     private boolean mPreventDefaultMovement;
606 
607     private TextUtils.TruncateAt mEllipsize;
608 
609     // A flag to indicate the cursor was hidden by IME.
610     private boolean mImeIsConsumingInput;
611 
612     // Whether cursor is visible without regard to {@link mImeConsumesInput}.
613     // {@code true} is the default value.
614     private boolean mCursorVisibleFromAttr = true;
615 
616     static class Drawables {
617         static final int LEFT = 0;
618         static final int TOP = 1;
619         static final int RIGHT = 2;
620         static final int BOTTOM = 3;
621 
622         static final int DRAWABLE_NONE = -1;
623         static final int DRAWABLE_RIGHT = 0;
624         static final int DRAWABLE_LEFT = 1;
625 
626         final Rect mCompoundRect = new Rect();
627 
628         final Drawable[] mShowing = new Drawable[4];
629 
630         ColorStateList mTintList;
631         BlendMode mBlendMode;
632         boolean mHasTint;
633         boolean mHasTintMode;
634 
635         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
636         Drawable mDrawableLeftInitial, mDrawableRightInitial;
637 
638         boolean mIsRtlCompatibilityMode;
639         boolean mOverride;
640 
641         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
642                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
643 
644         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
645                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
646 
647         int mDrawablePadding;
648 
649         int mDrawableSaved = DRAWABLE_NONE;
650 
Drawables(Context context)651         public Drawables(Context context) {
652             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
653             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
654                     || !context.getApplicationInfo().hasRtlSupport();
655             mOverride = false;
656         }
657 
658         /**
659          * @return {@code true} if this object contains metadata that needs to
660          *         be retained, {@code false} otherwise
661          */
662         public boolean hasMetadata() {
663             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
664         }
665 
666         /**
667          * Updates the list of displayed drawables to account for the current
668          * layout direction.
669          *
670          * @param layoutDirection the current layout direction
671          * @return {@code true} if the displayed drawables changed
672          */
673         public boolean resolveWithLayoutDirection(int layoutDirection) {
674             final Drawable previousLeft = mShowing[Drawables.LEFT];
675             final Drawable previousRight = mShowing[Drawables.RIGHT];
676 
677             // First reset "left" and "right" drawables to their initial values
678             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
679             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
680 
681             if (mIsRtlCompatibilityMode) {
682                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
683                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
684                     mShowing[Drawables.LEFT] = mDrawableStart;
685                     mDrawableSizeLeft = mDrawableSizeStart;
686                     mDrawableHeightLeft = mDrawableHeightStart;
687                 }
688                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
689                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
690                     mShowing[Drawables.RIGHT] = mDrawableEnd;
691                     mDrawableSizeRight = mDrawableSizeEnd;
692                     mDrawableHeightRight = mDrawableHeightEnd;
693                 }
694             } else {
695                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
696                 // drawable if and only if they have been defined
697                 switch(layoutDirection) {
698                     case LAYOUT_DIRECTION_RTL:
699                         if (mOverride) {
700                             mShowing[Drawables.RIGHT] = mDrawableStart;
701                             mDrawableSizeRight = mDrawableSizeStart;
702                             mDrawableHeightRight = mDrawableHeightStart;
703 
704                             mShowing[Drawables.LEFT] = mDrawableEnd;
705                             mDrawableSizeLeft = mDrawableSizeEnd;
706                             mDrawableHeightLeft = mDrawableHeightEnd;
707                         }
708                         break;
709 
710                     case LAYOUT_DIRECTION_LTR:
711                     default:
712                         if (mOverride) {
713                             mShowing[Drawables.LEFT] = mDrawableStart;
714                             mDrawableSizeLeft = mDrawableSizeStart;
715                             mDrawableHeightLeft = mDrawableHeightStart;
716 
717                             mShowing[Drawables.RIGHT] = mDrawableEnd;
718                             mDrawableSizeRight = mDrawableSizeEnd;
719                             mDrawableHeightRight = mDrawableHeightEnd;
720                         }
721                         break;
722                 }
723             }
724 
725             applyErrorDrawableIfNeeded(layoutDirection);
726 
727             return mShowing[Drawables.LEFT] != previousLeft
728                     || mShowing[Drawables.RIGHT] != previousRight;
729         }
730 
731         public void setErrorDrawable(Drawable dr, TextView tv) {
732             if (mDrawableError != dr && mDrawableError != null) {
733                 mDrawableError.setCallback(null);
734             }
735             mDrawableError = dr;
736 
737             if (mDrawableError != null) {
738                 final Rect compoundRect = mCompoundRect;
739                 final int[] state = tv.getDrawableState();
740 
741                 mDrawableError.setState(state);
742                 mDrawableError.copyBounds(compoundRect);
743                 mDrawableError.setCallback(tv);
744                 mDrawableSizeError = compoundRect.width();
745                 mDrawableHeightError = compoundRect.height();
746             } else {
747                 mDrawableSizeError = mDrawableHeightError = 0;
748             }
749         }
750 
751         private void applyErrorDrawableIfNeeded(int layoutDirection) {
752             // first restore the initial state if needed
753             switch (mDrawableSaved) {
754                 case DRAWABLE_LEFT:
755                     mShowing[Drawables.LEFT] = mDrawableTemp;
756                     mDrawableSizeLeft = mDrawableSizeTemp;
757                     mDrawableHeightLeft = mDrawableHeightTemp;
758                     break;
759                 case DRAWABLE_RIGHT:
760                     mShowing[Drawables.RIGHT] = mDrawableTemp;
761                     mDrawableSizeRight = mDrawableSizeTemp;
762                     mDrawableHeightRight = mDrawableHeightTemp;
763                     break;
764                 case DRAWABLE_NONE:
765                 default:
766             }
767             // then, if needed, assign the Error drawable to the correct location
768             if (mDrawableError != null) {
769                 switch(layoutDirection) {
770                     case LAYOUT_DIRECTION_RTL:
771                         mDrawableSaved = DRAWABLE_LEFT;
772 
773                         mDrawableTemp = mShowing[Drawables.LEFT];
774                         mDrawableSizeTemp = mDrawableSizeLeft;
775                         mDrawableHeightTemp = mDrawableHeightLeft;
776 
777                         mShowing[Drawables.LEFT] = mDrawableError;
778                         mDrawableSizeLeft = mDrawableSizeError;
779                         mDrawableHeightLeft = mDrawableHeightError;
780                         break;
781                     case LAYOUT_DIRECTION_LTR:
782                     default:
783                         mDrawableSaved = DRAWABLE_RIGHT;
784 
785                         mDrawableTemp = mShowing[Drawables.RIGHT];
786                         mDrawableSizeTemp = mDrawableSizeRight;
787                         mDrawableHeightTemp = mDrawableHeightRight;
788 
789                         mShowing[Drawables.RIGHT] = mDrawableError;
790                         mDrawableSizeRight = mDrawableSizeError;
791                         mDrawableHeightRight = mDrawableHeightError;
792                         break;
793                 }
794             }
795         }
796     }
797 
798     @UnsupportedAppUsage
799     Drawables mDrawables;
800 
801     @UnsupportedAppUsage
802     private CharWrapper mCharWrapper;
803 
804     @UnsupportedAppUsage(trackingBug = 124050217)
805     private Marquee mMarquee;
806     @UnsupportedAppUsage
807     private boolean mRestartMarquee;
808 
809     private int mMarqueeRepeatLimit = 3;
810 
811     private int mLastLayoutDirection = -1;
812 
813     /**
814      * On some devices the fading edges add a performance penalty if used
815      * extensively in the same layout. This mode indicates how the marquee
816      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
817      */
818     @UnsupportedAppUsage
819     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
820 
821     /**
822      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
823      * the layout that should be used when the mode switches.
824      */
825     @UnsupportedAppUsage
826     private Layout mSavedMarqueeModeLayout;
827 
828     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
829     @ViewDebug.ExportedProperty(category = "text")
830     @UnsupportedAppUsage
831     private @Nullable CharSequence mText;
832     private @Nullable Spannable mSpannable;
833     private @Nullable PrecomputedText mPrecomputed;
834 
835     @UnsupportedAppUsage
836     private CharSequence mTransformed;
837     @UnsupportedAppUsage
838     private BufferType mBufferType = BufferType.NORMAL;
839 
840     private CharSequence mHint;
841     @UnsupportedAppUsage
842     private Layout mHintLayout;
843     private boolean mHideHint;
844 
845     private MovementMethod mMovement;
846 
847     private TransformationMethod mTransformation;
848     @UnsupportedAppUsage
849     private boolean mAllowTransformationLengthChange;
850     @UnsupportedAppUsage
851     private ChangeWatcher mChangeWatcher;
852 
853     @UnsupportedAppUsage(trackingBug = 123769451)
854     private ArrayList<TextWatcher> mListeners;
855 
856     // display attributes
857     @UnsupportedAppUsage
858     private final TextPaint mTextPaint;
859     @UnsupportedAppUsage
860     private boolean mUserSetTextScaleX;
861     @UnsupportedAppUsage
862     private Layout mLayout;
863     private boolean mLocalesChanged = false;
864     private int mTextSizeUnit = -1;
865     private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
866     private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
867 
868     // This is used to reflect the current user preference for changing font weight and making text
869     // more bold.
870     private int mFontWeightAdjustment;
871     private Typeface mOriginalTypeface;
872 
873     // True if setKeyListener() has been explicitly called
874     private boolean mListenerChanged = false;
875     // True if internationalized input should be used for numbers and date and time.
876     private final boolean mUseInternationalizedInput;
877 
878     // Fallback fonts that end up getting used should be allowed to affect line spacing.
879     private static final int FALLBACK_LINE_SPACING_NONE = 0;
880     private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1;
881     private static final int FALLBACK_LINE_SPACING_ALL = 2;
882 
883     private int mUseFallbackLineSpacing;
884     // True if the view text can be padded for compat reasons, when the view is translated.
885     private final boolean mUseTextPaddingForUiTranslation;
886 
887     private boolean mUseBoundsForWidth;
888     private boolean mShiftDrawingOffsetForStartOverhang;
889     @Nullable private Paint.FontMetrics mMinimumFontMetrics;
890     @Nullable private Paint.FontMetrics mLocalePreferredFontMetrics;
891     private boolean mUseLocalePreferredLineHeightForMinimum;
892 
893     @ViewDebug.ExportedProperty(category = "text")
894     @UnsupportedAppUsage
895     private int mGravity = Gravity.TOP | Gravity.START;
896     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
897     private boolean mHorizontallyScrolling;
898 
899     private int mAutoLinkMask;
900     private boolean mLinksClickable = true;
901 
902     @UnsupportedAppUsage
903     private float mSpacingMult = 1.0f;
904     @UnsupportedAppUsage
905     private float mSpacingAdd = 0.0f;
906 
907     /**
908      * Remembers what line height was set to originally, before we broke it down into raw pixels.
909      *
910      * <p>This is stored as a complex dimension with both value and unit packed into one field!
911      * {@see TypedValue}
912      */
913     private int mLineHeightComplexDimen;
914 
915     private int mBreakStrategy;
916     private int mHyphenationFrequency;
917     private int mJustificationMode;
918 
919     @UnsupportedAppUsage
920     private int mMaximum = Integer.MAX_VALUE;
921     @UnsupportedAppUsage
922     private int mMaxMode = LINES;
923     @UnsupportedAppUsage
924     private int mMinimum = 0;
925     @UnsupportedAppUsage
926     private int mMinMode = LINES;
927 
928     @UnsupportedAppUsage
929     private int mOldMaximum = mMaximum;
930     @UnsupportedAppUsage
931     private int mOldMaxMode = mMaxMode;
932 
933     @UnsupportedAppUsage
934     private int mMaxWidth = Integer.MAX_VALUE;
935     @UnsupportedAppUsage
936     private int mMaxWidthMode = PIXELS;
937     @UnsupportedAppUsage
938     private int mMinWidth = 0;
939     @UnsupportedAppUsage
940     private int mMinWidthMode = PIXELS;
941 
942     @UnsupportedAppUsage
943     private boolean mSingleLine;
944     @UnsupportedAppUsage
945     private int mDesiredHeightAtMeasure = -1;
946     @UnsupportedAppUsage
947     private boolean mIncludePad = true;
948     private int mDeferScroll = -1;
949 
950     // tmp primitives, so we don't alloc them on each draw
951     private Rect mTempRect;
952     private long mLastScroll;
953     private Scroller mScroller;
954     private TextPaint mTempTextPaint;
955 
956     private Object mTempCursor;
957     private Matrix mTempMatrix;
958 
959     @UnsupportedAppUsage
960     private BoringLayout.Metrics mBoring;
961     @UnsupportedAppUsage
962     private BoringLayout.Metrics mHintBoring;
963     @UnsupportedAppUsage
964     private BoringLayout mSavedLayout;
965     @UnsupportedAppUsage
966     private BoringLayout mSavedHintLayout;
967 
968     @UnsupportedAppUsage
969     private TextDirectionHeuristic mTextDir;
970 
971     private InputFilter[] mFilters = NO_FILTERS;
972 
973     /**
974      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
975      * the same as {@link Process#myUserHandle()}.
976      *
977      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
978      * other apps may need to set this so that the system can use right user's resources and
979      * services such as input methods and spell checkers.</p>
980      *
981      * @see #setTextOperationUser(UserHandle)
982      */
983     @Nullable
984     private UserHandle mTextOperationUser;
985 
986     private volatile Locale mCurrentSpellCheckerLocaleCache;
987 
988     // It is possible to have a selection even when mEditor is null (programmatically set, like when
989     // a link is pressed). These highlight-related fields do not go in mEditor.
990     @UnsupportedAppUsage
991     int mHighlightColor = 0x6633B5E5;
992     private Path mHighlightPath;
993     @UnsupportedAppUsage
994     private final Paint mHighlightPaint;
995     @UnsupportedAppUsage
996     private boolean mHighlightPathBogus = true;
997 
998     private List<Path> mHighlightPaths;
999     private List<Paint> mHighlightPaints;
1000     private Highlights mHighlights;
1001     private int[] mSearchResultHighlights = null;
1002     private Paint mSearchResultHighlightPaint = null;
1003     private Paint mFocusedSearchResultHighlightPaint = null;
1004     private int mFocusedSearchResultHighlightColor = 0xFFFF9632;
1005     private int mSearchResultHighlightColor = 0xFFFFFF00;
1006 
1007     private int mFocusedSearchResultIndex = -1;
1008     private int mGesturePreviewHighlightStart = -1;
1009     private int mGesturePreviewHighlightEnd = -1;
1010     private Paint mGesturePreviewHighlightPaint;
1011     private final List<Path> mPathRecyclePool = new ArrayList<>();
1012     private boolean mHighlightPathsBogus = true;
1013 
1014     // Although these fields are specific to editable text, they are not added to Editor because
1015     // they are defined by the TextView's style and are theme-dependent.
1016     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1017     int mCursorDrawableRes;
1018     private Drawable mCursorDrawable;
1019     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
1020     // by removing it, but we would break apps targeting <= P that use it by reflection.
1021     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1022     int mTextSelectHandleLeftRes;
1023     private Drawable mTextSelectHandleLeft;
1024     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
1025     // by removing it, but we would break apps targeting <= P that use it by reflection.
1026     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1027     int mTextSelectHandleRightRes;
1028     private Drawable mTextSelectHandleRight;
1029     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
1030     // by removing it, but we would break apps targeting <= P that use it by reflection.
1031     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1032     int mTextSelectHandleRes;
1033     private Drawable mTextSelectHandle;
1034     int mTextEditSuggestionItemLayout;
1035     int mTextEditSuggestionContainerLayout;
1036     int mTextEditSuggestionHighlightStyle;
1037 
1038     private static final int NO_POINTER_ID = -1;
1039     /**
1040      * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among
1041      * TextView and the handle views which are rendered on popup windows.
1042      */
1043     private int mPrimePointerId = NO_POINTER_ID;
1044 
1045     /**
1046      * Whether the prime pointer is from the event delivered to selection handle or insertion
1047      * handle.
1048      */
1049     private boolean mIsPrimePointerFromHandleView;
1050 
1051     /**
1052      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
1053      * See {@link #createEditorIfNeeded()}.
1054      */
1055     @UnsupportedAppUsage
1056     private Editor mEditor;
1057 
1058     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
1059     private static final int DEVICE_PROVISIONED_NO = 1;
1060     private static final int DEVICE_PROVISIONED_YES = 2;
1061 
1062     /**
1063      * Some special options such as sharing selected text should only be shown if the device
1064      * is provisioned. Only check the provisioned state once for a given view instance.
1065      */
1066     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
1067 
1068     /**
1069      * The last input source on this TextView.
1070      *
1071      * Use the SOURCE_TOUCHSCREEN as the default value for backward compatibility. There could be a
1072      * non UI event originated ActionMode initiation, e.g. API call, a11y events, etc.
1073      */
1074     private int mLastInputSource = InputDevice.SOURCE_TOUCHSCREEN;
1075 
1076     /**
1077      * The TextView does not auto-size text (default).
1078      */
1079     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
1080 
1081     /**
1082      * The TextView scales text size both horizontally and vertically to fit within the
1083      * container.
1084      */
1085     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
1086 
1087     /** @hide */
1088     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
1089             AUTO_SIZE_TEXT_TYPE_NONE,
1090             AUTO_SIZE_TEXT_TYPE_UNIFORM
1091     })
1092     @Retention(RetentionPolicy.SOURCE)
1093     public @interface AutoSizeTextType {}
1094     // Default minimum size for auto-sizing text in scaled pixels.
1095     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
1096     // Default maximum size for auto-sizing text in scaled pixels.
1097     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
1098     // Default value for the step size in pixels.
1099     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
1100     // Use this to specify that any of the auto-size configuration int values have not been set.
1101     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
1102     // Auto-size text type.
1103     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1104     // Specify if auto-size text is needed.
1105     private boolean mNeedsAutoSizeText = false;
1106     // Step size for auto-sizing in pixels.
1107     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1108     // Minimum text size for auto-sizing in pixels.
1109     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1110     // Maximum text size for auto-sizing in pixels.
1111     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1112     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
1113     // when auto-sizing text.
1114     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
1115     // Specifies whether auto-size should use the provided auto size steps set or if it should
1116     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
1117     // mAutoSizeStepGranularityInPx.
1118     private boolean mHasPresetAutoSizeValues = false;
1119 
1120     // Autofill-related attributes
1121     //
1122     // Indicates whether the text was set statically or dynamically, so it can be used to
1123     // sanitize autofill requests.
1124     private boolean mTextSetFromXmlOrResourceId = false;
1125     // Resource id used to set the text.
1126     private @StringRes int mTextId = Resources.ID_NULL;
1127     // Resource id used to set the hint.
1128     private @StringRes int mHintId = Resources.ID_NULL;
1129     //
1130     // End of autofill-related attributes
1131 
1132     private Pattern mWhitespacePattern;
1133 
1134     /**
1135      * Kick-start the font cache for the zygote process (to pay the cost of
1136      * initializing freetype for our default font only once).
1137      * @hide
1138      */
1139     public static void preloadFontCache() {
1140         if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
1141             return;
1142         }
1143         Paint p = new Paint();
1144         p.setAntiAlias(true);
1145         // Ensure that the Typeface is loaded here.
1146         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
1147         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
1148         // since Paint.measureText can not be called without Typeface static initializer.
1149         p.setTypeface(Typeface.DEFAULT);
1150         // We don't care about the result, just the side-effect of measuring.
1151         p.measureText("H");
1152     }
1153 
1154     /**
1155      * Interface definition for a callback to be invoked when an action is
1156      * performed on the editor.
1157      */
1158     public interface OnEditorActionListener {
1159         /**
1160          * Called when an action is being performed.
1161          *
1162          * @param v The view that was clicked.
1163          * @param actionId Identifier of the action.  This will be either the
1164          * identifier you supplied, or {@link EditorInfo#IME_NULL
1165          * EditorInfo.IME_NULL} if being called due to the enter key
1166          * being pressed. Starting from Android 14, the action identifier will
1167          * also be included when triggered by an enter key if the input is
1168          * constrained to a single line.
1169          * @param event If triggered by an enter key, this is the event;
1170          * otherwise, this is null.
1171          * @return Return true if you have consumed the action, else false.
1172          */
1173         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
1174     }
1175 
1176     public TextView(Context context) {
1177         this(context, null);
1178     }
1179 
1180     public TextView(Context context, @Nullable AttributeSet attrs) {
1181         this(context, attrs, com.android.internal.R.attr.textViewStyle);
1182     }
1183 
1184     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
1185         this(context, attrs, defStyleAttr, 0);
1186     }
1187 
1188     @SuppressWarnings("deprecation")
1189     public TextView(
1190             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1191         super(context, attrs, defStyleAttr, defStyleRes);
1192 
1193         // TextView is important by default, unless app developer overrode attribute.
1194         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
1195             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
1196         }
1197         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
1198             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
1199         }
1200 
1201         setTextInternal("");
1202 
1203         final Resources res = getResources();
1204         final CompatibilityInfo compat = res.getCompatibilityInfo();
1205 
1206         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
1207         mTextPaint.density = res.getDisplayMetrics().density;
1208         mTextPaint.setCompatibilityScaling(compat.applicationScale);
1209 
1210         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1211         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
1212 
1213         mMovement = getDefaultMovementMethod();
1214 
1215         mTransformation = null;
1216 
1217         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
1218         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
1219         attributes.mTextSize = 15;
1220         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1221         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1222         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1223         mLastOrientation = getResources().getConfiguration().orientation;
1224 
1225         final Resources.Theme theme = context.getTheme();
1226 
1227         /*
1228          * Look the appearance up without checking first if it exists because
1229          * almost every TextView has one and it greatly simplifies the logic
1230          * to be able to parse the appearance first and then let specific tags
1231          * for this View override it.
1232          */
1233         TypedArray a = theme.obtainStyledAttributes(attrs,
1234                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1235         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1236                 attrs, a, defStyleAttr, defStyleRes);
1237         TypedArray appearance = null;
1238         int ap = a.getResourceId(
1239                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1240         a.recycle();
1241         if (ap != -1) {
1242             appearance = theme.obtainStyledAttributes(
1243                     ap, com.android.internal.R.styleable.TextAppearance);
1244             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1245                     null, appearance, 0, ap);
1246         }
1247         if (appearance != null) {
1248             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1249             attributes.mFontFamilyExplicit = false;
1250             appearance.recycle();
1251         }
1252 
1253         boolean editable = getDefaultEditable();
1254         CharSequence inputMethod = null;
1255         int numeric = 0;
1256         CharSequence digits = null;
1257         boolean phone = false;
1258         boolean autotext = false;
1259         int autocap = -1;
1260         int buffertype = 0;
1261         boolean selectallonfocus = false;
1262         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1263                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1264         ColorStateList drawableTint = null;
1265         BlendMode drawableTintMode = null;
1266         int drawablePadding = 0;
1267         int ellipsize = ELLIPSIZE_NOT_SET;
1268         boolean singleLine = false;
1269         int maxlength = -1;
1270         CharSequence text = "";
1271         CharSequence hint = null;
1272         boolean password = false;
1273         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1274         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1275         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1276         int inputType = EditorInfo.TYPE_NULL;
1277         a = theme.obtainStyledAttributes(
1278                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1279         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1280                 defStyleAttr, defStyleRes);
1281         int firstBaselineToTopHeight = -1;
1282         int lastBaselineToBottomHeight = -1;
1283         float lineHeight = -1f;
1284         int lineHeightUnit = -1;
1285         boolean hasUseBoundForWidthValue = false;
1286 
1287         readTextAppearance(context, a, attributes, true /* styleArray */);
1288 
1289         int n = a.getIndexCount();
1290 
1291         // Must set id in a temporary variable because it will be reset by setText()
1292         boolean textIsSetFromXml = false;
1293         for (int i = 0; i < n; i++) {
1294             int attr = a.getIndex(i);
1295 
1296             switch (attr) {
1297                 case com.android.internal.R.styleable.TextView_editable:
1298                     editable = a.getBoolean(attr, editable);
1299                     break;
1300 
1301                 case com.android.internal.R.styleable.TextView_inputMethod:
1302                     inputMethod = a.getText(attr);
1303                     break;
1304 
1305                 case com.android.internal.R.styleable.TextView_numeric:
1306                     numeric = a.getInt(attr, numeric);
1307                     break;
1308 
1309                 case com.android.internal.R.styleable.TextView_digits:
1310                     digits = a.getText(attr);
1311                     break;
1312 
1313                 case com.android.internal.R.styleable.TextView_phoneNumber:
1314                     phone = a.getBoolean(attr, phone);
1315                     break;
1316 
1317                 case com.android.internal.R.styleable.TextView_autoText:
1318                     autotext = a.getBoolean(attr, autotext);
1319                     break;
1320 
1321                 case com.android.internal.R.styleable.TextView_capitalize:
1322                     autocap = a.getInt(attr, autocap);
1323                     break;
1324 
1325                 case com.android.internal.R.styleable.TextView_bufferType:
1326                     buffertype = a.getInt(attr, buffertype);
1327                     break;
1328 
1329                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1330                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1331                     break;
1332 
1333                 case com.android.internal.R.styleable.TextView_autoLink:
1334                     mAutoLinkMask = a.getInt(attr, 0);
1335                     break;
1336 
1337                 case com.android.internal.R.styleable.TextView_linksClickable:
1338                     mLinksClickable = a.getBoolean(attr, true);
1339                     break;
1340 
1341                 case com.android.internal.R.styleable.TextView_drawableLeft:
1342                     drawableLeft = a.getDrawable(attr);
1343                     break;
1344 
1345                 case com.android.internal.R.styleable.TextView_drawableTop:
1346                     drawableTop = a.getDrawable(attr);
1347                     break;
1348 
1349                 case com.android.internal.R.styleable.TextView_drawableRight:
1350                     drawableRight = a.getDrawable(attr);
1351                     break;
1352 
1353                 case com.android.internal.R.styleable.TextView_drawableBottom:
1354                     drawableBottom = a.getDrawable(attr);
1355                     break;
1356 
1357                 case com.android.internal.R.styleable.TextView_drawableStart:
1358                     drawableStart = a.getDrawable(attr);
1359                     break;
1360 
1361                 case com.android.internal.R.styleable.TextView_drawableEnd:
1362                     drawableEnd = a.getDrawable(attr);
1363                     break;
1364 
1365                 case com.android.internal.R.styleable.TextView_drawableTint:
1366                     drawableTint = a.getColorStateList(attr);
1367                     break;
1368 
1369                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1370                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1371                             drawableTintMode);
1372                     break;
1373 
1374                 case com.android.internal.R.styleable.TextView_drawablePadding:
1375                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1376                     break;
1377 
1378                 case com.android.internal.R.styleable.TextView_maxLines:
1379                     setMaxLines(a.getInt(attr, -1));
1380                     break;
1381 
1382                 case com.android.internal.R.styleable.TextView_maxHeight:
1383                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1384                     break;
1385 
1386                 case com.android.internal.R.styleable.TextView_lines:
1387                     setLines(a.getInt(attr, -1));
1388                     break;
1389 
1390                 case com.android.internal.R.styleable.TextView_height:
1391                     setHeight(a.getDimensionPixelSize(attr, -1));
1392                     break;
1393 
1394                 case com.android.internal.R.styleable.TextView_minLines:
1395                     setMinLines(a.getInt(attr, -1));
1396                     break;
1397 
1398                 case com.android.internal.R.styleable.TextView_minHeight:
1399                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1400                     break;
1401 
1402                 case com.android.internal.R.styleable.TextView_maxEms:
1403                     setMaxEms(a.getInt(attr, -1));
1404                     break;
1405 
1406                 case com.android.internal.R.styleable.TextView_maxWidth:
1407                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1408                     break;
1409 
1410                 case com.android.internal.R.styleable.TextView_ems:
1411                     setEms(a.getInt(attr, -1));
1412                     break;
1413 
1414                 case com.android.internal.R.styleable.TextView_width:
1415                     setWidth(a.getDimensionPixelSize(attr, -1));
1416                     break;
1417 
1418                 case com.android.internal.R.styleable.TextView_minEms:
1419                     setMinEms(a.getInt(attr, -1));
1420                     break;
1421 
1422                 case com.android.internal.R.styleable.TextView_minWidth:
1423                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1424                     break;
1425 
1426                 case com.android.internal.R.styleable.TextView_gravity:
1427                     setGravity(a.getInt(attr, -1));
1428                     break;
1429 
1430                 case com.android.internal.R.styleable.TextView_hint:
1431                     mHintId = a.getResourceId(attr, Resources.ID_NULL);
1432                     hint = a.getText(attr);
1433                     break;
1434 
1435                 case com.android.internal.R.styleable.TextView_text:
1436                     textIsSetFromXml = true;
1437                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1438                     text = a.getText(attr);
1439                     break;
1440 
1441                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1442                     if (a.getBoolean(attr, false)) {
1443                         setHorizontallyScrolling(true);
1444                     }
1445                     break;
1446 
1447                 case com.android.internal.R.styleable.TextView_singleLine:
1448                     singleLine = a.getBoolean(attr, singleLine);
1449                     break;
1450 
1451                 case com.android.internal.R.styleable.TextView_ellipsize:
1452                     ellipsize = a.getInt(attr, ellipsize);
1453                     break;
1454 
1455                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1456                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1457                     break;
1458 
1459                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1460                     if (!a.getBoolean(attr, true)) {
1461                         setIncludeFontPadding(false);
1462                     }
1463                     break;
1464 
1465                 case com.android.internal.R.styleable.TextView_cursorVisible:
1466                     if (!a.getBoolean(attr, true)) {
1467                         setCursorVisible(false);
1468                     }
1469                     break;
1470 
1471                 case com.android.internal.R.styleable.TextView_maxLength:
1472                     maxlength = a.getInt(attr, -1);
1473                     break;
1474 
1475                 case com.android.internal.R.styleable.TextView_textScaleX:
1476                     setTextScaleX(a.getFloat(attr, 1.0f));
1477                     break;
1478 
1479                 case com.android.internal.R.styleable.TextView_freezesText:
1480                     mFreezesText = a.getBoolean(attr, false);
1481                     break;
1482 
1483                 case com.android.internal.R.styleable.TextView_enabled:
1484                     setEnabled(a.getBoolean(attr, isEnabled()));
1485                     break;
1486 
1487                 case com.android.internal.R.styleable.TextView_password:
1488                     password = a.getBoolean(attr, password);
1489                     break;
1490 
1491                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1492                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1493                     break;
1494 
1495                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1496                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1497                     break;
1498 
1499                 case com.android.internal.R.styleable.TextView_inputType:
1500                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1501                     break;
1502 
1503                 case com.android.internal.R.styleable.TextView_allowUndo:
1504                     createEditorIfNeeded();
1505                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1506                     break;
1507 
1508                 case com.android.internal.R.styleable.TextView_imeOptions:
1509                     createEditorIfNeeded();
1510                     mEditor.createInputContentTypeIfNeeded();
1511                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1512                             mEditor.mInputContentType.imeOptions);
1513                     break;
1514 
1515                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1516                     createEditorIfNeeded();
1517                     mEditor.createInputContentTypeIfNeeded();
1518                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1519                     break;
1520 
1521                 case com.android.internal.R.styleable.TextView_imeActionId:
1522                     createEditorIfNeeded();
1523                     mEditor.createInputContentTypeIfNeeded();
1524                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1525                             mEditor.mInputContentType.imeActionId);
1526                     break;
1527 
1528                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1529                     setPrivateImeOptions(a.getString(attr));
1530                     break;
1531 
1532                 case com.android.internal.R.styleable.TextView_editorExtras:
1533                     try {
1534                         setInputExtras(a.getResourceId(attr, 0));
1535                     } catch (XmlPullParserException e) {
1536                         Log.w(LOG_TAG, "Failure reading input extras", e);
1537                     } catch (IOException e) {
1538                         Log.w(LOG_TAG, "Failure reading input extras", e);
1539                     }
1540                     break;
1541 
1542                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1543                     mCursorDrawableRes = a.getResourceId(attr, 0);
1544                     break;
1545 
1546                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1547                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1548                     break;
1549 
1550                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1551                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1552                     break;
1553 
1554                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1555                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1556                     break;
1557 
1558                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1559                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1560                     break;
1561 
1562                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1563                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1564                     break;
1565 
1566                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1567                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1568                     break;
1569 
1570                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1571                     setTextIsSelectable(a.getBoolean(attr, false));
1572                     break;
1573 
1574                 case com.android.internal.R.styleable.TextView_breakStrategy:
1575                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1576                     break;
1577 
1578                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1579                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1580                     break;
1581 
1582                 case com.android.internal.R.styleable.TextView_lineBreakStyle:
1583                     mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE);
1584                     break;
1585 
1586                 case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
1587                     mLineBreakWordStyle = a.getInt(attr,
1588                             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
1589                     break;
1590 
1591                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1592                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1593                     break;
1594 
1595                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1596                     autoSizeStepGranularityInPx = a.getDimension(attr,
1597                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1598                     break;
1599 
1600                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1601                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1602                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1603                     break;
1604 
1605                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1606                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1607                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1608                     break;
1609 
1610                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1611                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1612                     if (autoSizeStepSizeArrayResId > 0) {
1613                         final TypedArray autoSizePresetTextSizes = a.getResources()
1614                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1615                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1616                         autoSizePresetTextSizes.recycle();
1617                     }
1618                     break;
1619                 case com.android.internal.R.styleable.TextView_justificationMode:
1620                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1621                     break;
1622 
1623                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1624                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1625                     break;
1626 
1627                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1628                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1629                     break;
1630 
1631                 case com.android.internal.R.styleable.TextView_lineHeight:
1632                     TypedValue peekValue = a.peekValue(attr);
1633                     if (peekValue != null && peekValue.type == TypedValue.TYPE_DIMENSION) {
1634                         lineHeightUnit = peekValue.getComplexUnit();
1635                         lineHeight = TypedValue.complexToFloat(peekValue.data);
1636                     } else {
1637                         lineHeight = a.getDimensionPixelSize(attr, -1);
1638                     }
1639                     break;
1640                 case com.android.internal.R.styleable.TextView_useBoundsForWidth:
1641                     mUseBoundsForWidth = a.getBoolean(attr, false);
1642                     hasUseBoundForWidthValue = true;
1643                     break;
1644                 case com.android.internal.R.styleable
1645                         .TextView_shiftDrawingOffsetForStartOverhang:
1646                     mShiftDrawingOffsetForStartOverhang = a.getBoolean(attr, false);
1647                     break;
1648                 case com.android.internal.R.styleable
1649                         .TextView_useLocalePreferredLineHeightForMinimum:
1650                     mUseLocalePreferredLineHeightForMinimum = a.getBoolean(attr, false);
1651                     break;
1652             }
1653         }
1654 
1655         a.recycle();
1656 
1657         BufferType bufferType = BufferType.EDITABLE;
1658 
1659         final int variation =
1660                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1661         final boolean passwordInputType = variation
1662                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1663         final boolean webPasswordInputType = variation
1664                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1665         final boolean numberPasswordInputType = variation
1666                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1667 
1668         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1669         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1670         if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
1671             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL;
1672         } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) {
1673             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
1674         } else {
1675             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
1676         }
1677 
1678         if (!hasUseBoundForWidthValue) {
1679             mUseBoundsForWidth = CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH);
1680         }
1681 
1682         // TODO(b/179693024): Use a ChangeId instead.
1683         mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R;
1684 
1685         if (inputMethod != null) {
1686             Class<?> c;
1687 
1688             try {
1689                 c = Class.forName(inputMethod.toString());
1690             } catch (ClassNotFoundException ex) {
1691                 throw new RuntimeException(ex);
1692             }
1693 
1694             try {
1695                 createEditorIfNeeded();
1696                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1697             } catch (InstantiationException ex) {
1698                 throw new RuntimeException(ex);
1699             } catch (IllegalAccessException ex) {
1700                 throw new RuntimeException(ex);
1701             }
1702             try {
1703                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1704                         ? inputType
1705                         : mEditor.mKeyListener.getInputType();
1706             } catch (IncompatibleClassChangeError e) {
1707                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1708             }
1709         } else if (digits != null) {
1710             createEditorIfNeeded();
1711             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1712             // If no input type was specified, we will default to generic
1713             // text, since we can't tell the IME about the set of digits
1714             // that was selected.
1715             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1716                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1717         } else if (inputType != EditorInfo.TYPE_NULL) {
1718             setInputType(inputType, true);
1719             // If set, the input type overrides what was set using the deprecated singleLine flag.
1720             singleLine = !isMultilineInputType(inputType);
1721         } else if (phone) {
1722             createEditorIfNeeded();
1723             mEditor.mKeyListener = DialerKeyListener.getInstance();
1724             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1725         } else if (numeric != 0) {
1726             createEditorIfNeeded();
1727             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1728                     null,  // locale
1729                     (numeric & SIGNED) != 0,
1730                     (numeric & DECIMAL) != 0);
1731             inputType = mEditor.mKeyListener.getInputType();
1732             mEditor.mInputType = inputType;
1733         } else if (autotext || autocap != -1) {
1734             TextKeyListener.Capitalize cap;
1735 
1736             inputType = EditorInfo.TYPE_CLASS_TEXT;
1737 
1738             switch (autocap) {
1739                 case 1:
1740                     cap = TextKeyListener.Capitalize.SENTENCES;
1741                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1742                     break;
1743 
1744                 case 2:
1745                     cap = TextKeyListener.Capitalize.WORDS;
1746                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1747                     break;
1748 
1749                 case 3:
1750                     cap = TextKeyListener.Capitalize.CHARACTERS;
1751                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1752                     break;
1753 
1754                 default:
1755                     cap = TextKeyListener.Capitalize.NONE;
1756                     break;
1757             }
1758 
1759             createEditorIfNeeded();
1760             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1761             mEditor.mInputType = inputType;
1762         } else if (editable) {
1763             createEditorIfNeeded();
1764             mEditor.mKeyListener = TextKeyListener.getInstance();
1765             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1766         } else if (isTextSelectable()) {
1767             // Prevent text changes from keyboard.
1768             if (mEditor != null) {
1769                 mEditor.mKeyListener = null;
1770                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1771             }
1772             bufferType = BufferType.SPANNABLE;
1773             // So that selection can be changed using arrow keys and touch is handled.
1774             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1775         } else {
1776             if (mEditor != null) mEditor.mKeyListener = null;
1777 
1778             switch (buffertype) {
1779                 case 0:
1780                     bufferType = BufferType.NORMAL;
1781                     break;
1782                 case 1:
1783                     bufferType = BufferType.SPANNABLE;
1784                     break;
1785                 case 2:
1786                     bufferType = BufferType.EDITABLE;
1787                     break;
1788             }
1789         }
1790 
1791         if (mEditor != null) {
1792             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1793                     numberPasswordInputType);
1794         }
1795 
1796         if (selectallonfocus) {
1797             createEditorIfNeeded();
1798             mEditor.mSelectAllOnFocus = true;
1799 
1800             if (bufferType == BufferType.NORMAL) {
1801                 bufferType = BufferType.SPANNABLE;
1802             }
1803         }
1804 
1805         // Set up the tint (if needed) before setting the drawables so that it
1806         // gets applied correctly.
1807         if (drawableTint != null || drawableTintMode != null) {
1808             if (mDrawables == null) {
1809                 mDrawables = new Drawables(context);
1810             }
1811             if (drawableTint != null) {
1812                 mDrawables.mTintList = drawableTint;
1813                 mDrawables.mHasTint = true;
1814             }
1815             if (drawableTintMode != null) {
1816                 mDrawables.mBlendMode = drawableTintMode;
1817                 mDrawables.mHasTintMode = true;
1818             }
1819         }
1820 
1821         // This call will save the initial left/right drawables
1822         setCompoundDrawablesWithIntrinsicBounds(
1823                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1824         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1825         setCompoundDrawablePadding(drawablePadding);
1826 
1827         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1828         // of lines of height are unchanged for multi-line TextViews.
1829         setInputTypeSingleLine(singleLine);
1830         applySingleLine(singleLine, singleLine, singleLine,
1831                 // Does not apply automated max length filter since length filter will be resolved
1832                 // later in this function.
1833                 false
1834         );
1835 
1836         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1837             ellipsize = ELLIPSIZE_END;
1838         }
1839 
1840         switch (ellipsize) {
1841             case ELLIPSIZE_START:
1842                 setEllipsize(TextUtils.TruncateAt.START);
1843                 break;
1844             case ELLIPSIZE_MIDDLE:
1845                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1846                 break;
1847             case ELLIPSIZE_END:
1848                 setEllipsize(TextUtils.TruncateAt.END);
1849                 break;
1850             case ELLIPSIZE_MARQUEE:
1851                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1852                     setHorizontalFadingEdgeEnabled(true);
1853                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1854                 } else {
1855                     setHorizontalFadingEdgeEnabled(false);
1856                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1857                 }
1858                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1859                 break;
1860         }
1861 
1862         final boolean isPassword = password || passwordInputType || webPasswordInputType
1863                 || numberPasswordInputType;
1864         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1865                 && (mEditor.mInputType
1866                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1867                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1868         if (isMonospaceEnforced) {
1869             attributes.mTypefaceIndex = MONOSPACE;
1870         }
1871 
1872         mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment;
1873         applyTextAppearance(attributes);
1874 
1875         if (isPassword) {
1876             setTransformationMethod(PasswordTransformationMethod.getInstance());
1877         }
1878 
1879         // For addressing b/145128646
1880         // For the performance reason, we limit characters for single line text field.
1881         if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) {
1882             mSingleLineLengthFilter = new InputFilter.LengthFilter(
1883                 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
1884         }
1885 
1886         if (mSingleLineLengthFilter != null) {
1887             setFilters(new InputFilter[] { mSingleLineLengthFilter });
1888         } else if (maxlength >= 0) {
1889             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1890         } else {
1891             setFilters(NO_FILTERS);
1892         }
1893 
1894         setText(text, bufferType);
1895         if (mText == null) {
1896             mText = "";
1897         }
1898         if (mTransformed == null) {
1899             mTransformed = "";
1900         }
1901 
1902         if (textIsSetFromXml) {
1903             mTextSetFromXmlOrResourceId = true;
1904         }
1905 
1906         if (hint != null) setHint(hint);
1907 
1908         /*
1909          * Views are not normally clickable unless specified to be.
1910          * However, TextViews that have input or movement methods *are*
1911          * clickable by default. By setting clickable here, we implicitly set focusable as well
1912          * if not overridden by the developer.
1913          */
1914         a = context.obtainStyledAttributes(
1915                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1916         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1917         boolean clickable = canInputOrMove || isClickable();
1918         boolean longClickable = canInputOrMove || isLongClickable();
1919         int focusable = getFocusable();
1920         boolean isAutoHandwritingEnabled = true;
1921 
1922         n = a.getIndexCount();
1923         for (int i = 0; i < n; i++) {
1924             int attr = a.getIndex(i);
1925 
1926             switch (attr) {
1927                 case com.android.internal.R.styleable.View_focusable:
1928                     TypedValue val = new TypedValue();
1929                     if (a.getValue(attr, val)) {
1930                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1931                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1932                                 : val.data;
1933                     }
1934                     break;
1935 
1936                 case com.android.internal.R.styleable.View_clickable:
1937                     clickable = a.getBoolean(attr, clickable);
1938                     break;
1939 
1940                 case com.android.internal.R.styleable.View_longClickable:
1941                     longClickable = a.getBoolean(attr, longClickable);
1942                     break;
1943 
1944                 case com.android.internal.R.styleable.View_autoHandwritingEnabled:
1945                     isAutoHandwritingEnabled = a.getBoolean(attr, true);
1946                     break;
1947             }
1948         }
1949         a.recycle();
1950 
1951         // Some apps were relying on the undefined behavior of focusable winning over
1952         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1953         // when starting with EditText and setting only focusable=false). To keep those apps from
1954         // breaking, re-apply the focusable attribute here.
1955         if (focusable != getFocusable()) {
1956             setFocusable(focusable);
1957         }
1958         setClickable(clickable);
1959         setLongClickable(longClickable);
1960         setAutoHandwritingEnabled(isAutoHandwritingEnabled);
1961 
1962         if (mEditor != null) mEditor.prepareCursorControllers();
1963 
1964         // If not explicitly specified this view is important for accessibility.
1965         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1966             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1967         }
1968 
1969         if (supportsAutoSizeText()) {
1970             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1971                 // If uniform auto-size has been specified but preset values have not been set then
1972                 // replace the auto-size configuration values that have not been specified with the
1973                 // defaults.
1974                 if (!mHasPresetAutoSizeValues) {
1975                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1976 
1977                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1978                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1979                                 TypedValue.COMPLEX_UNIT_SP,
1980                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1981                                 displayMetrics);
1982                     }
1983 
1984                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1985                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1986                                 TypedValue.COMPLEX_UNIT_SP,
1987                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1988                                 displayMetrics);
1989                     }
1990 
1991                     if (autoSizeStepGranularityInPx
1992                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1993                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1994                     }
1995 
1996                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1997                             autoSizeMaxTextSizeInPx,
1998                             autoSizeStepGranularityInPx);
1999                 }
2000 
2001                 setupAutoSizeText();
2002             }
2003         } else {
2004             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2005         }
2006 
2007         if (firstBaselineToTopHeight >= 0) {
2008             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
2009         }
2010         if (lastBaselineToBottomHeight >= 0) {
2011             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
2012         }
2013         if (lineHeight >= 0) {
2014             if (lineHeightUnit == -1) {
2015                 setLineHeightPx(lineHeight);
2016             } else {
2017                 setLineHeight(lineHeightUnit, lineHeight);
2018             }
2019         }
2020     }
2021 
2022     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)2023     private void setTextInternal(@Nullable CharSequence text) {
2024         mText = text;
2025         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
2026         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
2027     }
2028 
2029     /**
2030      * Specify whether this widget should automatically scale the text to try to perfectly fit
2031      * within the layout bounds by using the default auto-size configuration.
2032      *
2033      * @param autoSizeTextType the type of auto-size. Must be one of
2034      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
2035      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
2036      *
2037      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
2038      *
2039      * @attr ref android.R.styleable#TextView_autoSizeTextType
2040      *
2041      * @see #getAutoSizeTextType()
2042      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)2043     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
2044         if (supportsAutoSizeText()) {
2045             switch (autoSizeTextType) {
2046                 case AUTO_SIZE_TEXT_TYPE_NONE:
2047                     clearAutoSizeConfiguration();
2048                     break;
2049                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
2050                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2051                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
2052                             TypedValue.COMPLEX_UNIT_SP,
2053                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
2054                             displayMetrics);
2055                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
2056                             TypedValue.COMPLEX_UNIT_SP,
2057                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
2058                             displayMetrics);
2059 
2060                     validateAndSetAutoSizeTextTypeUniformConfiguration(
2061                             autoSizeMinTextSizeInPx,
2062                             autoSizeMaxTextSizeInPx,
2063                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
2064                     if (setupAutoSizeText()) {
2065                         autoSizeText();
2066                         invalidate();
2067                     }
2068                     break;
2069                 default:
2070                     throw new IllegalArgumentException(
2071                             "Unknown auto-size text type: " + autoSizeTextType);
2072             }
2073         }
2074     }
2075 
2076     /**
2077      * Specify whether this widget should automatically scale the text to try to perfectly fit
2078      * within the layout bounds. If all the configuration params are valid the type of auto-size is
2079      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
2080      *
2081      * @param autoSizeMinTextSize the minimum text size available for auto-size
2082      * @param autoSizeMaxTextSize the maximum text size available for auto-size
2083      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
2084      *                                the minimum and maximum text size in order to build the set of
2085      *                                text sizes the system uses to choose from when auto-sizing
2086      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
2087      *             possible dimension units
2088      *
2089      * @throws IllegalArgumentException if any of the configuration params are invalid.
2090      *
2091      * @attr ref android.R.styleable#TextView_autoSizeTextType
2092      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2093      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2094      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
2095      *
2096      * @see #setAutoSizeTextTypeWithDefaults(int)
2097      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2098      * @see #getAutoSizeMinTextSize()
2099      * @see #getAutoSizeMaxTextSize()
2100      * @see #getAutoSizeStepGranularity()
2101      * @see #getAutoSizeTextAvailableSizes()
2102      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)2103     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
2104             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
2105         if (supportsAutoSizeText()) {
2106             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2107             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
2108                     unit, autoSizeMinTextSize, displayMetrics);
2109             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
2110                     unit, autoSizeMaxTextSize, displayMetrics);
2111             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
2112                     unit, autoSizeStepGranularity, displayMetrics);
2113 
2114             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
2115                     autoSizeMaxTextSizeInPx,
2116                     autoSizeStepGranularityInPx);
2117 
2118             if (setupAutoSizeText()) {
2119                 autoSizeText();
2120                 invalidate();
2121             }
2122         }
2123     }
2124 
2125     /**
2126      * Specify whether this widget should automatically scale the text to try to perfectly fit
2127      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
2128      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
2129      *
2130      * @param presetSizes an {@code int} array of sizes in pixels
2131      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
2132      *             the possible dimension units
2133      *
2134      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
2135      *
2136      * @attr ref android.R.styleable#TextView_autoSizeTextType
2137      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
2138      *
2139      * @see #setAutoSizeTextTypeWithDefaults(int)
2140      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2141      * @see #getAutoSizeMinTextSize()
2142      * @see #getAutoSizeMaxTextSize()
2143      * @see #getAutoSizeTextAvailableSizes()
2144      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)2145     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
2146         if (supportsAutoSizeText()) {
2147             final int presetSizesLength = presetSizes.length;
2148             if (presetSizesLength > 0) {
2149                 int[] presetSizesInPx = new int[presetSizesLength];
2150 
2151                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
2152                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
2153                 } else {
2154                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2155                     // Convert all to sizes to pixels.
2156                     for (int i = 0; i < presetSizesLength; i++) {
2157                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
2158                             presetSizes[i], displayMetrics));
2159                     }
2160                 }
2161 
2162                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
2163                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
2164                     throw new IllegalArgumentException("None of the preset sizes is valid: "
2165                             + Arrays.toString(presetSizes));
2166                 }
2167             } else {
2168                 mHasPresetAutoSizeValues = false;
2169             }
2170 
2171             if (setupAutoSizeText()) {
2172                 autoSizeText();
2173                 invalidate();
2174             }
2175         }
2176     }
2177 
2178     /**
2179      * Returns the type of auto-size set for this widget.
2180      *
2181      * @return an {@code int} corresponding to one of the auto-size types:
2182      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
2183      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
2184      *
2185      * @attr ref android.R.styleable#TextView_autoSizeTextType
2186      *
2187      * @see #setAutoSizeTextTypeWithDefaults(int)
2188      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2189      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2190      */
2191     @InspectableProperty(enumMapping = {
2192             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
2193             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
2194     })
2195     @AutoSizeTextType
getAutoSizeTextType()2196     public int getAutoSizeTextType() {
2197         return mAutoSizeTextType;
2198     }
2199 
2200     /**
2201      * @return the current auto-size step granularity in pixels.
2202      *
2203      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
2204      *
2205      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2206      */
2207     @InspectableProperty
getAutoSizeStepGranularity()2208     public int getAutoSizeStepGranularity() {
2209         return Math.round(mAutoSizeStepGranularityInPx);
2210     }
2211 
2212     /**
2213      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
2214      *         if auto-size has not been configured this function returns {@code -1}.
2215      *
2216      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2217      *
2218      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2219      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2220      */
2221     @InspectableProperty
getAutoSizeMinTextSize()2222     public int getAutoSizeMinTextSize() {
2223         return Math.round(mAutoSizeMinTextSizeInPx);
2224     }
2225 
2226     /**
2227      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
2228      *         if auto-size has not been configured this function returns {@code -1}.
2229      *
2230      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2231      *
2232      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2233      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2234      */
2235     @InspectableProperty
getAutoSizeMaxTextSize()2236     public int getAutoSizeMaxTextSize() {
2237         return Math.round(mAutoSizeMaxTextSizeInPx);
2238     }
2239 
2240     /**
2241      * @return the current auto-size {@code int} sizes array (in pixels).
2242      *
2243      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2244      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2245      */
getAutoSizeTextAvailableSizes()2246     public int[] getAutoSizeTextAvailableSizes() {
2247         return mAutoSizeTextSizesInPx;
2248     }
2249 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)2250     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
2251         final int textSizesLength = textSizes.length();
2252         final int[] parsedSizes = new int[textSizesLength];
2253 
2254         if (textSizesLength > 0) {
2255             for (int i = 0; i < textSizesLength; i++) {
2256                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
2257             }
2258             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
2259             setupAutoSizeUniformPresetSizesConfiguration();
2260         }
2261     }
2262 
setupAutoSizeUniformPresetSizesConfiguration()2263     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
2264         final int sizesLength = mAutoSizeTextSizesInPx.length;
2265         mHasPresetAutoSizeValues = sizesLength > 0;
2266         if (mHasPresetAutoSizeValues) {
2267             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2268             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
2269             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
2270             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2271         }
2272         return mHasPresetAutoSizeValues;
2273     }
2274 
2275     /**
2276      * If all params are valid then save the auto-size configuration.
2277      *
2278      * @throws IllegalArgumentException if any of the params are invalid
2279      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2280     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
2281             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
2282         // First validate.
2283         if (autoSizeMinTextSizeInPx <= 0) {
2284             throw new IllegalArgumentException("Minimum auto-size text size ("
2285                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
2286         }
2287 
2288         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2289             throw new IllegalArgumentException("Maximum auto-size text size ("
2290                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2291                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2292         }
2293 
2294         if (autoSizeStepGranularityInPx <= 0) {
2295             throw new IllegalArgumentException("The auto-size step granularity ("
2296                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2297         }
2298 
2299         // All good, persist the configuration.
2300         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2301         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2302         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2303         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2304         mHasPresetAutoSizeValues = false;
2305     }
2306 
clearAutoSizeConfiguration()2307     private void clearAutoSizeConfiguration() {
2308         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2309         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2310         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2311         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2312         mAutoSizeTextSizesInPx = EmptyArray.INT;
2313         mNeedsAutoSizeText = false;
2314     }
2315 
2316     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)2317     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2318         final int presetValuesLength = presetValues.length;
2319         if (presetValuesLength == 0) {
2320             return presetValues;
2321         }
2322         Arrays.sort(presetValues);
2323 
2324         final IntArray uniqueValidSizes = new IntArray();
2325         for (int i = 0; i < presetValuesLength; i++) {
2326             final int currentPresetValue = presetValues[i];
2327 
2328             if (currentPresetValue > 0
2329                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2330                 uniqueValidSizes.add(currentPresetValue);
2331             }
2332         }
2333 
2334         return presetValuesLength == uniqueValidSizes.size()
2335             ? presetValues
2336             : uniqueValidSizes.toArray();
2337     }
2338 
setupAutoSizeText()2339     private boolean setupAutoSizeText() {
2340         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2341             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2342             // not have a predefined set of sizes or if the current sizes array is empty.
2343             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2344                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2345                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2346                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2347                 for (int i = 0; i < autoSizeValuesLength; i++) {
2348                     autoSizeTextSizesInPx[i] = Math.round(
2349                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2350                 }
2351                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2352             }
2353 
2354             mNeedsAutoSizeText = true;
2355         } else {
2356             mNeedsAutoSizeText = false;
2357         }
2358 
2359         return mNeedsAutoSizeText;
2360     }
2361 
parseDimensionArray(TypedArray dimens)2362     private int[] parseDimensionArray(TypedArray dimens) {
2363         if (dimens == null) {
2364             return null;
2365         }
2366         int[] result = new int[dimens.length()];
2367         for (int i = 0; i < result.length; i++) {
2368             result[i] = dimens.getDimensionPixelSize(i, 0);
2369         }
2370         return result;
2371     }
2372 
2373     /**
2374      * @hide
2375      */
2376     @TestApi
2377     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)2378     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
2379         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2380             if (resultCode == Activity.RESULT_OK && data != null) {
2381                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2382                 if (result != null) {
2383                     if (isTextEditable()) {
2384                         ClipData clip = ClipData.newPlainText("", result);
2385                         ContentInfo payload =
2386                                 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build();
2387                         performReceiveContent(payload);
2388                         if (mEditor != null) {
2389                             mEditor.refreshTextActionMode();
2390                         }
2391                     } else {
2392                         if (result.length() > 0) {
2393                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2394                                 .show();
2395                         }
2396                     }
2397                 }
2398             } else if (mSpannable != null) {
2399                 // Reset the selection.
2400                 Selection.setSelection(mSpannable, getSelectionEnd());
2401             }
2402         }
2403     }
2404 
2405     /**
2406      * Sets the Typeface taking into account the given attributes.
2407      *
2408      * @param typeface a typeface
2409      * @param familyName family name string, e.g. "serif"
2410      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2411      * @param style a typeface style
2412      * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED}
2413      *               if not specified.
2414      */
setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)2415     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2416             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2417             @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
2418                     int weight) {
2419         if (typeface == null && familyName != null) {
2420             // Lookup normal Typeface from system font map.
2421             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2422             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2423         } else if (typeface != null) {
2424             resolveStyleAndSetTypeface(typeface, style, weight);
2425         } else {  // both typeface and familyName is null.
2426             switch (typefaceIndex) {
2427                 case SANS:
2428                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2429                     break;
2430                 case SERIF:
2431                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2432                     break;
2433                 case MONOSPACE:
2434                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2435                     break;
2436                 case DEFAULT_TYPEFACE:
2437                 default:
2438                     resolveStyleAndSetTypeface(null, style, weight);
2439                     break;
2440             }
2441         }
2442     }
2443 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)2444     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2445             @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
2446                     int weight) {
2447         if (weight >= 0) {
2448             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2449             final boolean italic = (style & Typeface.ITALIC) != 0;
2450             setTypeface(Typeface.create(typeface, weight, italic));
2451         } else {
2452             setTypeface(typeface, style);
2453         }
2454     }
2455 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2456     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2457         boolean hasRelativeDrawables = (start != null) || (end != null);
2458         if (hasRelativeDrawables) {
2459             Drawables dr = mDrawables;
2460             if (dr == null) {
2461                 mDrawables = dr = new Drawables(getContext());
2462             }
2463             mDrawables.mOverride = true;
2464             final Rect compoundRect = dr.mCompoundRect;
2465             int[] state = getDrawableState();
2466             if (start != null) {
2467                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2468                 start.setState(state);
2469                 start.copyBounds(compoundRect);
2470                 start.setCallback(this);
2471 
2472                 dr.mDrawableStart = start;
2473                 dr.mDrawableSizeStart = compoundRect.width();
2474                 dr.mDrawableHeightStart = compoundRect.height();
2475             } else {
2476                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2477             }
2478             if (end != null) {
2479                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2480                 end.setState(state);
2481                 end.copyBounds(compoundRect);
2482                 end.setCallback(this);
2483 
2484                 dr.mDrawableEnd = end;
2485                 dr.mDrawableSizeEnd = compoundRect.width();
2486                 dr.mDrawableHeightEnd = compoundRect.height();
2487             } else {
2488                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2489             }
2490             resetResolvedDrawables();
2491             resolveDrawables();
2492             applyCompoundDrawableTint();
2493         }
2494     }
2495 
2496     @android.view.RemotableViewMethod
2497     @Override
setEnabled(boolean enabled)2498     public void setEnabled(boolean enabled) {
2499         if (enabled == isEnabled()) {
2500             return;
2501         }
2502 
2503         if (!enabled) {
2504             // Hide the soft input if the currently active TextView is disabled
2505             InputMethodManager imm = getInputMethodManager();
2506             if (imm != null) {
2507                 imm.hideSoftInputFromView(this, 0);
2508             }
2509         }
2510 
2511         super.setEnabled(enabled);
2512 
2513         if (enabled) {
2514             // Make sure IME is updated with current editor info.
2515             InputMethodManager imm = getInputMethodManager();
2516             if (imm != null) imm.restartInput(this);
2517         }
2518 
2519         // Will change text color
2520         if (mEditor != null) {
2521             mEditor.invalidateTextDisplayList();
2522             mEditor.prepareCursorControllers();
2523 
2524             // start or stop the cursor blinking as appropriate
2525             mEditor.makeBlink();
2526         }
2527     }
2528 
2529     /**
2530      * Sets the typeface and style in which the text should be displayed,
2531      * and turns on the fake bold and italic bits in the Paint if the
2532      * Typeface that you provided does not have all the bits in the
2533      * style that you specified.
2534      *
2535      * @attr ref android.R.styleable#TextView_typeface
2536      * @attr ref android.R.styleable#TextView_textStyle
2537      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2538     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2539         if (style > 0) {
2540             if (tf == null) {
2541                 tf = Typeface.defaultFromStyle(style);
2542             } else {
2543                 tf = Typeface.create(tf, style);
2544             }
2545 
2546             setTypeface(tf);
2547             // now compute what (if any) algorithmic styling is needed
2548             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2549             int need = style & ~typefaceStyle;
2550             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2551             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2552         } else {
2553             mTextPaint.setFakeBoldText(false);
2554             mTextPaint.setTextSkewX(0);
2555             setTypeface(tf);
2556         }
2557     }
2558 
2559     /**
2560      * Subclasses override this to specify that they have a KeyListener
2561      * by default even if not specifically called for in the XML options.
2562      */
getDefaultEditable()2563     protected boolean getDefaultEditable() {
2564         return false;
2565     }
2566 
2567     /**
2568      * Subclasses override this to specify a default movement method.
2569      */
getDefaultMovementMethod()2570     protected MovementMethod getDefaultMovementMethod() {
2571         return null;
2572     }
2573 
2574     /**
2575      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2576      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2577      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2578      * the return value from this method to Spannable or Editable, respectively.
2579      *
2580      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2581      * should make your own copy first.</p>
2582      *
2583      * @return The text displayed by the text view.
2584      * @attr ref android.R.styleable#TextView_text
2585      */
2586     @ViewDebug.CapturedViewProperty
2587     @InspectableProperty
getText()2588     public CharSequence getText() {
2589         if (mUseTextPaddingForUiTranslation) {
2590             ViewTranslationCallback callback = getViewTranslationCallback();
2591             if (callback != null && callback instanceof TextViewTranslationCallback) {
2592                 TextViewTranslationCallback defaultCallback =
2593                         (TextViewTranslationCallback) callback;
2594                 if (defaultCallback.isTextPaddingEnabled()
2595                         && defaultCallback.isShowingTranslation()) {
2596                     return defaultCallback.getPaddedText(mText, mTransformed);
2597                 }
2598             }
2599         }
2600         return mText;
2601     }
2602 
2603     /**
2604      * Returns the length, in characters, of the text managed by this TextView
2605      * @return The length of the text managed by the TextView in characters.
2606      */
length()2607     public int length() {
2608         return mText.length();
2609     }
2610 
2611     /**
2612      * Return the text that TextView is displaying as an Editable object. If the text is not
2613      * editable, null is returned.
2614      *
2615      * @see #getText
2616      */
getEditableText()2617     public Editable getEditableText() {
2618         return (mText instanceof Editable) ? (Editable) mText : null;
2619     }
2620 
2621     /**
2622      * @hide
2623      */
2624     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getTransformed()2625     public CharSequence getTransformed() {
2626         return mTransformed;
2627     }
2628 
2629     /**
2630      * Gets the vertical distance between lines of text, in pixels.
2631      * Note that markup within the text can cause individual lines
2632      * to be taller or shorter than this height, and the layout may
2633      * contain additional first-or last-line padding.
2634      * @return The height of one standard line in pixels.
2635      */
2636     @InspectableProperty
getLineHeight()2637     public int getLineHeight() {
2638         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2639     }
2640 
2641     /**
2642      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2643      * This value can be null if the text or width has recently changed.
2644      * @return The Layout that is currently being used to display the text.
2645      */
getLayout()2646     public final Layout getLayout() {
2647         return mLayout;
2648     }
2649 
2650     /**
2651      * @return the {@link android.text.Layout} that is currently being used to
2652      * display the hint text. This can be null.
2653      */
2654     @UnsupportedAppUsage
getHintLayout()2655     final Layout getHintLayout() {
2656         return mHintLayout;
2657     }
2658 
2659     /**
2660      * Retrieve the {@link android.content.UndoManager} that is currently associated
2661      * with this TextView.  By default there is no associated UndoManager, so null
2662      * is returned.  One can be associated with the TextView through
2663      * {@link #setUndoManager(android.content.UndoManager, String)}
2664      *
2665      * @hide
2666      */
getUndoManager()2667     public final UndoManager getUndoManager() {
2668         // TODO: Consider supporting a global undo manager.
2669         throw new UnsupportedOperationException("not implemented");
2670     }
2671 
2672 
2673     /**
2674      * @hide
2675      */
2676     @VisibleForTesting
getEditorForTesting()2677     public final Editor getEditorForTesting() {
2678         return mEditor;
2679     }
2680 
2681     /**
2682      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2683      * done, all edit operations on the TextView will result in appropriate
2684      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2685      * stack.
2686      *
2687      * @param undoManager The {@link android.content.UndoManager} to associate with
2688      * this TextView, or null to clear any existing association.
2689      * @param tag String tag identifying this particular TextView owner in the
2690      * UndoManager.  This is used to keep the correct association with the
2691      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2692      *
2693      * @hide
2694      */
setUndoManager(UndoManager undoManager, String tag)2695     public final void setUndoManager(UndoManager undoManager, String tag) {
2696         // TODO: Consider supporting a global undo manager. An implementation will need to:
2697         // * createEditorIfNeeded()
2698         // * Promote to BufferType.EDITABLE if needed.
2699         // * Update the UndoManager and UndoOwner.
2700         // Likewise it will need to be able to restore the default UndoManager.
2701         throw new UnsupportedOperationException("not implemented");
2702     }
2703 
2704     /**
2705      * Gets the current {@link KeyListener} for the TextView.
2706      * This will frequently be null for non-EditText TextViews.
2707      * @return the current key listener for this TextView.
2708      *
2709      * @attr ref android.R.styleable#TextView_numeric
2710      * @attr ref android.R.styleable#TextView_digits
2711      * @attr ref android.R.styleable#TextView_phoneNumber
2712      * @attr ref android.R.styleable#TextView_inputMethod
2713      * @attr ref android.R.styleable#TextView_capitalize
2714      * @attr ref android.R.styleable#TextView_autoText
2715      */
getKeyListener()2716     public final KeyListener getKeyListener() {
2717         return mEditor == null ? null : mEditor.mKeyListener;
2718     }
2719 
2720     /**
2721      * Sets the key listener to be used with this TextView.  This can be null
2722      * to disallow user input.  Note that this method has significant and
2723      * subtle interactions with soft keyboards and other input method:
2724      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2725      * for important details.  Calling this method will replace the current
2726      * content type of the text view with the content type returned by the
2727      * key listener.
2728      * <p>
2729      * Be warned that if you want a TextView with a key listener or movement
2730      * method not to be focusable, or if you want a TextView without a
2731      * key listener or movement method to be focusable, you must call
2732      * {@link #setFocusable} again after calling this to get the focusability
2733      * back the way you want it.
2734      *
2735      * @attr ref android.R.styleable#TextView_numeric
2736      * @attr ref android.R.styleable#TextView_digits
2737      * @attr ref android.R.styleable#TextView_phoneNumber
2738      * @attr ref android.R.styleable#TextView_inputMethod
2739      * @attr ref android.R.styleable#TextView_capitalize
2740      * @attr ref android.R.styleable#TextView_autoText
2741      */
setKeyListener(KeyListener input)2742     public void setKeyListener(KeyListener input) {
2743         mListenerChanged = true;
2744         setKeyListenerOnly(input);
2745         fixFocusableAndClickableSettings();
2746 
2747         if (input != null) {
2748             createEditorIfNeeded();
2749             setInputTypeFromEditor();
2750         } else {
2751             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2752         }
2753 
2754         InputMethodManager imm = getInputMethodManager();
2755         if (imm != null) imm.restartInput(this);
2756 
2757         ensureEditorFocusedNotifiedToHandwritingInitiator();
2758     }
2759 
setInputTypeFromEditor()2760     private void setInputTypeFromEditor() {
2761         try {
2762             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2763         } catch (IncompatibleClassChangeError e) {
2764             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2765         }
2766         // Change inputType, without affecting transformation.
2767         // No need to applySingleLine since mSingleLine is unchanged.
2768         setInputTypeSingleLine(mSingleLine);
2769     }
2770 
setKeyListenerOnly(KeyListener input)2771     private void setKeyListenerOnly(KeyListener input) {
2772         if (mEditor == null && input == null) return; // null is the default value
2773 
2774         createEditorIfNeeded();
2775         if (mEditor.mKeyListener != input) {
2776             mEditor.mKeyListener = input;
2777             if (input != null && !(mText instanceof Editable)) {
2778                 setText(mText);
2779             }
2780 
2781             setFilters((Editable) mText, mFilters);
2782         }
2783     }
2784 
2785     /**
2786      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2787      * which provides positioning, scrolling, and text selection functionality.
2788      * This will frequently be null for non-EditText TextViews.
2789      * @return the movement method being used for this TextView.
2790      * @see android.text.method.MovementMethod
2791      */
getMovementMethod()2792     public final MovementMethod getMovementMethod() {
2793         return mMovement;
2794     }
2795 
2796     /**
2797      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2798      * for this TextView. This can be null to disallow using the arrow keys to move the
2799      * cursor or scroll the view.
2800      * <p>
2801      * Be warned that if you want a TextView with a key listener or movement
2802      * method not to be focusable, or if you want a TextView without a
2803      * key listener or movement method to be focusable, you must call
2804      * {@link #setFocusable} again after calling this to get the focusability
2805      * back the way you want it.
2806      */
setMovementMethod(MovementMethod movement)2807     public final void setMovementMethod(MovementMethod movement) {
2808         if (mMovement != movement) {
2809             mMovement = movement;
2810 
2811             if (movement != null && mSpannable == null) {
2812                 setText(mText);
2813             }
2814 
2815             fixFocusableAndClickableSettings();
2816 
2817             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2818             // mMovement
2819             if (mEditor != null) mEditor.prepareCursorControllers();
2820         }
2821     }
2822 
fixFocusableAndClickableSettings()2823     private void fixFocusableAndClickableSettings() {
2824         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2825             setFocusable(FOCUSABLE);
2826             setClickable(true);
2827             setLongClickable(true);
2828         } else {
2829             setFocusable(FOCUSABLE_AUTO);
2830             setClickable(false);
2831             setLongClickable(false);
2832         }
2833     }
2834 
2835     /**
2836      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2837      * This is frequently null, except for single-line and password fields.
2838      * @return the current transformation method for this TextView.
2839      *
2840      * @attr ref android.R.styleable#TextView_password
2841      * @attr ref android.R.styleable#TextView_singleLine
2842      */
getTransformationMethod()2843     public final TransformationMethod getTransformationMethod() {
2844         return mTransformation;
2845     }
2846 
2847     /**
2848      * Sets the transformation that is applied to the text that this
2849      * TextView is displaying.
2850      *
2851      * @attr ref android.R.styleable#TextView_password
2852      * @attr ref android.R.styleable#TextView_singleLine
2853      */
setTransformationMethod(TransformationMethod method)2854     public final void setTransformationMethod(TransformationMethod method) {
2855         if (mEditor != null) {
2856             mEditor.setTransformationMethod(method);
2857         } else {
2858             setTransformationMethodInternal(method, /* updateText */ true);
2859         }
2860     }
2861 
2862     /**
2863      * Set the transformation that is applied to the text that this TextView is displaying,
2864      * optionally call the setText.
2865      * @param method the new transformation method to be set.
2866      * @param updateText whether the call {@link #setText} which will update the TextView to display
2867      *                   the new content. This method is helpful when updating
2868      *                   {@link TransformationMethod} inside {@link #setText}. It should only be
2869      *                   false if text will be updated immediately after this call, otherwise the
2870      *                   TextView will enter an inconsistent state.
2871      */
setTransformationMethodInternal(@ullable TransformationMethod method, boolean updateText)2872     void setTransformationMethodInternal(@Nullable TransformationMethod method,
2873             boolean updateText) {
2874         if (method == mTransformation) {
2875             // Avoid the setText() below if the transformation is
2876             // the same.
2877             return;
2878         }
2879         if (mTransformation != null) {
2880             if (mSpannable != null) {
2881                 mSpannable.removeSpan(mTransformation);
2882             }
2883         }
2884 
2885         mTransformation = method;
2886 
2887         if (method instanceof TransformationMethod2) {
2888             TransformationMethod2 method2 = (TransformationMethod2) method;
2889             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2890             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2891         } else {
2892             mAllowTransformationLengthChange = false;
2893         }
2894 
2895         if (updateText) {
2896             if (Flags.insertModeNotUpdateSelection()) {
2897                 // Update the transformation text.
2898                 if (mTransformation == null) {
2899                     mTransformed = mText;
2900                 } else {
2901                     mTransformed = mTransformation.getTransformation(mText, this);
2902                 }
2903                 if (mTransformed == null) {
2904                     // Should not happen if the transformation method follows the non-null
2905                     // postcondition.
2906                     mTransformed = "";
2907                 }
2908                 final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
2909 
2910                 // If the mText is a Spannable and the new TransformationMethod needs to listen to
2911                 // its updates, apply the watcher on it.
2912                 if (mTransformation != null && mText instanceof Spannable
2913                         && (!mAllowTransformationLengthChange || isOffsetMapping)) {
2914                     Spannable sp = (Spannable) mText;
2915                     final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
2916                     sp.setSpan(mTransformation, 0, mText.length(),
2917                             Spanned.SPAN_INCLUSIVE_INCLUSIVE
2918                                     | (priority << Spanned.SPAN_PRIORITY_SHIFT));
2919                 }
2920                 if (mLayout != null) {
2921                     nullLayouts();
2922                     requestLayout();
2923                     invalidate();
2924                 }
2925             } else {
2926                 setText(mText);
2927             }
2928         }
2929 
2930         if (hasPasswordTransformationMethod()) {
2931             notifyViewAccessibilityStateChangedIfNeeded(
2932                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2933         }
2934 
2935         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2936         // getTextDirectionHeuristic, needs reset
2937         mTextDir = getTextDirectionHeuristic();
2938     }
2939 
2940     /**
2941      * Returns the top padding of the view, plus space for the top
2942      * Drawable if any.
2943      */
getCompoundPaddingTop()2944     public int getCompoundPaddingTop() {
2945         final Drawables dr = mDrawables;
2946         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2947             return mPaddingTop;
2948         } else {
2949             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2950         }
2951     }
2952 
2953     /**
2954      * Returns the bottom padding of the view, plus space for the bottom
2955      * Drawable if any.
2956      */
getCompoundPaddingBottom()2957     public int getCompoundPaddingBottom() {
2958         final Drawables dr = mDrawables;
2959         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2960             return mPaddingBottom;
2961         } else {
2962             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2963         }
2964     }
2965 
2966     /**
2967      * Returns the left padding of the view, plus space for the left
2968      * Drawable if any.
2969      */
getCompoundPaddingLeft()2970     public int getCompoundPaddingLeft() {
2971         final Drawables dr = mDrawables;
2972         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2973             return mPaddingLeft;
2974         } else {
2975             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2976         }
2977     }
2978 
2979     /**
2980      * Returns the right padding of the view, plus space for the right
2981      * Drawable if any.
2982      */
getCompoundPaddingRight()2983     public int getCompoundPaddingRight() {
2984         final Drawables dr = mDrawables;
2985         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2986             return mPaddingRight;
2987         } else {
2988             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2989         }
2990     }
2991 
2992     /**
2993      * Returns the start padding of the view, plus space for the start
2994      * Drawable if any.
2995      */
getCompoundPaddingStart()2996     public int getCompoundPaddingStart() {
2997         resolveDrawables();
2998         switch(getLayoutDirection()) {
2999             default:
3000             case LAYOUT_DIRECTION_LTR:
3001                 return getCompoundPaddingLeft();
3002             case LAYOUT_DIRECTION_RTL:
3003                 return getCompoundPaddingRight();
3004         }
3005     }
3006 
3007     /**
3008      * Returns the end padding of the view, plus space for the end
3009      * Drawable if any.
3010      */
getCompoundPaddingEnd()3011     public int getCompoundPaddingEnd() {
3012         resolveDrawables();
3013         switch(getLayoutDirection()) {
3014             default:
3015             case LAYOUT_DIRECTION_LTR:
3016                 return getCompoundPaddingRight();
3017             case LAYOUT_DIRECTION_RTL:
3018                 return getCompoundPaddingLeft();
3019         }
3020     }
3021 
3022     /**
3023      * Returns the extended top padding of the view, including both the
3024      * top Drawable if any and any extra space to keep more than maxLines
3025      * of text from showing.  It is only valid to call this after measuring.
3026      */
getExtendedPaddingTop()3027     public int getExtendedPaddingTop() {
3028         if (mMaxMode != LINES) {
3029             return getCompoundPaddingTop();
3030         }
3031 
3032         if (mLayout == null) {
3033             assumeLayout();
3034         }
3035 
3036         if (mLayout.getLineCount() <= mMaximum) {
3037             return getCompoundPaddingTop();
3038         }
3039 
3040         int top = getCompoundPaddingTop();
3041         int bottom = getCompoundPaddingBottom();
3042         int viewht = getHeight() - top - bottom;
3043         int layoutht = mLayout.getLineTop(mMaximum);
3044 
3045         if (layoutht >= viewht) {
3046             return top;
3047         }
3048 
3049         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3050         if (gravity == Gravity.TOP) {
3051             return top;
3052         } else if (gravity == Gravity.BOTTOM) {
3053             return top + viewht - layoutht;
3054         } else { // (gravity == Gravity.CENTER_VERTICAL)
3055             return top + (viewht - layoutht) / 2;
3056         }
3057     }
3058 
3059     /**
3060      * Returns the extended bottom padding of the view, including both the
3061      * bottom Drawable if any and any extra space to keep more than maxLines
3062      * of text from showing.  It is only valid to call this after measuring.
3063      */
getExtendedPaddingBottom()3064     public int getExtendedPaddingBottom() {
3065         if (mMaxMode != LINES) {
3066             return getCompoundPaddingBottom();
3067         }
3068 
3069         if (mLayout == null) {
3070             assumeLayout();
3071         }
3072 
3073         if (mLayout.getLineCount() <= mMaximum) {
3074             return getCompoundPaddingBottom();
3075         }
3076 
3077         int top = getCompoundPaddingTop();
3078         int bottom = getCompoundPaddingBottom();
3079         int viewht = getHeight() - top - bottom;
3080         int layoutht = mLayout.getLineTop(mMaximum);
3081 
3082         if (layoutht >= viewht) {
3083             return bottom;
3084         }
3085 
3086         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3087         if (gravity == Gravity.TOP) {
3088             return bottom + viewht - layoutht;
3089         } else if (gravity == Gravity.BOTTOM) {
3090             return bottom;
3091         } else { // (gravity == Gravity.CENTER_VERTICAL)
3092             return bottom + (viewht - layoutht) / 2;
3093         }
3094     }
3095 
3096     /**
3097      * Returns the total left padding of the view, including the left
3098      * Drawable if any.
3099      */
getTotalPaddingLeft()3100     public int getTotalPaddingLeft() {
3101         return getCompoundPaddingLeft();
3102     }
3103 
3104     /**
3105      * Returns the total right padding of the view, including the right
3106      * Drawable if any.
3107      */
getTotalPaddingRight()3108     public int getTotalPaddingRight() {
3109         return getCompoundPaddingRight();
3110     }
3111 
3112     /**
3113      * Returns the total start padding of the view, including the start
3114      * Drawable if any.
3115      */
getTotalPaddingStart()3116     public int getTotalPaddingStart() {
3117         return getCompoundPaddingStart();
3118     }
3119 
3120     /**
3121      * Returns the total end padding of the view, including the end
3122      * Drawable if any.
3123      */
getTotalPaddingEnd()3124     public int getTotalPaddingEnd() {
3125         return getCompoundPaddingEnd();
3126     }
3127 
3128     /**
3129      * Returns the total top padding of the view, including the top
3130      * Drawable if any, the extra space to keep more than maxLines
3131      * from showing, and the vertical offset for gravity, if any.
3132      */
getTotalPaddingTop()3133     public int getTotalPaddingTop() {
3134         return getExtendedPaddingTop() + getVerticalOffset(true);
3135     }
3136 
3137     /**
3138      * Returns the total bottom padding of the view, including the bottom
3139      * Drawable if any, the extra space to keep more than maxLines
3140      * from showing, and the vertical offset for gravity, if any.
3141      */
getTotalPaddingBottom()3142     public int getTotalPaddingBottom() {
3143         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
3144     }
3145 
3146     /**
3147      * Sets the Drawables (if any) to appear to the left of, above, to the
3148      * right of, and below the text. Use {@code null} if you do not want a
3149      * Drawable there. The Drawables must already have had
3150      * {@link Drawable#setBounds} called.
3151      * <p>
3152      * Calling this method will overwrite any Drawables previously set using
3153      * {@link #setCompoundDrawablesRelative} or related methods.
3154      *
3155      * @attr ref android.R.styleable#TextView_drawableLeft
3156      * @attr ref android.R.styleable#TextView_drawableTop
3157      * @attr ref android.R.styleable#TextView_drawableRight
3158      * @attr ref android.R.styleable#TextView_drawableBottom
3159      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3160     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
3161             @Nullable Drawable right, @Nullable Drawable bottom) {
3162         Drawables dr = mDrawables;
3163 
3164         // We're switching to absolute, discard relative.
3165         if (dr != null) {
3166             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3167             dr.mDrawableStart = null;
3168             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
3169             dr.mDrawableEnd = null;
3170             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3171             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3172         }
3173 
3174         final boolean drawables = left != null || top != null || right != null || bottom != null;
3175         if (!drawables) {
3176             // Clearing drawables...  can we free the data structure?
3177             if (dr != null) {
3178                 if (!dr.hasMetadata()) {
3179                     mDrawables = null;
3180                 } else {
3181                     // We need to retain the last set padding, so just clear
3182                     // out all of the fields in the existing structure.
3183                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
3184                         if (dr.mShowing[i] != null) {
3185                             dr.mShowing[i].setCallback(null);
3186                         }
3187                         dr.mShowing[i] = null;
3188                     }
3189                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3190                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3191                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3192                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3193                 }
3194             }
3195         } else {
3196             if (dr == null) {
3197                 mDrawables = dr = new Drawables(getContext());
3198             }
3199 
3200             mDrawables.mOverride = false;
3201 
3202             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
3203                 dr.mShowing[Drawables.LEFT].setCallback(null);
3204             }
3205             dr.mShowing[Drawables.LEFT] = left;
3206 
3207             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3208                 dr.mShowing[Drawables.TOP].setCallback(null);
3209             }
3210             dr.mShowing[Drawables.TOP] = top;
3211 
3212             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
3213                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3214             }
3215             dr.mShowing[Drawables.RIGHT] = right;
3216 
3217             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3218                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3219             }
3220             dr.mShowing[Drawables.BOTTOM] = bottom;
3221 
3222             final Rect compoundRect = dr.mCompoundRect;
3223             int[] state;
3224 
3225             state = getDrawableState();
3226 
3227             if (left != null) {
3228                 left.setState(state);
3229                 left.copyBounds(compoundRect);
3230                 left.setCallback(this);
3231                 dr.mDrawableSizeLeft = compoundRect.width();
3232                 dr.mDrawableHeightLeft = compoundRect.height();
3233             } else {
3234                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3235             }
3236 
3237             if (right != null) {
3238                 right.setState(state);
3239                 right.copyBounds(compoundRect);
3240                 right.setCallback(this);
3241                 dr.mDrawableSizeRight = compoundRect.width();
3242                 dr.mDrawableHeightRight = compoundRect.height();
3243             } else {
3244                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3245             }
3246 
3247             if (top != null) {
3248                 top.setState(state);
3249                 top.copyBounds(compoundRect);
3250                 top.setCallback(this);
3251                 dr.mDrawableSizeTop = compoundRect.height();
3252                 dr.mDrawableWidthTop = compoundRect.width();
3253             } else {
3254                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3255             }
3256 
3257             if (bottom != null) {
3258                 bottom.setState(state);
3259                 bottom.copyBounds(compoundRect);
3260                 bottom.setCallback(this);
3261                 dr.mDrawableSizeBottom = compoundRect.height();
3262                 dr.mDrawableWidthBottom = compoundRect.width();
3263             } else {
3264                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3265             }
3266         }
3267 
3268         // Save initial left/right drawables
3269         if (dr != null) {
3270             dr.mDrawableLeftInitial = left;
3271             dr.mDrawableRightInitial = right;
3272         }
3273 
3274         resetResolvedDrawables();
3275         resolveDrawables();
3276         applyCompoundDrawableTint();
3277         invalidate();
3278         requestLayout();
3279     }
3280 
3281     /**
3282      * Sets the Drawables (if any) to appear to the left of, above, to the
3283      * right of, and below the text. Use 0 if you do not want a Drawable there.
3284      * The Drawables' bounds will be set to their intrinsic bounds.
3285      * <p>
3286      * Calling this method will overwrite any Drawables previously set using
3287      * {@link #setCompoundDrawablesRelative} or related methods.
3288      *
3289      * @param left Resource identifier of the left Drawable.
3290      * @param top Resource identifier of the top Drawable.
3291      * @param right Resource identifier of the right Drawable.
3292      * @param bottom Resource identifier of the bottom Drawable.
3293      *
3294      * @attr ref android.R.styleable#TextView_drawableLeft
3295      * @attr ref android.R.styleable#TextView_drawableTop
3296      * @attr ref android.R.styleable#TextView_drawableRight
3297      * @attr ref android.R.styleable#TextView_drawableBottom
3298      */
3299     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)3300     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
3301             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
3302         final Context context = getContext();
3303         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
3304                 top != 0 ? context.getDrawable(top) : null,
3305                 right != 0 ? context.getDrawable(right) : null,
3306                 bottom != 0 ? context.getDrawable(bottom) : null);
3307     }
3308 
3309     /**
3310      * Sets the Drawables (if any) to appear to the left of, above, to the
3311      * right of, and below the text. Use {@code null} if you do not want a
3312      * Drawable there. The Drawables' bounds will be set to their intrinsic
3313      * bounds.
3314      * <p>
3315      * Calling this method will overwrite any Drawables previously set using
3316      * {@link #setCompoundDrawablesRelative} or related methods.
3317      *
3318      * @attr ref android.R.styleable#TextView_drawableLeft
3319      * @attr ref android.R.styleable#TextView_drawableTop
3320      * @attr ref android.R.styleable#TextView_drawableRight
3321      * @attr ref android.R.styleable#TextView_drawableBottom
3322      */
3323     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3324     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
3325             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
3326 
3327         if (left != null) {
3328             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
3329         }
3330         if (right != null) {
3331             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
3332         }
3333         if (top != null) {
3334             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3335         }
3336         if (bottom != null) {
3337             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3338         }
3339         setCompoundDrawables(left, top, right, bottom);
3340     }
3341 
3342     /**
3343      * Sets the Drawables (if any) to appear to the start of, above, to the end
3344      * of, and below the text. Use {@code null} if you do not want a Drawable
3345      * there. The Drawables must already have had {@link Drawable#setBounds}
3346      * called.
3347      * <p>
3348      * Calling this method will overwrite any Drawables previously set using
3349      * {@link #setCompoundDrawables} or related methods.
3350      *
3351      * @attr ref android.R.styleable#TextView_drawableStart
3352      * @attr ref android.R.styleable#TextView_drawableTop
3353      * @attr ref android.R.styleable#TextView_drawableEnd
3354      * @attr ref android.R.styleable#TextView_drawableBottom
3355      */
3356     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3357     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
3358             @Nullable Drawable end, @Nullable Drawable bottom) {
3359         Drawables dr = mDrawables;
3360 
3361         // We're switching to relative, discard absolute.
3362         if (dr != null) {
3363             if (dr.mShowing[Drawables.LEFT] != null) {
3364                 dr.mShowing[Drawables.LEFT].setCallback(null);
3365             }
3366             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3367             if (dr.mShowing[Drawables.RIGHT] != null) {
3368                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3369             }
3370             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3371             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3372             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3373         }
3374 
3375         final boolean drawables = start != null || top != null
3376                 || end != null || bottom != null;
3377 
3378         if (!drawables) {
3379             // Clearing drawables...  can we free the data structure?
3380             if (dr != null) {
3381                 if (!dr.hasMetadata()) {
3382                     mDrawables = null;
3383                 } else {
3384                     // We need to retain the last set padding, so just clear
3385                     // out all of the fields in the existing structure.
3386                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3387                     dr.mDrawableStart = null;
3388                     if (dr.mShowing[Drawables.TOP] != null) {
3389                         dr.mShowing[Drawables.TOP].setCallback(null);
3390                     }
3391                     dr.mShowing[Drawables.TOP] = null;
3392                     if (dr.mDrawableEnd != null) {
3393                         dr.mDrawableEnd.setCallback(null);
3394                     }
3395                     dr.mDrawableEnd = null;
3396                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3397                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3398                     }
3399                     dr.mShowing[Drawables.BOTTOM] = null;
3400                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3401                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3402                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3403                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3404                 }
3405             }
3406         } else {
3407             if (dr == null) {
3408                 mDrawables = dr = new Drawables(getContext());
3409             }
3410 
3411             mDrawables.mOverride = true;
3412 
3413             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3414                 dr.mDrawableStart.setCallback(null);
3415             }
3416             dr.mDrawableStart = start;
3417 
3418             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3419                 dr.mShowing[Drawables.TOP].setCallback(null);
3420             }
3421             dr.mShowing[Drawables.TOP] = top;
3422 
3423             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3424                 dr.mDrawableEnd.setCallback(null);
3425             }
3426             dr.mDrawableEnd = end;
3427 
3428             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3429                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3430             }
3431             dr.mShowing[Drawables.BOTTOM] = bottom;
3432 
3433             final Rect compoundRect = dr.mCompoundRect;
3434             int[] state;
3435 
3436             state = getDrawableState();
3437 
3438             if (start != null) {
3439                 start.setState(state);
3440                 start.copyBounds(compoundRect);
3441                 start.setCallback(this);
3442                 dr.mDrawableSizeStart = compoundRect.width();
3443                 dr.mDrawableHeightStart = compoundRect.height();
3444             } else {
3445                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3446             }
3447 
3448             if (end != null) {
3449                 end.setState(state);
3450                 end.copyBounds(compoundRect);
3451                 end.setCallback(this);
3452                 dr.mDrawableSizeEnd = compoundRect.width();
3453                 dr.mDrawableHeightEnd = compoundRect.height();
3454             } else {
3455                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3456             }
3457 
3458             if (top != null) {
3459                 top.setState(state);
3460                 top.copyBounds(compoundRect);
3461                 top.setCallback(this);
3462                 dr.mDrawableSizeTop = compoundRect.height();
3463                 dr.mDrawableWidthTop = compoundRect.width();
3464             } else {
3465                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3466             }
3467 
3468             if (bottom != null) {
3469                 bottom.setState(state);
3470                 bottom.copyBounds(compoundRect);
3471                 bottom.setCallback(this);
3472                 dr.mDrawableSizeBottom = compoundRect.height();
3473                 dr.mDrawableWidthBottom = compoundRect.width();
3474             } else {
3475                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3476             }
3477         }
3478 
3479         resetResolvedDrawables();
3480         resolveDrawables();
3481         invalidate();
3482         requestLayout();
3483     }
3484 
3485     /**
3486      * Sets the Drawables (if any) to appear to the start of, above, to the end
3487      * of, and below the text. Use 0 if you do not want a Drawable there. The
3488      * Drawables' bounds will be set to their intrinsic bounds.
3489      * <p>
3490      * Calling this method will overwrite any Drawables previously set using
3491      * {@link #setCompoundDrawables} or related methods.
3492      *
3493      * @param start Resource identifier of the start Drawable.
3494      * @param top Resource identifier of the top Drawable.
3495      * @param end Resource identifier of the end Drawable.
3496      * @param bottom Resource identifier of the bottom Drawable.
3497      *
3498      * @attr ref android.R.styleable#TextView_drawableStart
3499      * @attr ref android.R.styleable#TextView_drawableTop
3500      * @attr ref android.R.styleable#TextView_drawableEnd
3501      * @attr ref android.R.styleable#TextView_drawableBottom
3502      */
3503     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3504     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3505             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3506         final Context context = getContext();
3507         setCompoundDrawablesRelativeWithIntrinsicBounds(
3508                 start != 0 ? context.getDrawable(start) : null,
3509                 top != 0 ? context.getDrawable(top) : null,
3510                 end != 0 ? context.getDrawable(end) : null,
3511                 bottom != 0 ? context.getDrawable(bottom) : null);
3512     }
3513 
3514     /**
3515      * Sets the Drawables (if any) to appear to the start of, above, to the end
3516      * of, and below the text. Use {@code null} if you do not want a Drawable
3517      * there. The Drawables' bounds will be set to their intrinsic bounds.
3518      * <p>
3519      * Calling this method will overwrite any Drawables previously set using
3520      * {@link #setCompoundDrawables} or related methods.
3521      *
3522      * @attr ref android.R.styleable#TextView_drawableStart
3523      * @attr ref android.R.styleable#TextView_drawableTop
3524      * @attr ref android.R.styleable#TextView_drawableEnd
3525      * @attr ref android.R.styleable#TextView_drawableBottom
3526      */
3527     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3528     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3529             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3530 
3531         if (start != null) {
3532             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3533         }
3534         if (end != null) {
3535             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3536         }
3537         if (top != null) {
3538             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3539         }
3540         if (bottom != null) {
3541             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3542         }
3543         setCompoundDrawablesRelative(start, top, end, bottom);
3544     }
3545 
3546     /**
3547      * Returns drawables for the left, top, right, and bottom borders.
3548      *
3549      * @attr ref android.R.styleable#TextView_drawableLeft
3550      * @attr ref android.R.styleable#TextView_drawableTop
3551      * @attr ref android.R.styleable#TextView_drawableRight
3552      * @attr ref android.R.styleable#TextView_drawableBottom
3553      */
3554     @NonNull
getCompoundDrawables()3555     public Drawable[] getCompoundDrawables() {
3556         final Drawables dr = mDrawables;
3557         if (dr != null) {
3558             return dr.mShowing.clone();
3559         } else {
3560             return new Drawable[] { null, null, null, null };
3561         }
3562     }
3563 
3564     /**
3565      * Returns drawables for the start, top, end, and bottom borders.
3566      *
3567      * @attr ref android.R.styleable#TextView_drawableStart
3568      * @attr ref android.R.styleable#TextView_drawableTop
3569      * @attr ref android.R.styleable#TextView_drawableEnd
3570      * @attr ref android.R.styleable#TextView_drawableBottom
3571      */
3572     @NonNull
getCompoundDrawablesRelative()3573     public Drawable[] getCompoundDrawablesRelative() {
3574         final Drawables dr = mDrawables;
3575         if (dr != null) {
3576             return new Drawable[] {
3577                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3578                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3579             };
3580         } else {
3581             return new Drawable[] { null, null, null, null };
3582         }
3583     }
3584 
3585     /**
3586      * Sets the size of the padding between the compound drawables and
3587      * the text.
3588      *
3589      * @attr ref android.R.styleable#TextView_drawablePadding
3590      */
3591     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3592     public void setCompoundDrawablePadding(int pad) {
3593         Drawables dr = mDrawables;
3594         if (pad == 0) {
3595             if (dr != null) {
3596                 dr.mDrawablePadding = pad;
3597             }
3598         } else {
3599             if (dr == null) {
3600                 mDrawables = dr = new Drawables(getContext());
3601             }
3602             dr.mDrawablePadding = pad;
3603         }
3604 
3605         invalidate();
3606         requestLayout();
3607     }
3608 
3609     /**
3610      * Returns the padding between the compound drawables and the text.
3611      *
3612      * @attr ref android.R.styleable#TextView_drawablePadding
3613      */
3614     @InspectableProperty(name = "drawablePadding")
getCompoundDrawablePadding()3615     public int getCompoundDrawablePadding() {
3616         final Drawables dr = mDrawables;
3617         return dr != null ? dr.mDrawablePadding : 0;
3618     }
3619 
3620     /**
3621      * Applies a tint to the compound drawables. Does not modify the
3622      * current tint mode, which is {@link BlendMode#SRC_IN} by default.
3623      * <p>
3624      * Subsequent calls to
3625      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3626      * and related methods will automatically mutate the drawables and apply
3627      * the specified tint and tint mode using
3628      * {@link Drawable#setTintList(ColorStateList)}.
3629      *
3630      * @param tint the tint to apply, may be {@code null} to clear tint
3631      *
3632      * @attr ref android.R.styleable#TextView_drawableTint
3633      * @see #getCompoundDrawableTintList()
3634      * @see Drawable#setTintList(ColorStateList)
3635      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3636     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3637         if (mDrawables == null) {
3638             mDrawables = new Drawables(getContext());
3639         }
3640         mDrawables.mTintList = tint;
3641         mDrawables.mHasTint = true;
3642 
3643         applyCompoundDrawableTint();
3644     }
3645 
3646     /**
3647      * @return the tint applied to the compound drawables
3648      * @attr ref android.R.styleable#TextView_drawableTint
3649      * @see #setCompoundDrawableTintList(ColorStateList)
3650      */
3651     @InspectableProperty(name = "drawableTint")
getCompoundDrawableTintList()3652     public ColorStateList getCompoundDrawableTintList() {
3653         return mDrawables != null ? mDrawables.mTintList : 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 tintMode 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#setTintMode(PorterDuff.Mode)
3666      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3667     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3668         setCompoundDrawableTintBlendMode(tintMode != null
3669                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3670     }
3671 
3672     /**
3673      * Specifies the blending mode used to apply the tint specified by
3674      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3675      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3676      *
3677      * @param blendMode the blending mode used to apply the tint, may be
3678      *                 {@code null} to clear tint
3679      * @attr ref android.R.styleable#TextView_drawableTintMode
3680      * @see #setCompoundDrawableTintList(ColorStateList)
3681      * @see Drawable#setTintBlendMode(BlendMode)
3682      */
setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3683     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3684         if (mDrawables == null) {
3685             mDrawables = new Drawables(getContext());
3686         }
3687         mDrawables.mBlendMode = blendMode;
3688         mDrawables.mHasTintMode = true;
3689 
3690         applyCompoundDrawableTint();
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 #setCompoundDrawableTintMode(PorterDuff.Mode)
3701      *
3702      */
3703     @InspectableProperty(name = "drawableTintMode")
getCompoundDrawableTintMode()3704     public PorterDuff.Mode getCompoundDrawableTintMode() {
3705         BlendMode mode = getCompoundDrawableTintBlendMode();
3706         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3707     }
3708 
3709     /**
3710      * Returns the blending mode used to apply the tint to the compound
3711      * drawables, if specified.
3712      *
3713      * @return the blending mode used to apply the tint to the compound
3714      *         drawables
3715      * @attr ref android.R.styleable#TextView_drawableTintMode
3716      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3717      */
3718     @InspectableProperty(name = "drawableBlendMode",
3719             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
getCompoundDrawableTintBlendMode()3720     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3721         return mDrawables != null ? mDrawables.mBlendMode : null;
3722     }
3723 
applyCompoundDrawableTint()3724     private void applyCompoundDrawableTint() {
3725         if (mDrawables == null) {
3726             return;
3727         }
3728 
3729         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3730             final ColorStateList tintList = mDrawables.mTintList;
3731             final BlendMode blendMode = mDrawables.mBlendMode;
3732             final boolean hasTint = mDrawables.mHasTint;
3733             final boolean hasTintMode = mDrawables.mHasTintMode;
3734             final int[] state = getDrawableState();
3735 
3736             for (Drawable dr : mDrawables.mShowing) {
3737                 if (dr == null) {
3738                     continue;
3739                 }
3740 
3741                 if (dr == mDrawables.mDrawableError) {
3742                     // From a developer's perspective, the error drawable isn't
3743                     // a compound drawable. Don't apply the generic compound
3744                     // drawable tint to it.
3745                     continue;
3746                 }
3747 
3748                 dr.mutate();
3749 
3750                 if (hasTint) {
3751                     dr.setTintList(tintList);
3752                 }
3753 
3754                 if (hasTintMode) {
3755                     dr.setTintBlendMode(blendMode);
3756                 }
3757 
3758                 // The drawable (or one of its children) may not have been
3759                 // stateful before applying the tint, so let's try again.
3760                 if (dr.isStateful()) {
3761                     dr.setState(state);
3762                 }
3763             }
3764         }
3765     }
3766 
3767     /**
3768      * @inheritDoc
3769      *
3770      * @see #setFirstBaselineToTopHeight(int)
3771      * @see #setLastBaselineToBottomHeight(int)
3772      */
3773     @Override
setPadding(int left, int top, int right, int bottom)3774     public void setPadding(int left, int top, int right, int bottom) {
3775         if (left != mPaddingLeft
3776                 || right != mPaddingRight
3777                 || top != mPaddingTop
3778                 ||  bottom != mPaddingBottom) {
3779             nullLayouts();
3780         }
3781 
3782         // the super call will requestLayout()
3783         super.setPadding(left, top, right, bottom);
3784         invalidate();
3785     }
3786 
3787     /**
3788      * @inheritDoc
3789      *
3790      * @see #setFirstBaselineToTopHeight(int)
3791      * @see #setLastBaselineToBottomHeight(int)
3792      */
3793     @Override
setPaddingRelative(int start, int top, int end, int bottom)3794     public void setPaddingRelative(int start, int top, int end, int bottom) {
3795         if (start != getPaddingStart()
3796                 || end != getPaddingEnd()
3797                 || top != mPaddingTop
3798                 || bottom != mPaddingBottom) {
3799             nullLayouts();
3800         }
3801 
3802         // the super call will requestLayout()
3803         super.setPaddingRelative(start, top, end, bottom);
3804         invalidate();
3805     }
3806 
3807     /**
3808      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3809      * the distance between the top of the TextView and first line's baseline.
3810      * <p>
3811      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3812      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3813      *
3814      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3815      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3816      * Moreover since this function sets the top padding, if the height of the TextView is less than
3817      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3818      * down and bottom will be clipped.
3819      *
3820      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3821      *      in pixels
3822      *
3823      * @see #getFirstBaselineToTopHeight()
3824      * @see #setLastBaselineToBottomHeight(int)
3825      * @see #setPadding(int, int, int, int)
3826      * @see #setPaddingRelative(int, int, int, int)
3827      *
3828      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3829      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3830     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3831         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3832 
3833         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3834         final int fontMetricsTop;
3835         if (getIncludeFontPadding()) {
3836             fontMetricsTop = fontMetrics.top;
3837         } else {
3838             fontMetricsTop = fontMetrics.ascent;
3839         }
3840 
3841         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3842         // in settings). At the moment, we don't.
3843 
3844         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3845             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3846             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3847         }
3848     }
3849 
3850     /**
3851      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3852      * the distance between the bottom of the TextView and the last line's baseline.
3853      * <p>
3854      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3855      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3856      *
3857      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3858      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3859      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3860      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3861      * clipped.
3862      *
3863      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3864      *      in pixels
3865      *
3866      * @see #getLastBaselineToBottomHeight()
3867      * @see #setFirstBaselineToTopHeight(int)
3868      * @see #setPadding(int, int, int, int)
3869      * @see #setPaddingRelative(int, int, int, int)
3870      *
3871      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3872      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3873     public void setLastBaselineToBottomHeight(
3874             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3875         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3876 
3877         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3878         final int fontMetricsBottom;
3879         if (getIncludeFontPadding()) {
3880             fontMetricsBottom = fontMetrics.bottom;
3881         } else {
3882             fontMetricsBottom = fontMetrics.descent;
3883         }
3884 
3885         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3886         // in settings). At the moment, we don't.
3887 
3888         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3889             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3890             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3891         }
3892     }
3893 
3894     /**
3895      * Returns the distance between the first text baseline and the top of this TextView.
3896      *
3897      * @see #setFirstBaselineToTopHeight(int)
3898      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3899      */
3900     @InspectableProperty
getFirstBaselineToTopHeight()3901     public int getFirstBaselineToTopHeight() {
3902         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3903     }
3904 
3905     /**
3906      * Returns the distance between the last text baseline and the bottom of this TextView.
3907      *
3908      * @see #setLastBaselineToBottomHeight(int)
3909      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3910      */
3911     @InspectableProperty
getLastBaselineToBottomHeight()3912     public int getLastBaselineToBottomHeight() {
3913         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3914     }
3915 
3916     /**
3917      * Gets the autolink mask of the text.
3918      *
3919      * See {@link Linkify#ALL} and peers for possible values.
3920      *
3921      * @attr ref android.R.styleable#TextView_autoLink
3922      */
3923     @InspectableProperty(name = "autoLink", flagMapping = {
3924             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3925             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3926             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3927             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3928     })
getAutoLinkMask()3929     public final int getAutoLinkMask() {
3930         return mAutoLinkMask;
3931     }
3932 
3933     /**
3934      * Sets the Drawable corresponding to the selection handle used for
3935      * positioning the cursor within text. The Drawable defaults to the value
3936      * of the textSelectHandle attribute.
3937      * Note that any change applied to the handle Drawable will not be visible
3938      * until the handle is hidden and then drawn again.
3939      *
3940      * @see #setTextSelectHandle(int)
3941      * @attr ref android.R.styleable#TextView_textSelectHandle
3942      */
3943     @android.view.RemotableViewMethod
setTextSelectHandle(@onNull Drawable textSelectHandle)3944     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3945         Preconditions.checkNotNull(textSelectHandle,
3946                 "The text select handle should not be null.");
3947         mTextSelectHandle = textSelectHandle;
3948         mTextSelectHandleRes = 0;
3949         if (mEditor != null) {
3950             mEditor.loadHandleDrawables(true /* overwrite */);
3951         }
3952     }
3953 
3954     /**
3955      * Sets the Drawable corresponding to the selection handle used for
3956      * positioning the cursor within text. The Drawable defaults to the value
3957      * of the textSelectHandle attribute.
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      * @see #setTextSelectHandle(Drawable)
3962      * @attr ref android.R.styleable#TextView_textSelectHandle
3963      */
3964     @android.view.RemotableViewMethod
setTextSelectHandle(@rawableRes int textSelectHandle)3965     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3966         Preconditions.checkArgument(textSelectHandle != 0,
3967                 "The text select handle should be a valid drawable resource id.");
3968         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3969     }
3970 
3971     /**
3972      * Returns the Drawable corresponding to the selection handle used
3973      * for positioning the cursor within text.
3974      * Note that any change applied to the handle Drawable will not be visible
3975      * until the handle is hidden and then drawn again.
3976      *
3977      * @return the text select handle drawable
3978      *
3979      * @see #setTextSelectHandle(Drawable)
3980      * @see #setTextSelectHandle(int)
3981      * @attr ref android.R.styleable#TextView_textSelectHandle
3982      */
getTextSelectHandle()3983     @Nullable public Drawable getTextSelectHandle() {
3984         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3985             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3986         }
3987         return mTextSelectHandle;
3988     }
3989 
3990     /**
3991      * Sets the Drawable corresponding to the left handle used
3992      * for selecting text. The Drawable defaults to the value of the
3993      * textSelectHandleLeft attribute.
3994      * Note that any change applied to the handle Drawable will not be visible
3995      * until the handle is hidden and then drawn again.
3996      *
3997      * @see #setTextSelectHandleLeft(int)
3998      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3999      */
4000     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)4001     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
4002         Preconditions.checkNotNull(textSelectHandleLeft,
4003                 "The left text select handle should not be null.");
4004         mTextSelectHandleLeft = textSelectHandleLeft;
4005         mTextSelectHandleLeftRes = 0;
4006         if (mEditor != null) {
4007             mEditor.loadHandleDrawables(true /* overwrite */);
4008         }
4009     }
4010 
4011     /**
4012      * Sets the Drawable corresponding to the left handle used
4013      * for selecting text. The Drawable defaults to the value of the
4014      * textSelectHandleLeft attribute.
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      * @see #setTextSelectHandleLeft(Drawable)
4019      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
4020      */
4021     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)4022     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
4023         Preconditions.checkArgument(textSelectHandleLeft != 0,
4024                 "The text select left handle should be a valid drawable resource id.");
4025         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
4026     }
4027 
4028     /**
4029      * Returns the Drawable corresponding to the left handle used
4030      * for selecting text.
4031      * Note that any change applied to the handle Drawable will not be visible
4032      * until the handle is hidden and then drawn again.
4033      *
4034      * @return the left text selection handle drawable
4035      *
4036      * @see #setTextSelectHandleLeft(Drawable)
4037      * @see #setTextSelectHandleLeft(int)
4038      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
4039      */
getTextSelectHandleLeft()4040     @Nullable public Drawable getTextSelectHandleLeft() {
4041         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
4042             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
4043         }
4044         return mTextSelectHandleLeft;
4045     }
4046 
4047     /**
4048      * Sets the Drawable corresponding to the right handle used
4049      * for selecting text. The Drawable defaults to the value of the
4050      * textSelectHandleRight attribute.
4051      * Note that any change applied to the handle Drawable will not be visible
4052      * until the handle is hidden and then drawn again.
4053      *
4054      * @see #setTextSelectHandleRight(int)
4055      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4056      */
4057     @android.view.RemotableViewMethod
setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)4058     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
4059         Preconditions.checkNotNull(textSelectHandleRight,
4060                 "The right text select handle should not be null.");
4061         mTextSelectHandleRight = textSelectHandleRight;
4062         mTextSelectHandleRightRes = 0;
4063         if (mEditor != null) {
4064             mEditor.loadHandleDrawables(true /* overwrite */);
4065         }
4066     }
4067 
4068     /**
4069      * Sets the Drawable corresponding to the right handle used
4070      * for selecting text. The Drawable defaults to the value of the
4071      * textSelectHandleRight attribute.
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      * @see #setTextSelectHandleRight(Drawable)
4076      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4077      */
4078     @android.view.RemotableViewMethod
setTextSelectHandleRight(@rawableRes int textSelectHandleRight)4079     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
4080         Preconditions.checkArgument(textSelectHandleRight != 0,
4081                 "The text select right handle should be a valid drawable resource id.");
4082         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
4083     }
4084 
4085     /**
4086      * Returns the Drawable corresponding to the right handle used
4087      * for selecting text.
4088      * Note that any change applied to the handle Drawable will not be visible
4089      * until the handle is hidden and then drawn again.
4090      *
4091      * @return the right text selection handle drawable
4092      *
4093      * @see #setTextSelectHandleRight(Drawable)
4094      * @see #setTextSelectHandleRight(int)
4095      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4096      */
getTextSelectHandleRight()4097     @Nullable public Drawable getTextSelectHandleRight() {
4098         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
4099             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
4100         }
4101         return mTextSelectHandleRight;
4102     }
4103 
4104     /**
4105      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
4106      * value of the textCursorDrawable attribute.
4107      * Note that any change applied to the cursor Drawable will not be visible
4108      * until the cursor is hidden and then drawn again.
4109      *
4110      * @see #setTextCursorDrawable(int)
4111      * @attr ref android.R.styleable#TextView_textCursorDrawable
4112      */
setTextCursorDrawable(@ullable Drawable textCursorDrawable)4113     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
4114         mCursorDrawable = textCursorDrawable;
4115         mCursorDrawableRes = 0;
4116         if (mEditor != null) {
4117             mEditor.loadCursorDrawable();
4118         }
4119     }
4120 
4121     /**
4122      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
4123      * value of the textCursorDrawable attribute.
4124      * Note that any change applied to the cursor Drawable will not be visible
4125      * until the cursor is hidden and then drawn again.
4126      *
4127      * @see #setTextCursorDrawable(Drawable)
4128      * @attr ref android.R.styleable#TextView_textCursorDrawable
4129      */
setTextCursorDrawable(@rawableRes int textCursorDrawable)4130     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
4131         setTextCursorDrawable(
4132                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
4133     }
4134 
4135     /**
4136      * Returns the Drawable corresponding to the text cursor.
4137      * Note that any change applied to the cursor Drawable will not be visible
4138      * until the cursor is hidden and then drawn again.
4139      *
4140      * @return the text cursor drawable
4141      *
4142      * @see #setTextCursorDrawable(Drawable)
4143      * @see #setTextCursorDrawable(int)
4144      * @attr ref android.R.styleable#TextView_textCursorDrawable
4145      */
getTextCursorDrawable()4146     @Nullable public Drawable getTextCursorDrawable() {
4147         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
4148             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
4149         }
4150         return mCursorDrawable;
4151     }
4152 
4153     /**
4154      * Sets the text appearance from the specified style resource.
4155      * <p>
4156      * Use a framework-defined {@code TextAppearance} style like
4157      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
4158      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
4159      * set of attributes that can be used in a custom style.
4160      *
4161      * @param resId the resource identifier of the style to apply
4162      * @attr ref android.R.styleable#TextView_textAppearance
4163      */
4164     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)4165     public void setTextAppearance(@StyleRes int resId) {
4166         setTextAppearance(mContext, resId);
4167     }
4168 
4169     /**
4170      * Sets the text color, size, style, hint color, and highlight color
4171      * from the specified TextAppearance resource.
4172      *
4173      * @deprecated Use {@link #setTextAppearance(int)} instead.
4174      */
4175     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)4176     public void setTextAppearance(Context context, @StyleRes int resId) {
4177         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
4178         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
4179         readTextAppearance(context, ta, attributes, false /* styleArray */);
4180         ta.recycle();
4181         applyTextAppearance(attributes);
4182     }
4183 
4184     /**
4185      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
4186      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
4187      */
4188     private static class TextAppearanceAttributes {
4189         int mTextColorHighlight = 0;
4190         int mSearchResultHighlightColor = 0;
4191         int mFocusedSearchResultHighlightColor = 0;
4192         ColorStateList mTextColor = null;
4193         ColorStateList mTextColorHint = null;
4194         ColorStateList mTextColorLink = null;
4195         int mTextSize = -1;
4196         int mTextSizeUnit = -1;
4197         LocaleList mTextLocales = null;
4198         String mFontFamily = null;
4199         Typeface mFontTypeface = null;
4200         boolean mFontFamilyExplicit = false;
4201         int mTypefaceIndex = -1;
4202         int mTextStyle = 0;
4203         int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
4204         boolean mAllCaps = false;
4205         int mShadowColor = 0;
4206         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
4207         boolean mHasElegant = false;
4208         boolean mElegant = false;
4209         boolean mHasFallbackLineSpacing = false;
4210         boolean mFallbackLineSpacing = false;
4211         boolean mHasLetterSpacing = false;
4212         float mLetterSpacing = 0;
4213         String mFontFeatureSettings = null;
4214         String mFontVariationSettings = null;
4215         boolean mHasLineBreakStyle = false;
4216         boolean mHasLineBreakWordStyle = false;
4217         int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
4218         int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
4219 
4220         @Override
toString()4221         public String toString() {
4222             return "TextAppearanceAttributes {\n"
4223                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
4224                     + "    mSearchResultHighlightColor: " + mSearchResultHighlightColor + "\n"
4225                     + "    mFocusedSearchResultHighlightColor: "
4226                     + mFocusedSearchResultHighlightColor + "\n"
4227                     + "    mTextColor:" + mTextColor + "\n"
4228                     + "    mTextColorHint:" + mTextColorHint + "\n"
4229                     + "    mTextColorLink:" + mTextColorLink + "\n"
4230                     + "    mTextSize:" + mTextSize + "\n"
4231                     + "    mTextSizeUnit:" + mTextSizeUnit + "\n"
4232                     + "    mTextLocales:" + mTextLocales + "\n"
4233                     + "    mFontFamily:" + mFontFamily + "\n"
4234                     + "    mFontTypeface:" + mFontTypeface + "\n"
4235                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
4236                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
4237                     + "    mTextStyle:" + mTextStyle + "\n"
4238                     + "    mFontWeight:" + mFontWeight + "\n"
4239                     + "    mAllCaps:" + mAllCaps + "\n"
4240                     + "    mShadowColor:" + mShadowColor + "\n"
4241                     + "    mShadowDx:" + mShadowDx + "\n"
4242                     + "    mShadowDy:" + mShadowDy + "\n"
4243                     + "    mShadowRadius:" + mShadowRadius + "\n"
4244                     + "    mHasElegant:" + mHasElegant + "\n"
4245                     + "    mElegant:" + mElegant + "\n"
4246                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
4247                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
4248                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
4249                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
4250                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
4251                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
4252                     + "    mHasLineBreakStyle:" + mHasLineBreakStyle + "\n"
4253                     + "    mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n"
4254                     + "    mLineBreakStyle:" + mLineBreakStyle + "\n"
4255                     + "    mLineBreakWordStyle:" + mLineBreakWordStyle + "\n"
4256                     + "}";
4257         }
4258     }
4259 
4260     // Maps styleable attributes that exist both in TextView style and TextAppearance.
4261     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
4262     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)4263         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
4264                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor)4265         sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor,
4266                 com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor);
sAppearanceValues.put( com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor)4267         sAppearanceValues.put(
4268                 com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor,
4269                 com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)4270         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
4271                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)4272         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
4273                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)4274         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
4275                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)4276         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
4277                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)4278         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
4279                 com.android.internal.R.styleable.TextAppearance_textLocale);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)4280         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
4281                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)4282         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
4283                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)4284         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
4285                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4286         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
4287                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4288         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
4289                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4290         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
4291                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4292         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
4293                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4294         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
4295                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4296         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
4297                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4298         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
4299                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4300         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
4301                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4302         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
4303                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4304         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
4305                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4306         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
4307                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, com.android.internal.R.styleable.TextAppearance_lineBreakStyle)4308         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle,
4309                 com.android.internal.R.styleable.TextAppearance_lineBreakStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle)4310         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle,
4311                 com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle);
4312     }
4313 
4314     /**
4315      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
4316      * set. If the TypedArray contains a value that was already set in the given attributes, that
4317      * will be overridden.
4318      *
4319      * @param context The Context to be used
4320      * @param appearance The TypedArray to read properties from
4321      * @param attributes the TextAppearanceAttributes to fill in
4322      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
4323      *                   what attribute indexes will be used to read the properties.
4324      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)4325     private void readTextAppearance(Context context, TypedArray appearance,
4326             TextAppearanceAttributes attributes, boolean styleArray) {
4327         final int n = appearance.getIndexCount();
4328         for (int i = 0; i < n; i++) {
4329             final int attr = appearance.getIndex(i);
4330             int index = attr;
4331             // Translate style array index ids to TextAppearance ids.
4332             if (styleArray) {
4333                 index = sAppearanceValues.get(attr, -1);
4334                 if (index == -1) {
4335                     // This value is not part of a Text Appearance and should be ignored.
4336                     continue;
4337                 }
4338             }
4339             switch (index) {
4340                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
4341                     attributes.mTextColorHighlight =
4342                             appearance.getColor(attr, attributes.mTextColorHighlight);
4343                     break;
4344                 case com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor:
4345                     attributes.mSearchResultHighlightColor =
4346                             appearance.getColor(attr, attributes.mSearchResultHighlightColor);
4347                     break;
4348                 case com.android.internal.R.styleable
4349                         .TextAppearance_focusedSearchResultHighlightColor:
4350                     attributes.mFocusedSearchResultHighlightColor =
4351                             appearance.getColor(attr,
4352                                     attributes.mFocusedSearchResultHighlightColor);
4353                     break;
4354                 case com.android.internal.R.styleable.TextAppearance_textColor:
4355                     attributes.mTextColor = appearance.getColorStateList(attr);
4356                     break;
4357                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
4358                     attributes.mTextColorHint = appearance.getColorStateList(attr);
4359                     break;
4360                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
4361                     attributes.mTextColorLink = appearance.getColorStateList(attr);
4362                     break;
4363                 case com.android.internal.R.styleable.TextAppearance_textSize:
4364                     attributes.mTextSize =
4365                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
4366                     attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit();
4367                     break;
4368                 case com.android.internal.R.styleable.TextAppearance_textLocale:
4369                     final String localeString = appearance.getString(attr);
4370                     if (localeString != null) {
4371                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
4372                         if (!localeList.isEmpty()) {
4373                             attributes.mTextLocales = localeList;
4374                         }
4375                     }
4376                     break;
4377                 case com.android.internal.R.styleable.TextAppearance_typeface:
4378                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
4379                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4380                         attributes.mFontFamily = null;
4381                     }
4382                     break;
4383                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
4384                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
4385                         try {
4386                             attributes.mFontTypeface = appearance.getFont(attr);
4387                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
4388                             // Expected if it is not a font resource.
4389                         }
4390                     }
4391                     if (attributes.mFontTypeface == null) {
4392                         attributes.mFontFamily = appearance.getString(attr);
4393                     }
4394                     attributes.mFontFamilyExplicit = true;
4395                     break;
4396                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4397                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4398                     break;
4399                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4400                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4401                     break;
4402                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4403                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4404                     break;
4405                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4406                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4407                     break;
4408                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4409                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4410                     break;
4411                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4412                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4413                     break;
4414                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4415                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4416                     break;
4417                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4418                     attributes.mHasElegant = true;
4419                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4420                     break;
4421                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4422                     attributes.mHasFallbackLineSpacing = true;
4423                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4424                             attributes.mFallbackLineSpacing);
4425                     break;
4426                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4427                     attributes.mHasLetterSpacing = true;
4428                     attributes.mLetterSpacing =
4429                             appearance.getFloat(attr, attributes.mLetterSpacing);
4430                     break;
4431                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4432                     attributes.mFontFeatureSettings = appearance.getString(attr);
4433                     break;
4434                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4435                     attributes.mFontVariationSettings = appearance.getString(attr);
4436                     break;
4437                 case com.android.internal.R.styleable.TextAppearance_lineBreakStyle:
4438                     attributes.mHasLineBreakStyle = true;
4439                     attributes.mLineBreakStyle =
4440                             appearance.getInt(attr, attributes.mLineBreakStyle);
4441                     break;
4442                 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
4443                     attributes.mHasLineBreakWordStyle = true;
4444                     attributes.mLineBreakWordStyle =
4445                             appearance.getInt(attr, attributes.mLineBreakWordStyle);
4446                     break;
4447                 default:
4448             }
4449         }
4450     }
4451 
applyTextAppearance(TextAppearanceAttributes attributes)4452     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4453         if (attributes.mTextColor != null) {
4454             setTextColor(attributes.mTextColor);
4455         }
4456 
4457         if (attributes.mTextColorHint != null) {
4458             setHintTextColor(attributes.mTextColorHint);
4459         }
4460 
4461         if (attributes.mTextColorLink != null) {
4462             setLinkTextColor(attributes.mTextColorLink);
4463         }
4464 
4465         if (attributes.mTextColorHighlight != 0) {
4466             setHighlightColor(attributes.mTextColorHighlight);
4467         }
4468 
4469         if (attributes.mSearchResultHighlightColor != 0) {
4470             setSearchResultHighlightColor(attributes.mSearchResultHighlightColor);
4471         }
4472 
4473         if (attributes.mFocusedSearchResultHighlightColor != 0) {
4474             setFocusedSearchResultHighlightColor(attributes.mFocusedSearchResultHighlightColor);
4475         }
4476 
4477         if (attributes.mTextSize != -1) {
4478             mTextSizeUnit = attributes.mTextSizeUnit;
4479             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4480         }
4481 
4482         if (attributes.mTextLocales != null) {
4483             setTextLocales(attributes.mTextLocales);
4484         }
4485 
4486         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4487             attributes.mFontFamily = null;
4488         }
4489         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4490                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4491 
4492         if (attributes.mShadowColor != 0) {
4493             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4494                     attributes.mShadowColor);
4495         }
4496 
4497         if (attributes.mAllCaps) {
4498             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4499         }
4500 
4501         if (attributes.mHasElegant) {
4502             setElegantTextHeight(attributes.mElegant);
4503         }
4504 
4505         if (attributes.mHasFallbackLineSpacing) {
4506             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4507         }
4508 
4509         if (attributes.mHasLetterSpacing) {
4510             setLetterSpacing(attributes.mLetterSpacing);
4511         }
4512 
4513         if (attributes.mFontFeatureSettings != null) {
4514             setFontFeatureSettings(attributes.mFontFeatureSettings);
4515         }
4516 
4517         if (attributes.mFontVariationSettings != null) {
4518             setFontVariationSettings(attributes.mFontVariationSettings);
4519         }
4520 
4521         if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) {
4522             updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle,
4523                     attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle,
4524                     attributes.mLineBreakWordStyle);
4525         }
4526     }
4527 
4528     /**
4529      * Updates the LineBreakConfig from the TextAppearance.
4530      *
4531      * This method updates the given line configuration from the TextAppearance. This method will
4532      * request new layout if line break config has been changed.
4533      *
4534      * @param isLineBreakStyleSpecified true if the line break style is specified.
4535      * @param isLineBreakWordStyleSpecified true if the line break word style is specified.
4536      * @param lineBreakStyle the value of the line break style in the TextAppearance.
4537      * @param lineBreakWordStyle the value of the line break word style in the TextAppearance.
4538      */
updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, boolean isLineBreakWordStyleSpecified, @LineBreakConfig.LineBreakStyle int lineBreakStyle, @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)4539     private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified,
4540             boolean isLineBreakWordStyleSpecified,
4541             @LineBreakConfig.LineBreakStyle int lineBreakStyle,
4542             @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
4543         boolean updated = false;
4544         if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) {
4545             mLineBreakStyle = lineBreakStyle;
4546             updated = true;
4547         }
4548         if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) {
4549             mLineBreakWordStyle = lineBreakWordStyle;
4550             updated = true;
4551         }
4552         if (updated && mLayout != null) {
4553             nullLayouts();
4554             requestLayout();
4555             invalidate();
4556         }
4557     }
4558     /**
4559      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4560      * the first member of {@link #getTextLocales()}.
4561      * @return the default primary {@link Locale} of the text in this TextView.
4562      */
4563     @NonNull
getTextLocale()4564     public Locale getTextLocale() {
4565         return mTextPaint.getTextLocale();
4566     }
4567 
4568     /**
4569      * Get the default {@link LocaleList} of the text in this TextView.
4570      * @return the default {@link LocaleList} of the text in this TextView.
4571      */
4572     @NonNull @Size(min = 1)
getTextLocales()4573     public LocaleList getTextLocales() {
4574         return mTextPaint.getTextLocales();
4575     }
4576 
changeListenerLocaleTo(@ullable Locale locale)4577     private void changeListenerLocaleTo(@Nullable Locale locale) {
4578         if (mListenerChanged) {
4579             // If a listener has been explicitly set, don't change it. We may break something.
4580             return;
4581         }
4582         // The following null check is not absolutely necessary since all calling points of
4583         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4584         // here in case others would want to call this method in the future.
4585         if (mEditor != null) {
4586             KeyListener listener = mEditor.mKeyListener;
4587             if (listener instanceof DigitsKeyListener) {
4588                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4589             } else if (listener instanceof DateKeyListener) {
4590                 listener = DateKeyListener.getInstance(locale);
4591             } else if (listener instanceof TimeKeyListener) {
4592                 listener = TimeKeyListener.getInstance(locale);
4593             } else if (listener instanceof DateTimeKeyListener) {
4594                 listener = DateTimeKeyListener.getInstance(locale);
4595             } else {
4596                 return;
4597             }
4598             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4599             setKeyListenerOnly(listener);
4600             setInputTypeFromEditor();
4601             if (wasPasswordType) {
4602                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4603                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4604                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4605                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4606                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4607                 }
4608             }
4609         }
4610     }
4611 
4612     /**
4613      * Set the default {@link Locale} of the text in this TextView to a one-member
4614      * {@link LocaleList} containing just the given Locale.
4615      *
4616      * @param locale the {@link Locale} for drawing text, must not be null.
4617      *
4618      * @see #setTextLocales
4619      */
setTextLocale(@onNull Locale locale)4620     public void setTextLocale(@NonNull Locale locale) {
4621         mLocalesChanged = true;
4622         mTextPaint.setTextLocale(locale);
4623         if (mLayout != null) {
4624             nullLayouts();
4625             requestLayout();
4626             invalidate();
4627         }
4628     }
4629 
4630     /**
4631      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4632      *
4633      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4634      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4635      * other aspects of text display, including line breaking.
4636      *
4637      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4638      *
4639      * @see Paint#setTextLocales
4640      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)4641     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4642         mLocalesChanged = true;
4643         mTextPaint.setTextLocales(locales);
4644         if (mLayout != null) {
4645             nullLayouts();
4646             requestLayout();
4647             invalidate();
4648         }
4649     }
4650 
4651     @Override
onConfigurationChanged(Configuration newConfig)4652     protected void onConfigurationChanged(Configuration newConfig) {
4653         super.onConfigurationChanged(newConfig);
4654         if (!mLocalesChanged) {
4655             mTextPaint.setTextLocales(LocaleList.getDefault());
4656             if (mLayout != null) {
4657                 nullLayouts();
4658                 requestLayout();
4659                 invalidate();
4660             }
4661         }
4662         if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) {
4663             mFontWeightAdjustment = newConfig.fontWeightAdjustment;
4664             setTypeface(getTypeface());
4665         }
4666 
4667         InputMethodManager imm = getInputMethodManager();
4668         // if orientation changed and this TextView is currently served.
4669         if (mLastOrientation != newConfig.orientation
4670                 && imm != null && imm.hasActiveInputConnection(this)) {
4671             // EditorInfo.internalImeOptions are out of date.
4672             imm.restartInput(this);
4673         }
4674         mLastOrientation = newConfig.orientation;
4675     }
4676 
4677     /**
4678      * @return the size (in pixels) of the default text size in this TextView.
4679      */
4680     @InspectableProperty
4681     @ViewDebug.ExportedProperty(category = "text")
getTextSize()4682     public float getTextSize() {
4683         return mTextPaint.getTextSize();
4684     }
4685 
4686     /**
4687      * @return the size (in scaled pixels) of the default text size in this TextView.
4688      * @hide
4689      */
4690     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()4691     public float getScaledTextSize() {
4692         return mTextPaint.getTextSize() / mTextPaint.density;
4693     }
4694 
4695     /** @hide */
4696     @ViewDebug.ExportedProperty(category = "text", mapping = {
4697             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4698             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4699             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4700             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4701     })
getTypefaceStyle()4702     public int getTypefaceStyle() {
4703         Typeface typeface = mTextPaint.getTypeface();
4704         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4705     }
4706 
4707     /**
4708      * Set the default text size to the given value, interpreted as "scaled
4709      * pixel" units.  This size is adjusted based on the current density and
4710      * user font size preference.
4711      *
4712      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4713      *
4714      * @param size The scaled pixel size.
4715      *
4716      * @attr ref android.R.styleable#TextView_textSize
4717      */
4718     @android.view.RemotableViewMethod
setTextSize(float size)4719     public void setTextSize(float size) {
4720         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4721     }
4722 
4723     /**
4724      * Set the default text size to a given unit and value. See {@link
4725      * TypedValue} for the possible dimension units.
4726      *
4727      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4728      *
4729      * @param unit The desired dimension unit.
4730      * @param size The desired size in the given units.
4731      *
4732      * @attr ref android.R.styleable#TextView_textSize
4733      */
setTextSize(int unit, float size)4734     public void setTextSize(int unit, float size) {
4735         if (!isAutoSizeEnabled()) {
4736             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4737         }
4738     }
4739 
4740     @NonNull
getDisplayMetricsOrSystem()4741     private DisplayMetrics getDisplayMetricsOrSystem() {
4742         Context c = getContext();
4743         Resources r;
4744 
4745         if (c == null) {
4746             r = Resources.getSystem();
4747         } else {
4748             r = c.getResources();
4749         }
4750 
4751         return r.getDisplayMetrics();
4752     }
4753 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4754     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4755         mTextSizeUnit = unit;
4756         setRawTextSize(TypedValue.applyDimension(unit, size, getDisplayMetricsOrSystem()),
4757                 shouldRequestLayout);
4758     }
4759 
4760     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setRawTextSize(float size, boolean shouldRequestLayout)4761     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4762         if (size != mTextPaint.getTextSize()) {
4763             mTextPaint.setTextSize(size);
4764 
4765             maybeRecalculateLineHeight();
4766             if (shouldRequestLayout && mLayout != null) {
4767                 // Do not auto-size right after setting the text size.
4768                 mNeedsAutoSizeText = false;
4769                 nullLayouts();
4770                 requestLayout();
4771                 invalidate();
4772             }
4773         }
4774     }
4775 
4776     /**
4777      * Gets the text size unit defined by the developer. It may be specified in resources or be
4778      * passed as the unit argument of {@link #setTextSize(int, float)} at runtime.
4779      *
4780      * @return the dimension type of the text size unit originally defined.
4781      * @see TypedValue#TYPE_DIMENSION
4782      */
getTextSizeUnit()4783     public int getTextSizeUnit() {
4784         return mTextSizeUnit;
4785     }
4786 
4787     /**
4788      * Gets the extent by which text should be stretched horizontally.
4789      * This will usually be 1.0.
4790      * @return The horizontal scale factor.
4791      */
4792     @InspectableProperty
getTextScaleX()4793     public float getTextScaleX() {
4794         return mTextPaint.getTextScaleX();
4795     }
4796 
4797     /**
4798      * Sets the horizontal scale factor for text. The default value
4799      * is 1.0. Values greater than 1.0 stretch the text wider.
4800      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4801      * @param size The horizontal scale factor.
4802      * @attr ref android.R.styleable#TextView_textScaleX
4803      */
4804     @android.view.RemotableViewMethod
setTextScaleX(float size)4805     public void setTextScaleX(float size) {
4806         if (size != mTextPaint.getTextScaleX()) {
4807             mUserSetTextScaleX = true;
4808             mTextPaint.setTextScaleX(size);
4809 
4810             if (mLayout != null) {
4811                 nullLayouts();
4812                 requestLayout();
4813                 invalidate();
4814             }
4815         }
4816     }
4817 
4818     /**
4819      * Sets the typeface and style in which the text should be displayed.
4820      * Note that not all Typeface families actually have bold and italic
4821      * variants, so you may need to use
4822      * {@link #setTypeface(Typeface, int)} to get the appearance
4823      * that you actually want.
4824      *
4825      * @see #getTypeface()
4826      *
4827      * @attr ref android.R.styleable#TextView_fontFamily
4828      * @attr ref android.R.styleable#TextView_typeface
4829      * @attr ref android.R.styleable#TextView_textStyle
4830      */
setTypeface(@ullable Typeface tf)4831     public void setTypeface(@Nullable Typeface tf) {
4832         mOriginalTypeface = tf;
4833         if (mFontWeightAdjustment != 0
4834                 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
4835             if (tf == null) {
4836                 if (Flags.fixNullTypefaceBolding()) {
4837                     tf = Typeface.DEFAULT_BOLD;
4838                 } else {
4839                     tf = Typeface.DEFAULT;
4840                 }
4841             } else {
4842                 int newWeight = Math.min(
4843                         Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
4844                         FontStyle.FONT_WEIGHT_MAX);
4845                 int typefaceStyle = tf != null ? tf.getStyle() : 0;
4846                 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0;
4847                 tf = Typeface.create(tf, newWeight, italic);
4848             }
4849         }
4850         if (mTextPaint.getTypeface() != tf) {
4851             mTextPaint.setTypeface(tf);
4852 
4853             if (mLayout != null) {
4854                 nullLayouts();
4855                 requestLayout();
4856                 invalidate();
4857             }
4858         }
4859     }
4860 
4861     /**
4862      * Gets the current {@link Typeface} that is used to style the text.
4863      * @return The current Typeface.
4864      *
4865      * @see #setTypeface(Typeface)
4866      *
4867      * @attr ref android.R.styleable#TextView_fontFamily
4868      * @attr ref android.R.styleable#TextView_typeface
4869      * @attr ref android.R.styleable#TextView_textStyle
4870      */
4871     @InspectableProperty
getTypeface()4872     public Typeface getTypeface() {
4873         return mOriginalTypeface;
4874     }
4875 
4876     /**
4877      * Set the TextView's elegant height metrics flag. This setting selects font
4878      * variants that have not been compacted to fit Latin-based vertical
4879      * metrics, and also increases top and bottom bounds to provide more space.
4880      *
4881      * @param elegant set the paint's elegant metrics flag.
4882      *
4883      * @see #isElegantTextHeight()
4884      * @see Paint#isElegantTextHeight()
4885      *
4886      * @attr ref android.R.styleable#TextView_elegantTextHeight
4887      */
setElegantTextHeight(boolean elegant)4888     public void setElegantTextHeight(boolean elegant) {
4889         if (elegant != mTextPaint.isElegantTextHeight()) {
4890             mTextPaint.setElegantTextHeight(elegant);
4891             if (mLayout != null) {
4892                 nullLayouts();
4893                 requestLayout();
4894                 invalidate();
4895             }
4896         }
4897     }
4898 
4899     /**
4900      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4901      * displaying the text (which is needed to avoid text from consecutive lines running into
4902      * each other). If set, fallback fonts that end up getting used can increase the ascent
4903      * and descent of the lines that they are used on.
4904      * <p/>
4905      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4906      * is typically much taller or deeper than Latin text.
4907      *
4908      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4909      *
4910      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4911      *
4912      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4913      */
setFallbackLineSpacing(boolean enabled)4914     public void setFallbackLineSpacing(boolean enabled) {
4915         int fallbackStrategy;
4916         if (enabled) {
4917             if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
4918                 fallbackStrategy = FALLBACK_LINE_SPACING_ALL;
4919             } else {
4920                 fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
4921             }
4922         } else {
4923             fallbackStrategy = FALLBACK_LINE_SPACING_NONE;
4924         }
4925         if (mUseFallbackLineSpacing != fallbackStrategy) {
4926             mUseFallbackLineSpacing = fallbackStrategy;
4927             if (mLayout != null) {
4928                 nullLayouts();
4929                 requestLayout();
4930                 invalidate();
4931             }
4932         }
4933     }
4934 
4935     /**
4936      * Set true for using width of bounding box as a source of automatic line breaking and drawing.
4937      *
4938      * If this value is false, the TextView determines the View width, drawing offset and automatic
4939      * line breaking based on total advances as text widths. By setting true, use glyph bound's as a
4940      * source of text width.
4941      *
4942      * If the font used for this TextView has glyphs that has negative bearing X or glyph xMax is
4943      * greater than advance, the glyph clipping can be happened because the drawing area may be
4944      * bigger than advance. By setting this to true, the TextView will reserve more spaces for
4945      * drawing are, so clipping can be prevented.
4946      *
4947      * This value is true by default if the target API version is 35 or later.
4948      *
4949      * @param useBoundsForWidth true for using bounding box for width. false for using advances for
4950      *                          width.
4951      * @see #getUseBoundsForWidth()
4952      * @see #setShiftDrawingOffsetForStartOverhang(boolean)
4953      * @see #getShiftDrawingOffsetForStartOverhang()
4954      */
4955     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
setUseBoundsForWidth(boolean useBoundsForWidth)4956     public void setUseBoundsForWidth(boolean useBoundsForWidth) {
4957         if (mUseBoundsForWidth != useBoundsForWidth) {
4958             mUseBoundsForWidth = useBoundsForWidth;
4959             if (mLayout != null) {
4960                 nullLayouts();
4961                 requestLayout();
4962                 invalidate();
4963             }
4964         }
4965     }
4966 
4967     /**
4968      * Returns true if using bounding box as a width, false for using advance as a width.
4969      *
4970      * @see #setUseBoundsForWidth(boolean)
4971      * @see #setShiftDrawingOffsetForStartOverhang(boolean)
4972      * @see #getShiftDrawingOffsetForStartOverhang()
4973      * @return True if using bounding box for width, false if using advance for width.
4974      */
4975     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
getUseBoundsForWidth()4976     public boolean getUseBoundsForWidth() {
4977         return mUseBoundsForWidth;
4978     }
4979 
4980     /**
4981      * Set true for shifting the drawing x offset for showing overhang at the start position.
4982      *
4983      * This flag is ignored if the {@link #getUseBoundsForWidth()} is false.
4984      *
4985      * If this value is false, the TextView draws text from the zero even if there is a glyph stroke
4986      * in a region where the x coordinate is negative. TextView clips the stroke in the region where
4987      * the X coordinate is negative unless the parents has {@link ViewGroup#getClipChildren()} to
4988      * true. This is useful for aligning multiple TextViews vertically.
4989      *
4990      * If this value is true, the TextView draws text with shifting the x coordinate of the drawing
4991      * bounding box. This prevents the clipping even if the parents doesn't have
4992      * {@link ViewGroup#getClipChildren()} to true.
4993      *
4994      * This value is false by default.
4995      *
4996      * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for showing
4997      *                                           the stroke that is in the region whre the x
4998      *                                           coorinate is negative.
4999      * @see #setUseBoundsForWidth(boolean)
5000      * @see #getUseBoundsForWidth()
5001      */
5002     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang)5003     public void setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang) {
5004         if (mShiftDrawingOffsetForStartOverhang != shiftDrawingOffsetForStartOverhang) {
5005             mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
5006             if (mLayout != null) {
5007                 nullLayouts();
5008                 requestLayout();
5009                 invalidate();
5010             }
5011         }
5012     }
5013 
5014     /**
5015      * Returns true if shifting the drawing x offset for start overhang.
5016      *
5017      * @see #setShiftDrawingOffsetForStartOverhang(boolean)
5018      * @see #setUseBoundsForWidth(boolean)
5019      * @see #getUseBoundsForWidth()
5020      * @return True if shifting the drawing x offset for start overhang.
5021      */
5022     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
getShiftDrawingOffsetForStartOverhang()5023     public boolean getShiftDrawingOffsetForStartOverhang() {
5024         return mShiftDrawingOffsetForStartOverhang;
5025     }
5026 
5027     /**
5028      * Set the minimum font metrics used for line spacing.
5029      *
5030      * <p>
5031      * {@code null} is the default value. If {@code null} is set or left as default, the font
5032      * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
5033      *
5034      * <p>
5035      * The minimum meaning here is the minimum value of line spacing: maximum value of
5036      * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
5037      *
5038      * <p>
5039      * By setting this value, each line will have minimum line spacing regardless of the text
5040      * rendered. For example, usually Japanese script has larger vertical metrics than Latin script.
5041      * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
5042      * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese,
5043      * the line spacing for Japanese is reserved if the TextView contains English text. If the
5044      * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font
5045      * metrics is used.
5046      *
5047      * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value
5048      *                           obtained by
5049      *                           {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
5050      * @see #getMinimumFontMetrics()
5051      * @see Layout#getMinimumFontMetrics()
5052      * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5053      * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5054      * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5055      */
5056     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
setMinimumFontMetrics(@ullable Paint.FontMetrics minimumFontMetrics)5057     public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
5058         mMinimumFontMetrics = minimumFontMetrics;
5059     }
5060 
5061     /**
5062      * Get the minimum font metrics used for line spacing.
5063      *
5064      * @see #setMinimumFontMetrics(Paint.FontMetrics)
5065      * @see Layout#getMinimumFontMetrics()
5066      * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5067      * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5068      * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5069      *
5070      * @return a minimum font metrics. {@code null} for using the value obtained by
5071      *         {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
5072      */
5073     @Nullable
5074     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
getMinimumFontMetrics()5075     public Paint.FontMetrics getMinimumFontMetrics() {
5076         return mMinimumFontMetrics;
5077     }
5078 
5079     /**
5080      * Returns true if the locale preferred line height is used for the minimum line height.
5081      *
5082      * @return true if using locale preferred line height for the minimum line height. Otherwise
5083      *         false.
5084      *
5085      * @see #setLocalePreferredLineHeightForMinimumUsed(boolean)
5086      * @see #setMinimumFontMetrics(Paint.FontMetrics)
5087      * @see #getMinimumFontMetrics()
5088      */
5089     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
isLocalePreferredLineHeightForMinimumUsed()5090     public boolean isLocalePreferredLineHeightForMinimumUsed() {
5091         return mUseLocalePreferredLineHeightForMinimum;
5092     }
5093 
5094     /**
5095      * Set true if the locale preferred line height is used for the minimum line height.
5096      *
5097      * By setting this flag to true is equivalenet to call
5098      * {@link #setMinimumFontMetrics(Paint.FontMetrics)} with the one obtained by
5099      * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}.
5100      *
5101      * If custom minimum line height was specified by
5102      * {@link #setMinimumFontMetrics(Paint.FontMetrics)}, this flag will be ignored.
5103      *
5104      * @param flag true for using locale preferred line height for the minimum line height.
5105      * @see #isLocalePreferredLineHeightForMinimumUsed()
5106      * @see #setMinimumFontMetrics(Paint.FontMetrics)
5107      * @see #getMinimumFontMetrics()
5108      */
5109     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
setLocalePreferredLineHeightForMinimumUsed(boolean flag)5110     public void setLocalePreferredLineHeightForMinimumUsed(boolean flag) {
5111         mUseLocalePreferredLineHeightForMinimum = flag;
5112     }
5113 
5114     /**
5115      * @return whether fallback line spacing is enabled, {@code true} by default
5116      *
5117      * @see #setFallbackLineSpacing(boolean)
5118      *
5119      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
5120      */
5121     @InspectableProperty
isFallbackLineSpacing()5122     public boolean isFallbackLineSpacing() {
5123         return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE;
5124     }
5125 
isFallbackLineSpacingForBoringLayout()5126     private boolean isFallbackLineSpacingForBoringLayout() {
5127         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL;
5128     }
5129 
5130     // Package privte for accessing from Editor.java
isFallbackLineSpacingForStaticLayout()5131     /* package */ boolean isFallbackLineSpacingForStaticLayout() {
5132         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL
5133                 || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
5134     }
5135 
5136     /**
5137      * Get the value of the TextView's elegant height metrics flag. This setting selects font
5138      * variants that have not been compacted to fit Latin-based vertical
5139      * metrics, and also increases top and bottom bounds to provide more space.
5140      * @return {@code true} if the elegant height metrics flag is set.
5141      *
5142      * @see #setElegantTextHeight(boolean)
5143      * @see Paint#setElegantTextHeight(boolean)
5144      */
5145     @InspectableProperty
isElegantTextHeight()5146     public boolean isElegantTextHeight() {
5147         return mTextPaint.isElegantTextHeight();
5148     }
5149 
5150     /**
5151      * Gets the text letter-space value, which determines the spacing between characters.
5152      * The value returned is in ems. Normally, this value is 0.0.
5153      * @return The text letter-space value in ems.
5154      *
5155      * @see #setLetterSpacing(float)
5156      * @see Paint#setLetterSpacing
5157      */
5158     @InspectableProperty
getLetterSpacing()5159     public float getLetterSpacing() {
5160         return mTextPaint.getLetterSpacing();
5161     }
5162 
5163     /**
5164      * Sets text letter-spacing in em units.  Typical values
5165      * for slight expansion will be around 0.05.  Negative values tighten text.
5166      *
5167      * @see #getLetterSpacing()
5168      * @see Paint#getLetterSpacing
5169      *
5170      * @param letterSpacing A text letter-space value in ems.
5171      * @attr ref android.R.styleable#TextView_letterSpacing
5172      */
5173     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)5174     public void setLetterSpacing(float letterSpacing) {
5175         if (letterSpacing != mTextPaint.getLetterSpacing()) {
5176             mTextPaint.setLetterSpacing(letterSpacing);
5177 
5178             if (mLayout != null) {
5179                 nullLayouts();
5180                 requestLayout();
5181                 invalidate();
5182             }
5183         }
5184     }
5185 
5186     /**
5187      * Returns the font feature settings. The format is the same as the CSS
5188      * font-feature-settings attribute:
5189      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
5190      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
5191      *
5192      * @return the currently set font feature settings.  Default is null.
5193      *
5194      * @see #setFontFeatureSettings(String)
5195      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
5196      */
5197     @InspectableProperty
5198     @Nullable
getFontFeatureSettings()5199     public String getFontFeatureSettings() {
5200         return mTextPaint.getFontFeatureSettings();
5201     }
5202 
5203     /**
5204      * Returns the font variation settings.
5205      *
5206      * @return the currently set font variation settings.  Returns null if no variation is
5207      * specified.
5208      *
5209      * @see #setFontVariationSettings(String)
5210      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
5211      */
5212     @Nullable
getFontVariationSettings()5213     public String getFontVariationSettings() {
5214         return mTextPaint.getFontVariationSettings();
5215     }
5216 
5217     /**
5218      * Sets the break strategy for breaking paragraphs into lines. The default value for
5219      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
5220      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
5221      * text "dancing" when being edited.
5222      * <p>
5223      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
5224      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
5225      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
5226      * improves the structure of text layout however has performance impact and requires more time
5227      * to do the text layout.</p>
5228      * <p>
5229      * Compared with {@link #setLineBreakStyle(int)}, line break style with different strictness is
5230      * evaluated in the ICU to identify the potential breakpoints. In
5231      * {@link #setBreakStrategy(int)}, line break strategy handles the post processing of ICU's line
5232      * break result. It aims to evaluate ICU's breakpoints and break the lines based on the
5233      * constraint.
5234      * </p>
5235      *
5236      * @attr ref android.R.styleable#TextView_breakStrategy
5237      * @see #getBreakStrategy()
5238      * @see #setHyphenationFrequency(int)
5239      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)5240     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
5241         mBreakStrategy = breakStrategy;
5242         if (mLayout != null) {
5243             nullLayouts();
5244             requestLayout();
5245             invalidate();
5246         }
5247     }
5248 
5249     /**
5250      * Gets the current strategy for breaking paragraphs into lines.
5251      * @return the current strategy for breaking paragraphs into lines.
5252      *
5253      * @attr ref android.R.styleable#TextView_breakStrategy
5254      * @see #setBreakStrategy(int)
5255      */
5256     @InspectableProperty(enumMapping = {
5257             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
5258             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
5259             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
5260     })
5261     @Layout.BreakStrategy
getBreakStrategy()5262     public int getBreakStrategy() {
5263         return mBreakStrategy;
5264     }
5265 
5266     /**
5267      * Sets the frequency of automatic hyphenation to use when determining word breaks.
5268      * The default value for both TextView and {@link EditText} is
5269      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
5270      * is set from the theme.
5271      * <p/>
5272      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
5273      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
5274      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
5275      * improves the structure of text layout however has performance impact and requires more time
5276      * to do the text layout.
5277      * <p/>
5278      * Note: Before Android Q, in the theme hyphenation frequency is set to
5279      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
5280      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
5281      *
5282      * @param hyphenationFrequency the hyphenation frequency to use, one of
5283      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
5284      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
5285      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
5286      * @attr ref android.R.styleable#TextView_hyphenationFrequency
5287      * @see #getHyphenationFrequency()
5288      * @see #getBreakStrategy()
5289      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)5290     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
5291         mHyphenationFrequency = hyphenationFrequency;
5292         if (mLayout != null) {
5293             nullLayouts();
5294             requestLayout();
5295             invalidate();
5296         }
5297     }
5298 
5299     /**
5300      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
5301      * @return the current frequency of automatic hyphenation to be used when determining word
5302      * breaks.
5303      *
5304      * @attr ref android.R.styleable#TextView_hyphenationFrequency
5305      * @see #setHyphenationFrequency(int)
5306      */
5307     @InspectableProperty(enumMapping = {
5308             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
5309             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
5310             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
5311     })
5312     @Layout.HyphenationFrequency
getHyphenationFrequency()5313     public int getHyphenationFrequency() {
5314         return mHyphenationFrequency;
5315     }
5316 
5317     /**
5318      * Sets the line-break style for text wrapping.
5319      *
5320      * <p>Line-break style specifies the line-break strategies that can be used
5321      * for text wrapping. The line-break style affects rule-based line breaking
5322      * by specifying the strictness of line-breaking rules.
5323      *
5324      * <p>The following are types of line-break styles:
5325      * <ul>
5326      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}
5327      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}
5328      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}
5329      * </ul>
5330      *
5331      * <p>The default line-break style is
5332      * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
5333      * line-breaking rules are used.
5334      *
5335      * <p>See the
5336      * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
5337      * line-break property</a> for more information.
5338      *
5339      * @param lineBreakStyle The line-break style for the text.
5340      */
setLineBreakStyle(@ineBreakConfig.LineBreakStyle int lineBreakStyle)5341     public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
5342         if (mLineBreakStyle != lineBreakStyle) {
5343             mLineBreakStyle = lineBreakStyle;
5344             if (mLayout != null) {
5345                 nullLayouts();
5346                 requestLayout();
5347                 invalidate();
5348             }
5349         }
5350     }
5351 
5352     /**
5353      * Sets the line-break word style for text wrapping.
5354      *
5355      * <p>The line-break word style affects dictionary-based line breaking by
5356      * providing phrase-based line-breaking opportunities. Use
5357      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
5358      * phrase-based line breaking.
5359      *
5360      * <p>The default line-break word style is
5361      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
5362      * no line-breaking word style is used.
5363      *
5364      * <p>See the
5365      * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
5366      * word-break property</a> for more information.
5367      *
5368      * @param lineBreakWordStyle The line-break word style for the text.
5369      */
setLineBreakWordStyle(@ineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)5370     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
5371         if (mLineBreakWordStyle != lineBreakWordStyle) {
5372             mLineBreakWordStyle = lineBreakWordStyle;
5373             if (mLayout != null) {
5374                 nullLayouts();
5375                 requestLayout();
5376                 invalidate();
5377             }
5378         }
5379     }
5380 
5381     /**
5382      * Gets the current line-break style for text wrapping.
5383      *
5384      * @return The line-break style to be used for text wrapping.
5385      */
getLineBreakStyle()5386     public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
5387         return mLineBreakStyle;
5388     }
5389 
5390     /**
5391      * Gets the current line-break word style for text wrapping.
5392      *
5393      * @return The line-break word style to be used for text wrapping.
5394      */
getLineBreakWordStyle()5395     public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
5396         return mLineBreakWordStyle;
5397     }
5398 
5399     /**
5400      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
5401      *
5402      * @return a current {@link PrecomputedText.Params}
5403      * @see PrecomputedText
5404      */
getTextMetricsParams()5405     public @NonNull PrecomputedText.Params getTextMetricsParams() {
5406         return new PrecomputedText.Params(new TextPaint(mTextPaint),
5407                 LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
5408                 getTextDirectionHeuristic(),
5409                 mBreakStrategy, mHyphenationFrequency);
5410     }
5411 
5412     /**
5413      * Apply the text layout parameter.
5414      *
5415      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
5416      * @see PrecomputedText
5417      */
setTextMetricsParams(@onNull PrecomputedText.Params params)5418     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
5419         mTextPaint.set(params.getTextPaint());
5420         mUserSetTextScaleX = true;
5421         mTextDir = params.getTextDirection();
5422         mBreakStrategy = params.getBreakStrategy();
5423         mHyphenationFrequency = params.getHyphenationFrequency();
5424         LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
5425         mLineBreakStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig);
5426         mLineBreakWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig);
5427         if (mLayout != null) {
5428             nullLayouts();
5429             requestLayout();
5430             invalidate();
5431         }
5432     }
5433 
5434     /**
5435      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
5436      * last line is too short for justification, the last line will be displayed with the
5437      * alignment set by {@link android.view.View#setTextAlignment}.
5438      *
5439      * @see #getJustificationMode()
5440      */
5441     @Layout.JustificationMode
5442     @android.view.RemotableViewMethod
setJustificationMode(@ayout.JustificationMode int justificationMode)5443     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
5444         mJustificationMode = justificationMode;
5445         if (mLayout != null) {
5446             nullLayouts();
5447             requestLayout();
5448             invalidate();
5449         }
5450     }
5451 
5452     /**
5453      * @return true if currently paragraph justification mode.
5454      *
5455      * @see #setJustificationMode(int)
5456      */
5457     @InspectableProperty(enumMapping = {
5458             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
5459             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
5460     })
getJustificationMode()5461     public @Layout.JustificationMode int getJustificationMode() {
5462         return mJustificationMode;
5463     }
5464 
5465     /**
5466      * Sets font feature settings. The format is the same as the CSS
5467      * font-feature-settings attribute:
5468      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
5469      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
5470      *
5471      * @param fontFeatureSettings font feature settings represented as CSS compatible string
5472      *
5473      * @see #getFontFeatureSettings()
5474      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
5475      *
5476      * @attr ref android.R.styleable#TextView_fontFeatureSettings
5477      */
5478     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)5479     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
5480         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
5481             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
5482 
5483             if (mLayout != null) {
5484                 nullLayouts();
5485                 requestLayout();
5486                 invalidate();
5487             }
5488         }
5489     }
5490 
5491 
5492     /**
5493      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
5494      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
5495      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
5496      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
5497      * are invalid. If a specified axis name is not defined in the font, the settings will be
5498      * ignored.
5499      *
5500      * <p>
5501      * Examples,
5502      * <ul>
5503      * <li>Set font width to 150.
5504      * <pre>
5505      * <code>
5506      *   TextView textView = (TextView) findViewById(R.id.textView);
5507      *   textView.setFontVariationSettings("'wdth' 150");
5508      * </code>
5509      * </pre>
5510      * </li>
5511      *
5512      * <li>Set the font slant to 20 degrees and ask for italic style.
5513      * <pre>
5514      * <code>
5515      *   TextView textView = (TextView) findViewById(R.id.textView);
5516      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
5517      * </code>
5518      * </pre>
5519      * </p>
5520      * </li>
5521      * </ul>
5522      *
5523      * @param fontVariationSettings font variation settings. You can pass null or empty string as
5524      *                              no variation settings.
5525      * @return true if the given settings is effective to at least one font file underlying this
5526      *         TextView. This function also returns true for empty settings string. Otherwise
5527      *         returns false.
5528      *
5529      * @throws IllegalArgumentException If given string is not a valid font variation settings
5530      *                                  format.
5531      *
5532      * @see #getFontVariationSettings()
5533      * @see FontVariationAxis
5534      *
5535      * @attr ref android.R.styleable#TextView_fontVariationSettings
5536      */
5537     @android.view.RemotableViewMethod
setFontVariationSettings(@ullable String fontVariationSettings)5538     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
5539         final String existingSettings = mTextPaint.getFontVariationSettings();
5540         if (fontVariationSettings == existingSettings
5541                 || (fontVariationSettings != null
5542                         && fontVariationSettings.equals(existingSettings))) {
5543             return true;
5544         }
5545 
5546         boolean effective;
5547         if (Flags.typefaceRedesignReadonly()) {
5548             if (mFontWeightAdjustment != 0
5549                     && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
5550                 List<FontVariationAxis> axes = FontVariationAxis.fromFontVariationSettingsForList(
5551                         fontVariationSettings);
5552                 if (axes == null) {
5553                     return false;  // invalid format of the font variation settings.
5554                 }
5555                 boolean wghtAdjusted = false;
5556                 for (int i = 0; i < axes.size(); ++i) {
5557                     FontVariationAxis axis = axes.get(i);
5558                     if (axis.getOpenTypeTagValue() == 0x77676874 /* wght */) {
5559                         axes.set(i, new FontVariationAxis("wght",
5560                                 Math.clamp(axis.getStyleValue() + mFontWeightAdjustment,
5561                                         FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
5562                         wghtAdjusted = true;
5563                     }
5564                 }
5565                 if (!wghtAdjusted) {
5566                     axes.add(new FontVariationAxis("wght",
5567                             Math.clamp(400 + mFontWeightAdjustment,
5568                                     FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
5569                 }
5570                 mTextPaint.setFontVariationSettings(
5571                         FontVariationAxis.toFontVariationSettings(axes));
5572             } else {
5573                 mTextPaint.setFontVariationSettings(fontVariationSettings);
5574             }
5575             effective = true;
5576         } else {
5577             effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
5578         }
5579 
5580         if (effective && mLayout != null) {
5581             nullLayouts();
5582             requestLayout();
5583             invalidate();
5584         }
5585         return effective;
5586     }
5587 
5588     /**
5589      * Sets the text color for all the states (normal, selected,
5590      * focused) to be this color.
5591      *
5592      * @param color A color value in the form 0xAARRGGBB.
5593      * Do not pass a resource ID. To get a color value from a resource ID, call
5594      * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}.
5595      *
5596      * @see #setTextColor(ColorStateList)
5597      * @see #getTextColors()
5598      *
5599      * @attr ref android.R.styleable#TextView_textColor
5600      */
5601     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)5602     public void setTextColor(@ColorInt int color) {
5603         mTextColor = ColorStateList.valueOf(color);
5604         updateTextColors();
5605     }
5606 
5607     /**
5608      * Sets the text color.
5609      *
5610      * @see #setTextColor(int)
5611      * @see #getTextColors()
5612      * @see #setHintTextColor(ColorStateList)
5613      * @see #setLinkTextColor(ColorStateList)
5614      *
5615      * @attr ref android.R.styleable#TextView_textColor
5616      */
5617     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)5618     public void setTextColor(ColorStateList colors) {
5619         if (colors == null) {
5620             throw new NullPointerException();
5621         }
5622 
5623         mTextColor = colors;
5624         updateTextColors();
5625     }
5626 
5627     /**
5628      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
5629      *
5630      * @see #setTextColor(ColorStateList)
5631      * @see #setTextColor(int)
5632      *
5633      * @attr ref android.R.styleable#TextView_textColor
5634      */
5635     @InspectableProperty(name = "textColor")
getTextColors()5636     public final ColorStateList getTextColors() {
5637         return mTextColor;
5638     }
5639 
5640     /**
5641      * Return the current color selected for normal text.
5642      *
5643      * @return Returns the current text color.
5644      */
5645     @ColorInt
getCurrentTextColor()5646     public final int getCurrentTextColor() {
5647         return mCurTextColor;
5648     }
5649 
5650     /**
5651      * Sets the color used to display the selection highlight.
5652      *
5653      * @attr ref android.R.styleable#TextView_textColorHighlight
5654      */
5655     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)5656     public void setHighlightColor(@ColorInt int color) {
5657         if (mHighlightColor != color) {
5658             mHighlightColor = color;
5659             invalidate();
5660         }
5661     }
5662 
5663     /**
5664      * @return the color used to display the selection highlight
5665      *
5666      * @see #setHighlightColor(int)
5667      *
5668      * @attr ref android.R.styleable#TextView_textColorHighlight
5669      */
5670     @InspectableProperty(name = "textColorHighlight")
5671     @ColorInt
getHighlightColor()5672     public int getHighlightColor() {
5673         return mHighlightColor;
5674     }
5675 
5676     /**
5677      * Sets whether the soft input method will be made visible when this
5678      * TextView gets focused. The default is true.
5679      */
5680     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)5681     public final void setShowSoftInputOnFocus(boolean show) {
5682         createEditorIfNeeded();
5683         mEditor.mShowSoftInputOnFocus = show;
5684     }
5685 
5686     /**
5687      * Returns whether the soft input method will be made visible when this
5688      * TextView gets focused. The default is true.
5689      */
getShowSoftInputOnFocus()5690     public final boolean getShowSoftInputOnFocus() {
5691         // When there is no Editor, return default true value
5692         return mEditor == null || mEditor.mShowSoftInputOnFocus;
5693     }
5694 
5695     /**
5696      * Gives the text a shadow of the specified blur radius and color, the specified
5697      * distance from its drawn position.
5698      * <p>
5699      * The text shadow produced does not interact with the properties on view
5700      * that are responsible for real time shadows,
5701      * {@link View#getElevation() elevation} and
5702      * {@link View#getTranslationZ() translationZ}.
5703      *
5704      * @see Paint#setShadowLayer(float, float, float, int)
5705      *
5706      * @attr ref android.R.styleable#TextView_shadowColor
5707      * @attr ref android.R.styleable#TextView_shadowDx
5708      * @attr ref android.R.styleable#TextView_shadowDy
5709      * @attr ref android.R.styleable#TextView_shadowRadius
5710      */
setShadowLayer(float radius, float dx, float dy, int color)5711     public void setShadowLayer(float radius, float dx, float dy, int color) {
5712         mTextPaint.setShadowLayer(radius, dx, dy, color);
5713 
5714         mShadowRadius = radius;
5715         mShadowDx = dx;
5716         mShadowDy = dy;
5717         mShadowColor = color;
5718 
5719         // Will change text clip region
5720         if (mEditor != null) {
5721             mEditor.invalidateTextDisplayList();
5722             mEditor.invalidateHandlesAndActionMode();
5723         }
5724         invalidate();
5725     }
5726 
5727     /**
5728      * Gets the radius of the shadow layer.
5729      *
5730      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
5731      *
5732      * @see #setShadowLayer(float, float, float, int)
5733      *
5734      * @attr ref android.R.styleable#TextView_shadowRadius
5735      */
5736     @InspectableProperty
getShadowRadius()5737     public float getShadowRadius() {
5738         return mShadowRadius;
5739     }
5740 
5741     /**
5742      * @return the horizontal offset of the shadow layer
5743      *
5744      * @see #setShadowLayer(float, float, float, int)
5745      *
5746      * @attr ref android.R.styleable#TextView_shadowDx
5747      */
5748     @InspectableProperty
getShadowDx()5749     public float getShadowDx() {
5750         return mShadowDx;
5751     }
5752 
5753     /**
5754      * Gets the vertical offset of the shadow layer.
5755      * @return The vertical offset of the shadow layer.
5756      *
5757      * @see #setShadowLayer(float, float, float, int)
5758      *
5759      * @attr ref android.R.styleable#TextView_shadowDy
5760      */
5761     @InspectableProperty
getShadowDy()5762     public float getShadowDy() {
5763         return mShadowDy;
5764     }
5765 
5766     /**
5767      * Gets the color of the shadow layer.
5768      * @return the color of the shadow layer
5769      *
5770      * @see #setShadowLayer(float, float, float, int)
5771      *
5772      * @attr ref android.R.styleable#TextView_shadowColor
5773      */
5774     @InspectableProperty
5775     @ColorInt
getShadowColor()5776     public int getShadowColor() {
5777         return mShadowColor;
5778     }
5779 
5780     /**
5781      * Gets the {@link TextPaint} used for the text.
5782      * Use this only to consult the Paint's properties and not to change them.
5783      * @return The base paint used for the text.
5784      */
getPaint()5785     public TextPaint getPaint() {
5786         return mTextPaint;
5787     }
5788 
5789     /**
5790      * Sets the autolink mask of the text.  See {@link
5791      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
5792      * possible values.
5793      *
5794      * <p class="note"><b>Note:</b>
5795      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
5796      * is deprecated and should be avoided; see its documentation.
5797      *
5798      * @attr ref android.R.styleable#TextView_autoLink
5799      */
5800     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)5801     public final void setAutoLinkMask(int mask) {
5802         mAutoLinkMask = mask;
5803     }
5804 
5805     /**
5806      * Sets whether the movement method will automatically be set to
5807      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5808      * set to nonzero and links are detected in {@link #setText}.
5809      * The default is true.
5810      *
5811      * @attr ref android.R.styleable#TextView_linksClickable
5812      */
5813     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)5814     public final void setLinksClickable(boolean whether) {
5815         mLinksClickable = whether;
5816     }
5817 
5818     /**
5819      * Returns whether the movement method will automatically be set to
5820      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5821      * set to nonzero and links are detected in {@link #setText}.
5822      * The default is true.
5823      *
5824      * @attr ref android.R.styleable#TextView_linksClickable
5825      */
5826     @InspectableProperty
getLinksClickable()5827     public final boolean getLinksClickable() {
5828         return mLinksClickable;
5829     }
5830 
5831     /**
5832      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5833      * (by {@link Linkify} or otherwise) if any.  You can call
5834      * {@link URLSpan#getURL} on them to find where they link to
5835      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5836      * to find the region of the text they are attached to.
5837      */
getUrls()5838     public URLSpan[] getUrls() {
5839         if (mText instanceof Spanned) {
5840             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5841         } else {
5842             return new URLSpan[0];
5843         }
5844     }
5845 
5846     /**
5847      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5848      * TextView.
5849      *
5850      * @see #setHintTextColor(ColorStateList)
5851      * @see #getHintTextColors()
5852      * @see #setTextColor(int)
5853      *
5854      * @attr ref android.R.styleable#TextView_textColorHint
5855      */
5856     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)5857     public final void setHintTextColor(@ColorInt int color) {
5858         mHintTextColor = ColorStateList.valueOf(color);
5859         updateTextColors();
5860     }
5861 
5862     /**
5863      * Sets the color of the hint text.
5864      *
5865      * @see #getHintTextColors()
5866      * @see #setHintTextColor(int)
5867      * @see #setTextColor(ColorStateList)
5868      * @see #setLinkTextColor(ColorStateList)
5869      *
5870      * @attr ref android.R.styleable#TextView_textColorHint
5871      */
setHintTextColor(ColorStateList colors)5872     public final void setHintTextColor(ColorStateList colors) {
5873         mHintTextColor = colors;
5874         updateTextColors();
5875     }
5876 
5877     /**
5878      * @return the color of the hint text, for the different states of this TextView.
5879      *
5880      * @see #setHintTextColor(ColorStateList)
5881      * @see #setHintTextColor(int)
5882      * @see #setTextColor(ColorStateList)
5883      * @see #setLinkTextColor(ColorStateList)
5884      *
5885      * @attr ref android.R.styleable#TextView_textColorHint
5886      */
5887     @InspectableProperty(name = "textColorHint")
getHintTextColors()5888     public final ColorStateList getHintTextColors() {
5889         return mHintTextColor;
5890     }
5891 
5892     /**
5893      * <p>Return the current color selected to paint the hint text.</p>
5894      *
5895      * @return Returns the current hint text color.
5896      */
5897     @ColorInt
getCurrentHintTextColor()5898     public final int getCurrentHintTextColor() {
5899         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5900     }
5901 
5902     /**
5903      * Sets the color of links in the text.
5904      *
5905      * @see #setLinkTextColor(ColorStateList)
5906      * @see #getLinkTextColors()
5907      *
5908      * @attr ref android.R.styleable#TextView_textColorLink
5909      */
5910     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)5911     public final void setLinkTextColor(@ColorInt int color) {
5912         mLinkTextColor = ColorStateList.valueOf(color);
5913         updateTextColors();
5914     }
5915 
5916     /**
5917      * Sets the color of links in the text.
5918      *
5919      * @see #setLinkTextColor(int)
5920      * @see #getLinkTextColors()
5921      * @see #setTextColor(ColorStateList)
5922      * @see #setHintTextColor(ColorStateList)
5923      *
5924      * @attr ref android.R.styleable#TextView_textColorLink
5925      */
setLinkTextColor(ColorStateList colors)5926     public final void setLinkTextColor(ColorStateList colors) {
5927         mLinkTextColor = colors;
5928         updateTextColors();
5929     }
5930 
5931     /**
5932      * @return the list of colors used to paint the links in the text, for the different states of
5933      * this TextView
5934      *
5935      * @see #setLinkTextColor(ColorStateList)
5936      * @see #setLinkTextColor(int)
5937      *
5938      * @attr ref android.R.styleable#TextView_textColorLink
5939      */
5940     @InspectableProperty(name = "textColorLink")
getLinkTextColors()5941     public final ColorStateList getLinkTextColors() {
5942         return mLinkTextColor;
5943     }
5944 
5945     /**
5946      * Sets the horizontal alignment of the text and the
5947      * vertical gravity that will be used when there is extra space
5948      * in the TextView beyond what is required for the text itself.
5949      *
5950      * @see android.view.Gravity
5951      * @attr ref android.R.styleable#TextView_gravity
5952      */
5953     @android.view.RemotableViewMethod
setGravity(int gravity)5954     public void setGravity(int gravity) {
5955         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5956             gravity |= Gravity.START;
5957         }
5958         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5959             gravity |= Gravity.TOP;
5960         }
5961 
5962         boolean newLayout = false;
5963 
5964         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5965                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5966             newLayout = true;
5967         }
5968 
5969         if (gravity != mGravity) {
5970             invalidate();
5971         }
5972 
5973         mGravity = gravity;
5974 
5975         if (mLayout != null && newLayout) {
5976             // XXX this is heavy-handed because no actual content changes.
5977             int want = mLayout.getWidth();
5978             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5979 
5980             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5981                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5982         }
5983     }
5984 
5985     /**
5986      * Returns the horizontal and vertical alignment of this TextView.
5987      *
5988      * @see android.view.Gravity
5989      * @attr ref android.R.styleable#TextView_gravity
5990      */
5991     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()5992     public int getGravity() {
5993         return mGravity;
5994     }
5995 
5996     /**
5997      * Gets the flags on the Paint being used to display the text.
5998      * @return The flags on the Paint being used to display the text.
5999      * @see Paint#getFlags
6000      */
getPaintFlags()6001     public int getPaintFlags() {
6002         return mTextPaint.getFlags();
6003     }
6004 
6005     /**
6006      * Sets flags on the Paint being used to display the text and
6007      * reflows the text if they are different from the old flags.
6008      * @see Paint#setFlags
6009      */
6010     @android.view.RemotableViewMethod
setPaintFlags(int flags)6011     public void setPaintFlags(int flags) {
6012         if (mTextPaint.getFlags() != flags) {
6013             mTextPaint.setFlags(flags);
6014 
6015             if (mLayout != null) {
6016                 nullLayouts();
6017                 requestLayout();
6018                 invalidate();
6019             }
6020         }
6021     }
6022 
6023     /**
6024      * Sets whether the text should be allowed to be wider than the
6025      * View is.  If false, it will be wrapped to the width of the View.
6026      *
6027      * @attr ref android.R.styleable#TextView_scrollHorizontally
6028      */
setHorizontallyScrolling(boolean whether)6029     public void setHorizontallyScrolling(boolean whether) {
6030         if (mHorizontallyScrolling != whether) {
6031             mHorizontallyScrolling = whether;
6032 
6033             if (mLayout != null) {
6034                 nullLayouts();
6035                 requestLayout();
6036                 invalidate();
6037             }
6038         }
6039     }
6040 
6041     /**
6042      * Returns whether the text is allowed to be wider than the View.
6043      * If false, the text will be wrapped to the width of the View.
6044      *
6045      * @attr ref android.R.styleable#TextView_scrollHorizontally
6046      * @see #setHorizontallyScrolling(boolean)
6047      */
6048     @InspectableProperty(name = "scrollHorizontally")
isHorizontallyScrollable()6049     public final boolean isHorizontallyScrollable() {
6050         return mHorizontallyScrolling;
6051     }
6052 
6053     /**
6054      * Returns whether the text is allowed to be wider than the View.
6055      * If false, the text will be wrapped to the width of the View.
6056      *
6057      * @attr ref android.R.styleable#TextView_scrollHorizontally
6058      * @hide
6059      */
6060     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
getHorizontallyScrolling()6061     public boolean getHorizontallyScrolling() {
6062         return mHorizontallyScrolling;
6063     }
6064 
6065     /**
6066      * Sets the height of the TextView to be at least {@code minLines} tall.
6067      * <p>
6068      * This value is used for height calculation if LayoutParams does not force TextView to have an
6069      * exact height. Setting this value overrides other previous minimum height configurations such
6070      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
6071      * this value to 1.
6072      *
6073      * @param minLines the minimum height of TextView in terms of number of lines
6074      *
6075      * @see #getMinLines()
6076      * @see #setLines(int)
6077      *
6078      * @attr ref android.R.styleable#TextView_minLines
6079      */
6080     @android.view.RemotableViewMethod
setMinLines(int minLines)6081     public void setMinLines(int minLines) {
6082         mMinimum = minLines;
6083         mMinMode = LINES;
6084 
6085         requestLayout();
6086         invalidate();
6087     }
6088 
6089     /**
6090      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
6091      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
6092      *
6093      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
6094      *         height is not defined in lines
6095      *
6096      * @see #setMinLines(int)
6097      * @see #setLines(int)
6098      *
6099      * @attr ref android.R.styleable#TextView_minLines
6100      */
6101     @InspectableProperty
getMinLines()6102     public int getMinLines() {
6103         return mMinMode == LINES ? mMinimum : -1;
6104     }
6105 
6106     /**
6107      * Sets the height of the TextView to be at least {@code minPixels} tall.
6108      * <p>
6109      * This value is used for height calculation if LayoutParams does not force TextView to have an
6110      * exact height. Setting this value overrides previous minimum height configurations such as
6111      * {@link #setMinLines(int)} or {@link #setLines(int)}.
6112      * <p>
6113      * The value given here is different than {@link #setMinimumHeight(int)}. Between
6114      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
6115      * used to decide the final height.
6116      *
6117      * @param minPixels the minimum height of TextView in terms of pixels
6118      *
6119      * @see #getMinHeight()
6120      * @see #setHeight(int)
6121      *
6122      * @attr ref android.R.styleable#TextView_minHeight
6123      */
6124     @android.view.RemotableViewMethod
setMinHeight(int minPixels)6125     public void setMinHeight(int minPixels) {
6126         mMinimum = minPixels;
6127         mMinMode = PIXELS;
6128 
6129         requestLayout();
6130         invalidate();
6131     }
6132 
6133     /**
6134      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
6135      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
6136      *
6137      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
6138      *         defined in pixels
6139      *
6140      * @see #setMinHeight(int)
6141      * @see #setHeight(int)
6142      *
6143      * @attr ref android.R.styleable#TextView_minHeight
6144      */
getMinHeight()6145     public int getMinHeight() {
6146         return mMinMode == PIXELS ? mMinimum : -1;
6147     }
6148 
6149     /**
6150      * Sets the height of the TextView to be at most {@code maxLines} tall.
6151      * <p>
6152      * This value is used for height calculation if LayoutParams does not force TextView to have an
6153      * exact height. Setting this value overrides previous maximum height configurations such as
6154      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
6155      *
6156      * @param maxLines the maximum height of TextView in terms of number of lines
6157      *
6158      * @see #getMaxLines()
6159      * @see #setLines(int)
6160      *
6161      * @attr ref android.R.styleable#TextView_maxLines
6162      */
6163     @android.view.RemotableViewMethod
setMaxLines(int maxLines)6164     public void setMaxLines(int maxLines) {
6165         mMaximum = maxLines;
6166         mMaxMode = LINES;
6167 
6168         requestLayout();
6169         invalidate();
6170     }
6171 
6172     /**
6173      * Returns the maximum height of TextView in terms of number of lines or -1 if the
6174      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
6175      *
6176      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
6177      *         is not defined in lines.
6178      *
6179      * @see #setMaxLines(int)
6180      * @see #setLines(int)
6181      *
6182      * @attr ref android.R.styleable#TextView_maxLines
6183      */
6184     @InspectableProperty
getMaxLines()6185     public int getMaxLines() {
6186         return mMaxMode == LINES ? mMaximum : -1;
6187     }
6188 
6189     /**
6190      * Sets the height of the TextView to be at most {@code maxPixels} tall.
6191      * <p>
6192      * This value is used for height calculation if LayoutParams does not force TextView to have an
6193      * exact height. Setting this value overrides previous maximum height configurations such as
6194      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
6195      *
6196      * @param maxPixels the maximum height of TextView in terms of pixels
6197      *
6198      * @see #getMaxHeight()
6199      * @see #setHeight(int)
6200      *
6201      * @attr ref android.R.styleable#TextView_maxHeight
6202      */
6203     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)6204     public void setMaxHeight(int maxPixels) {
6205         mMaximum = maxPixels;
6206         mMaxMode = PIXELS;
6207 
6208         requestLayout();
6209         invalidate();
6210     }
6211 
6212     /**
6213      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
6214      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
6215      *
6216      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
6217      *         is not defined in pixels
6218      *
6219      * @see #setMaxHeight(int)
6220      * @see #setHeight(int)
6221      *
6222      * @attr ref android.R.styleable#TextView_maxHeight
6223      */
6224     @InspectableProperty
getMaxHeight()6225     public int getMaxHeight() {
6226         return mMaxMode == PIXELS ? mMaximum : -1;
6227     }
6228 
6229     /**
6230      * Sets the height of the TextView to be exactly {@code lines} tall.
6231      * <p>
6232      * This value is used for height calculation if LayoutParams does not force TextView to have an
6233      * exact height. Setting this value overrides previous minimum/maximum height configurations
6234      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
6235      * set this value to 1.
6236      *
6237      * @param lines the exact height of the TextView in terms of lines
6238      *
6239      * @see #setHeight(int)
6240      *
6241      * @attr ref android.R.styleable#TextView_lines
6242      */
6243     @android.view.RemotableViewMethod
setLines(int lines)6244     public void setLines(int lines) {
6245         mMaximum = mMinimum = lines;
6246         mMaxMode = mMinMode = LINES;
6247 
6248         requestLayout();
6249         invalidate();
6250     }
6251 
6252     /**
6253      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
6254      * <p>
6255      * This value is used for height calculation if LayoutParams does not force TextView to have an
6256      * exact height. Setting this value overrides previous minimum/maximum height configurations
6257      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
6258      *
6259      * @param pixels the exact height of the TextView in terms of pixels
6260      *
6261      * @see #setLines(int)
6262      *
6263      * @attr ref android.R.styleable#TextView_height
6264      */
6265     @android.view.RemotableViewMethod
setHeight(int pixels)6266     public void setHeight(int pixels) {
6267         mMaximum = mMinimum = pixels;
6268         mMaxMode = mMinMode = PIXELS;
6269 
6270         requestLayout();
6271         invalidate();
6272     }
6273 
6274     /**
6275      * Sets the width of the TextView to be at least {@code minEms} wide.
6276      * <p>
6277      * This value is used for width calculation if LayoutParams does not force TextView to have an
6278      * exact width. Setting this value overrides previous minimum width configurations such as
6279      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
6280      *
6281      * @param minEms the minimum width of TextView in terms of ems
6282      *
6283      * @see #getMinEms()
6284      * @see #setEms(int)
6285      *
6286      * @attr ref android.R.styleable#TextView_minEms
6287      */
6288     @android.view.RemotableViewMethod
setMinEms(int minEms)6289     public void setMinEms(int minEms) {
6290         mMinWidth = minEms;
6291         mMinWidthMode = EMS;
6292 
6293         requestLayout();
6294         invalidate();
6295     }
6296 
6297     /**
6298      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
6299      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
6300      *
6301      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
6302      *         defined in ems
6303      *
6304      * @see #setMinEms(int)
6305      * @see #setEms(int)
6306      *
6307      * @attr ref android.R.styleable#TextView_minEms
6308      */
6309     @InspectableProperty
getMinEms()6310     public int getMinEms() {
6311         return mMinWidthMode == EMS ? mMinWidth : -1;
6312     }
6313 
6314     /**
6315      * Sets the width of the TextView to be at least {@code minPixels} wide.
6316      * <p>
6317      * This value is used for width calculation if LayoutParams does not force TextView to have an
6318      * exact width. Setting this value overrides previous minimum width configurations such as
6319      * {@link #setMinEms(int)} or {@link #setEms(int)}.
6320      * <p>
6321      * The value given here is different than {@link #setMinimumWidth(int)}. Between
6322      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
6323      * to decide the final width.
6324      *
6325      * @param minPixels the minimum width of TextView in terms of pixels
6326      *
6327      * @see #getMinWidth()
6328      * @see #setWidth(int)
6329      *
6330      * @attr ref android.R.styleable#TextView_minWidth
6331      */
6332     @android.view.RemotableViewMethod
setMinWidth(int minPixels)6333     public void setMinWidth(int minPixels) {
6334         mMinWidth = minPixels;
6335         mMinWidthMode = PIXELS;
6336 
6337         requestLayout();
6338         invalidate();
6339     }
6340 
6341     /**
6342      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
6343      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
6344      *
6345      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
6346      *         defined in pixels
6347      *
6348      * @see #setMinWidth(int)
6349      * @see #setWidth(int)
6350      *
6351      * @attr ref android.R.styleable#TextView_minWidth
6352      */
6353     @InspectableProperty
getMinWidth()6354     public int getMinWidth() {
6355         return mMinWidthMode == PIXELS ? mMinWidth : -1;
6356     }
6357 
6358     /**
6359      * Sets the width of the TextView to be at most {@code maxEms} wide.
6360      * <p>
6361      * This value is used for width calculation if LayoutParams does not force TextView to have an
6362      * exact width. Setting this value overrides previous maximum width configurations such as
6363      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
6364      *
6365      * @param maxEms the maximum width of TextView in terms of ems
6366      *
6367      * @see #getMaxEms()
6368      * @see #setEms(int)
6369      *
6370      * @attr ref android.R.styleable#TextView_maxEms
6371      */
6372     @android.view.RemotableViewMethod
setMaxEms(int maxEms)6373     public void setMaxEms(int maxEms) {
6374         mMaxWidth = maxEms;
6375         mMaxWidthMode = EMS;
6376 
6377         requestLayout();
6378         invalidate();
6379     }
6380 
6381     /**
6382      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
6383      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
6384      *
6385      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
6386      *         defined in ems
6387      *
6388      * @see #setMaxEms(int)
6389      * @see #setEms(int)
6390      *
6391      * @attr ref android.R.styleable#TextView_maxEms
6392      */
6393     @InspectableProperty
getMaxEms()6394     public int getMaxEms() {
6395         return mMaxWidthMode == EMS ? mMaxWidth : -1;
6396     }
6397 
6398     /**
6399      * Sets the width of the TextView to be at most {@code maxPixels} wide.
6400      * <p>
6401      * This value is used for width calculation if LayoutParams does not force TextView to have an
6402      * exact width. Setting this value overrides previous maximum width configurations such as
6403      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
6404      *
6405      * @param maxPixels the maximum width of TextView in terms of pixels
6406      *
6407      * @see #getMaxWidth()
6408      * @see #setWidth(int)
6409      *
6410      * @attr ref android.R.styleable#TextView_maxWidth
6411      */
6412     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)6413     public void setMaxWidth(int maxPixels) {
6414         mMaxWidth = maxPixels;
6415         mMaxWidthMode = PIXELS;
6416 
6417         requestLayout();
6418         invalidate();
6419     }
6420 
6421     /**
6422      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
6423      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
6424      *
6425      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
6426      *         defined in pixels
6427      *
6428      * @see #setMaxWidth(int)
6429      * @see #setWidth(int)
6430      *
6431      * @attr ref android.R.styleable#TextView_maxWidth
6432      */
6433     @InspectableProperty
getMaxWidth()6434     public int getMaxWidth() {
6435         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
6436     }
6437 
6438     /**
6439      * Sets the width of the TextView to be exactly {@code ems} wide.
6440      *
6441      * This value is used for width calculation if LayoutParams does not force TextView to have an
6442      * exact width. Setting this value overrides previous minimum/maximum configurations such as
6443      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
6444      *
6445      * @param ems the exact width of the TextView in terms of ems
6446      *
6447      * @see #setWidth(int)
6448      *
6449      * @attr ref android.R.styleable#TextView_ems
6450      */
6451     @android.view.RemotableViewMethod
setEms(int ems)6452     public void setEms(int ems) {
6453         mMaxWidth = mMinWidth = ems;
6454         mMaxWidthMode = mMinWidthMode = EMS;
6455 
6456         requestLayout();
6457         invalidate();
6458     }
6459 
6460     /**
6461      * Sets the width of the TextView to be exactly {@code pixels} wide.
6462      * <p>
6463      * This value is used for width calculation if LayoutParams does not force TextView to have an
6464      * exact width. Setting this value overrides previous minimum/maximum width configurations
6465      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
6466      *
6467      * @param pixels the exact width of the TextView in terms of pixels
6468      *
6469      * @see #setEms(int)
6470      *
6471      * @attr ref android.R.styleable#TextView_width
6472      */
6473     @android.view.RemotableViewMethod
setWidth(int pixels)6474     public void setWidth(int pixels) {
6475         mMaxWidth = mMinWidth = pixels;
6476         mMaxWidthMode = mMinWidthMode = PIXELS;
6477 
6478         requestLayout();
6479         invalidate();
6480     }
6481 
6482     /**
6483      * Sets line spacing for this TextView.  Each line other than the last line will have its height
6484      * multiplied by {@code mult} and have {@code add} added to it.
6485      *
6486      * @param add The value in pixels that should be added to each line other than the last line.
6487      *            This will be applied after the multiplier
6488      * @param mult The value by which each line height other than the last line will be multiplied
6489      *             by
6490      *
6491      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6492      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6493      */
setLineSpacing(float add, float mult)6494     public void setLineSpacing(float add, float mult) {
6495         if (mSpacingAdd != add || mSpacingMult != mult) {
6496             mSpacingAdd = add;
6497             mSpacingMult = mult;
6498 
6499             if (mLayout != null) {
6500                 nullLayouts();
6501                 requestLayout();
6502                 invalidate();
6503             }
6504         }
6505     }
6506 
6507     /**
6508      * Gets the line spacing multiplier
6509      *
6510      * @return the value by which each line's height is multiplied to get its actual height.
6511      *
6512      * @see #setLineSpacing(float, float)
6513      * @see #getLineSpacingExtra()
6514      *
6515      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6516      */
6517     @InspectableProperty
getLineSpacingMultiplier()6518     public float getLineSpacingMultiplier() {
6519         return mSpacingMult;
6520     }
6521 
6522     /**
6523      * Gets the line spacing extra space
6524      *
6525      * @return the extra space that is added to the height of each lines of this TextView.
6526      *
6527      * @see #setLineSpacing(float, float)
6528      * @see #getLineSpacingMultiplier()
6529      *
6530      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6531      */
6532     @InspectableProperty
getLineSpacingExtra()6533     public float getLineSpacingExtra() {
6534         return mSpacingAdd;
6535     }
6536 
6537     /**
6538      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
6539      * between subsequent baselines in the TextView.
6540      *
6541      * @param lineHeight the line height in pixels
6542      *
6543      * @see #setLineSpacing(float, float)
6544      * @see #getLineSpacingExtra()
6545      *
6546      * @attr ref android.R.styleable#TextView_lineHeight
6547      */
6548     @android.view.RemotableViewMethod
setLineHeight(@x @ntRangefrom = 0) int lineHeight)6549     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
6550         setLineHeightPx(lineHeight);
6551     }
6552 
setLineHeightPx(@x @loatRangefrom = 0) float lineHeight)6553     private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) {
6554         Preconditions.checkArgumentNonNegative(lineHeight,
6555                 "Expecting non-negative lineHeight while the input is " + lineHeight);
6556 
6557         final int fontHeight = getPaint().getFontMetricsInt(null);
6558         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
6559         // TODO(b/274974975): should this also check if lineSpacing needs to change?
6560         if (lineHeight != fontHeight) {
6561             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
6562             setLineSpacing(lineHeight - fontHeight, 1f);
6563 
6564             mLineHeightComplexDimen =
6565                         TypedValue.createComplexDimension(lineHeight, TypedValue.COMPLEX_UNIT_PX);
6566         }
6567     }
6568 
6569     /**
6570      * Sets an explicit line height to a given unit and value for this TextView. This is equivalent
6571      * to the vertical distance between subsequent baselines in the TextView. See {@link
6572      * TypedValue} for the possible dimension units.
6573      *
6574      * @param unit The desired dimension unit. SP units are strongly recommended so that line height
6575      *             stays proportional to the text size when fonts are scaled up for accessibility.
6576      * @param lineHeight The desired line height in the given units.
6577      *
6578      * @see #setLineSpacing(float, float)
6579      * @see #getLineSpacingExtra()
6580      *
6581      * @attr ref android.R.styleable#TextView_lineHeight
6582      */
6583     @android.view.RemotableViewMethod
setLineHeight( @ypedValue.ComplexDimensionUnit int unit, @FloatRange(from = 0) float lineHeight )6584     public void setLineHeight(
6585             @TypedValue.ComplexDimensionUnit int unit,
6586             @FloatRange(from = 0) float lineHeight
6587     ) {
6588         var metrics = getDisplayMetricsOrSystem();
6589         // We can avoid the recalculation if we know non-linear font scaling isn't being used
6590         // (an optimization for the majority case).
6591         // We also don't try to do the recalculation unless both textSize and lineHeight are in SP.
6592         if (!FontScaleConverterFactory.isNonLinearFontScalingActive(
6593                     getResources().getConfiguration().fontScale)
6594                 || unit != TypedValue.COMPLEX_UNIT_SP
6595                 || mTextSizeUnit != TypedValue.COMPLEX_UNIT_SP
6596         ) {
6597             setLineHeightPx(TypedValue.applyDimension(unit, lineHeight, metrics));
6598 
6599             // Do this last so it overwrites what setLineHeightPx() sets it to.
6600             mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
6601             return;
6602         }
6603 
6604         // Recalculate a proportional line height when non-linear font scaling is in effect.
6605         // Otherwise, a desired 2x line height at font scale 1.0 will not be 2x at font scale 2.0,
6606         // due to non-linear font scaling compressing higher SP sizes. See b/273326061 for details.
6607         // We know they are using SP units for both the text size and the line height
6608         // at this point, so determine the ratio between them. This is the *intended* line spacing
6609         // multiplier if font scale == 1.0. We can then determine what the pixel value for the line
6610         // height would be if we preserved proportions.
6611         var textSizePx = getTextSize();
6612         var textSizeSp = TypedValue.convertPixelsToDimension(
6613                 TypedValue.COMPLEX_UNIT_SP,
6614                 textSizePx,
6615                 metrics
6616         );
6617         var ratio = lineHeight / textSizeSp;
6618         setLineHeightPx(textSizePx * ratio);
6619 
6620         // Do this last so it overwrites what setLineHeightPx() sets it to.
6621         mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
6622     }
6623 
maybeRecalculateLineHeight()6624     private void maybeRecalculateLineHeight() {
6625         if (mLineHeightComplexDimen == 0) {
6626             return;
6627         }
6628         int unit = TypedValue.getUnitFromComplexDimension(mLineHeightComplexDimen);
6629         if (unit != TypedValue.COMPLEX_UNIT_SP) {
6630             // The lineHeight was never supplied in SP, so we didn't do any fancy recalculations
6631             // in setLineHeight(). We don't need to recalculate.
6632             return;
6633         }
6634 
6635         setLineHeight(unit, TypedValue.complexToFloat(mLineHeightComplexDimen));
6636     }
6637 
6638     /**
6639      * Set Highlights
6640      *
6641      * @param highlights A highlight object. Call with null for reset.
6642      *
6643      * @see #getHighlights()
6644      * @see Highlights
6645      */
setHighlights(@ullable Highlights highlights)6646     public void setHighlights(@Nullable Highlights highlights) {
6647         mHighlights = highlights;
6648         mHighlightPathsBogus = true;
6649         invalidate();
6650     }
6651 
6652     /**
6653      * Returns highlights
6654      *
6655      * @return a highlight to be drawn. null if no highlight was set.
6656      *
6657      * @see #setHighlights(Highlights)
6658      * @see Highlights
6659      *
6660      */
6661     @Nullable
getHighlights()6662     public Highlights getHighlights() {
6663         return mHighlights;
6664     }
6665 
6666     /**
6667      * Sets the search result ranges with flatten range representation.
6668      *
6669      * Ranges are represented of flattened inclusive start and exclusive end integers array. The
6670      * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array.
6671      * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the
6672      * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array
6673      * [1, 2, 3, 4].
6674      *
6675      * TextView will render the search result with the highlights with specified color in the theme.
6676      * If there is a focused search result, it is rendered with focused color. By calling this
6677      * method, the focused search index will be cleared.
6678      *
6679      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6680      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6681      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6682      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6683      *
6684      * @see #getSearchResultHighlights()
6685      * @see #setFocusedSearchResultIndex(int)
6686      * @see #getFocusedSearchResultIndex()
6687      * @see #setSearchResultHighlightColor(int)
6688      * @see #getSearchResultHighlightColor()
6689      * @see #setFocusedSearchResultHighlightColor(int)
6690      * @see #getFocusedSearchResultHighlightColor()
6691      *
6692      * @param ranges the flatten ranges of the search result. null for clear.
6693      */
setSearchResultHighlights(@ullable int... ranges)6694     public void setSearchResultHighlights(@Nullable int... ranges) {
6695         if (ranges == null) {
6696             mSearchResultHighlights = null;
6697             mHighlightPathsBogus = true;
6698             return;
6699         }
6700         if (ranges.length % 2 == 1) {
6701             throw new IllegalArgumentException(
6702                     "Flatten ranges must have even numbered elements");
6703         }
6704         for (int j = 0; j < ranges.length / 2; ++j) {
6705             int start = ranges[j * 2];
6706             int end = ranges[j * 2 + 1];
6707             if (start > end) {
6708                 throw new IllegalArgumentException(
6709                         "Reverse range found in the flatten range: " + start + ", " + end + ""
6710                                 + " at " + j + "-th range");
6711             }
6712         }
6713         mHighlightPathsBogus = true;
6714         mSearchResultHighlights = ranges;
6715         mFocusedSearchResultIndex = FOCUSED_SEARCH_RESULT_INDEX_NONE;
6716         invalidate();
6717     }
6718 
6719     /**
6720      * Gets the current search result ranges.
6721      *
6722      * @see #setSearchResultHighlights(int[])
6723      * @see #setFocusedSearchResultIndex(int)
6724      * @see #getFocusedSearchResultIndex()
6725      * @see #setSearchResultHighlightColor(int)
6726      * @see #getSearchResultHighlightColor()
6727      * @see #setFocusedSearchResultHighlightColor(int)
6728      * @see #getFocusedSearchResultHighlightColor()
6729      *
6730      * @return a flatten search result ranges. null if not available.
6731      */
6732     @Nullable
getSearchResultHighlights()6733     public int[] getSearchResultHighlights() {
6734         return mSearchResultHighlights;
6735     }
6736 
6737     /**
6738      * A special index used for {@link #setFocusedSearchResultIndex(int)} and
6739      * {@link #getFocusedSearchResultIndex()} inidicating there is no focused search result.
6740      */
6741     public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1;
6742 
6743     /**
6744      * Sets the focused search result index.
6745      *
6746      * The focused search result is drawn in a focused color.
6747      * Calling {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} for clearing focused search result.
6748      *
6749      * This method must be called after setting search result ranges by
6750      * {@link #setSearchResultHighlights(int[])}.
6751      *
6752      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6753      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6754      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6755      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6756      *
6757      * @see #setSearchResultHighlights(int[])
6758      * @see #getSearchResultHighlights()
6759      * @see #setFocusedSearchResultIndex(int)
6760      * @see #getFocusedSearchResultIndex()
6761      * @see #setSearchResultHighlightColor(int)
6762      * @see #getSearchResultHighlightColor()
6763      * @see #setFocusedSearchResultHighlightColor(int)
6764      * @see #getFocusedSearchResultHighlightColor()
6765      *
6766      * @param index a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
6767      */
setFocusedSearchResultIndex(int index)6768     public void setFocusedSearchResultIndex(int index) {
6769         if (mSearchResultHighlights == null) {
6770             throw new IllegalArgumentException("Search result range must be set beforehand.");
6771         }
6772         if (index < -1 || index >= mSearchResultHighlights.length / 2) {
6773             throw new IllegalArgumentException("Focused index(" + index + ") must be larger than "
6774                     + "-1 and less than range count(" + (mSearchResultHighlights.length / 2) + ")");
6775         }
6776         mFocusedSearchResultIndex = index;
6777         mHighlightPathsBogus = true;
6778         invalidate();
6779     }
6780 
6781     /**
6782      * Gets the focused search result index.
6783      *
6784      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6785      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6786      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6787      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6788      *
6789      * @see #setSearchResultHighlights(int[])
6790      * @see #getSearchResultHighlights()
6791      * @see #setFocusedSearchResultIndex(int)
6792      * @see #getFocusedSearchResultIndex()
6793      * @see #setSearchResultHighlightColor(int)
6794      * @see #getSearchResultHighlightColor()
6795      * @see #setFocusedSearchResultHighlightColor(int)
6796      * @see #getFocusedSearchResultHighlightColor()
6797 
6798      * @return a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
6799      */
getFocusedSearchResultIndex()6800     public int getFocusedSearchResultIndex() {
6801         return mFocusedSearchResultIndex;
6802     }
6803 
6804     /**
6805      * Sets the search result highlight color.
6806      *
6807      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6808      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6809      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6810      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6811      *
6812      * @see #setSearchResultHighlights(int[])
6813      * @see #getSearchResultHighlights()
6814      * @see #setFocusedSearchResultIndex(int)
6815      * @see #getFocusedSearchResultIndex()
6816      * @see #setSearchResultHighlightColor(int)
6817      * @see #getSearchResultHighlightColor()
6818      * @see #setFocusedSearchResultHighlightColor(int)
6819      * @see #getFocusedSearchResultHighlightColor()
6820 
6821      * @param color a search result highlight color.
6822      */
setSearchResultHighlightColor(@olorInt int color)6823     public void setSearchResultHighlightColor(@ColorInt int color) {
6824         mSearchResultHighlightColor = color;
6825     }
6826 
6827     /**
6828      * Gets the search result highlight color.
6829      *
6830      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6831      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6832      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6833      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6834      *
6835      * @see #setSearchResultHighlights(int[])
6836      * @see #getSearchResultHighlights()
6837      * @see #setFocusedSearchResultIndex(int)
6838      * @see #getFocusedSearchResultIndex()
6839      * @see #setSearchResultHighlightColor(int)
6840      * @see #getSearchResultHighlightColor()
6841      * @see #setFocusedSearchResultHighlightColor(int)
6842      * @see #getFocusedSearchResultHighlightColor()
6843 
6844      * @return a search result highlight color.
6845      */
6846     @ColorInt
getSearchResultHighlightColor()6847     public int getSearchResultHighlightColor() {
6848         return mSearchResultHighlightColor;
6849     }
6850 
6851     /**
6852      * Sets focused search result highlight color.
6853      *
6854      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6855      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6856      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6857      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6858      *
6859      * @see #setSearchResultHighlights(int[])
6860      * @see #getSearchResultHighlights()
6861      * @see #setFocusedSearchResultIndex(int)
6862      * @see #getFocusedSearchResultIndex()
6863      * @see #setSearchResultHighlightColor(int)
6864      * @see #getSearchResultHighlightColor()
6865      * @see #setFocusedSearchResultHighlightColor(int)
6866      * @see #getFocusedSearchResultHighlightColor()
6867 
6868      * @param color a focused search result highlight color.
6869      */
setFocusedSearchResultHighlightColor(@olorInt int color)6870     public void setFocusedSearchResultHighlightColor(@ColorInt int color) {
6871         mFocusedSearchResultHighlightColor = color;
6872     }
6873 
6874     /**
6875      * Gets focused search result highlight color.
6876      *
6877      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6878      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6879      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6880      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6881      *
6882      * @see #setSearchResultHighlights(int[])
6883      * @see #getSearchResultHighlights()
6884      * @see #setFocusedSearchResultIndex(int)
6885      * @see #getFocusedSearchResultIndex()
6886      * @see #setSearchResultHighlightColor(int)
6887      * @see #getSearchResultHighlightColor()
6888      * @see #setFocusedSearchResultHighlightColor(int)
6889      * @see #getFocusedSearchResultHighlightColor()
6890 
6891      * @return a focused search result highlight color.
6892      */
6893     @ColorInt
getFocusedSearchResultHighlightColor()6894     public int getFocusedSearchResultHighlightColor() {
6895         return mFocusedSearchResultHighlightColor;
6896     }
6897 
6898     /**
6899      * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
6900      * will be selected by the ongoing select handwriting gesture. While the gesture preview
6901      * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
6902      * the gesture preview highlight will be cleared.
6903      */
setSelectGesturePreviewHighlight(int start, int end)6904     private void setSelectGesturePreviewHighlight(int start, int end) {
6905         // Selection preview highlight color is the same as selection highlight color.
6906         setGesturePreviewHighlight(start, end, mHighlightColor);
6907     }
6908 
6909     /**
6910      * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
6911      * will be deleted by the ongoing delete handwriting gesture. While the gesture preview
6912      * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
6913      * the gesture preview highlight will be cleared.
6914      */
setDeleteGesturePreviewHighlight(int start, int end)6915     private void setDeleteGesturePreviewHighlight(int start, int end) {
6916         // Deletion preview highlight color is 20% opacity of the default text color.
6917         int color = mTextColor.getDefaultColor();
6918         color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
6919         setGesturePreviewHighlight(start, end, color);
6920     }
6921 
setGesturePreviewHighlight(int start, int end, int color)6922     private void setGesturePreviewHighlight(int start, int end, int color) {
6923         mGesturePreviewHighlightStart = start;
6924         mGesturePreviewHighlightEnd = end;
6925         if (mGesturePreviewHighlightPaint == null) {
6926             mGesturePreviewHighlightPaint = new Paint();
6927             mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL);
6928         }
6929         mGesturePreviewHighlightPaint.setColor(color);
6930 
6931         if (mEditor != null) {
6932             mEditor.hideCursorAndSpanControllers();
6933             mEditor.stopTextActionModeWithPreservingSelection();
6934         }
6935 
6936         mHighlightPathsBogus = true;
6937         invalidate();
6938     }
6939 
clearGesturePreviewHighlight()6940     private void clearGesturePreviewHighlight() {
6941         mGesturePreviewHighlightStart = -1;
6942         mGesturePreviewHighlightEnd = -1;
6943         mHighlightPathsBogus = true;
6944         invalidate();
6945     }
6946 
hasGesturePreviewHighlight()6947     boolean hasGesturePreviewHighlight() {
6948         return mGesturePreviewHighlightStart >= 0;
6949     }
6950 
6951     /**
6952      * Convenience method to append the specified text to the TextView's
6953      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6954      * if it was not already editable.
6955      *
6956      * @param text text to be appended to the already displayed text
6957      */
append(CharSequence text)6958     public final void append(CharSequence text) {
6959         append(text, 0, text.length());
6960     }
6961 
6962     /**
6963      * Convenience method to append the specified text slice to the TextView's
6964      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6965      * if it was not already editable.
6966      *
6967      * @param text text to be appended to the already displayed text
6968      * @param start the index of the first character in the {@code text}
6969      * @param end the index of the character following the last character in the {@code text}
6970      *
6971      * @see Appendable#append(CharSequence, int, int)
6972      */
append(CharSequence text, int start, int end)6973     public void append(CharSequence text, int start, int end) {
6974         if (!(mText instanceof Editable)) {
6975             setText(mText, BufferType.EDITABLE);
6976         }
6977 
6978         ((Editable) mText).append(text, start, end);
6979 
6980         if (mAutoLinkMask != 0) {
6981             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
6982             // Do not change the movement method for text that support text selection as it
6983             // would prevent an arbitrary cursor displacement.
6984             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
6985                 setMovementMethod(LinkMovementMethod.getInstance());
6986             }
6987         }
6988     }
6989 
updateTextColors()6990     private void updateTextColors() {
6991         boolean inval = false;
6992         final int[] drawableState = getDrawableState();
6993         int color = mTextColor.getColorForState(drawableState, 0);
6994         if (color != mCurTextColor) {
6995             mCurTextColor = color;
6996             inval = true;
6997         }
6998         if (mLinkTextColor != null) {
6999             color = mLinkTextColor.getColorForState(drawableState, 0);
7000             if (color != mTextPaint.linkColor) {
7001                 mTextPaint.linkColor = color;
7002                 inval = true;
7003             }
7004         }
7005         if (mHintTextColor != null) {
7006             color = mHintTextColor.getColorForState(drawableState, 0);
7007             if (color != mCurHintTextColor) {
7008                 mCurHintTextColor = color;
7009                 if (mText.length() == 0) {
7010                     inval = true;
7011                 }
7012             }
7013         }
7014         if (inval) {
7015             // Text needs to be redrawn with the new color
7016             if (mEditor != null) mEditor.invalidateTextDisplayList();
7017             invalidate();
7018         }
7019     }
7020 
7021     @Override
drawableStateChanged()7022     protected void drawableStateChanged() {
7023         super.drawableStateChanged();
7024 
7025         if (mTextColor != null && mTextColor.isStateful()
7026                 || (mHintTextColor != null && mHintTextColor.isStateful())
7027                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
7028             updateTextColors();
7029         }
7030 
7031         if (mDrawables != null) {
7032             final int[] state = getDrawableState();
7033             for (Drawable dr : mDrawables.mShowing) {
7034                 if (dr != null && dr.isStateful() && dr.setState(state)) {
7035                     invalidateDrawable(dr);
7036                 }
7037             }
7038         }
7039     }
7040 
7041     @Override
drawableHotspotChanged(float x, float y)7042     public void drawableHotspotChanged(float x, float y) {
7043         super.drawableHotspotChanged(x, y);
7044 
7045         if (mDrawables != null) {
7046             for (Drawable dr : mDrawables.mShowing) {
7047                 if (dr != null) {
7048                     dr.setHotspot(x, y);
7049                 }
7050             }
7051         }
7052     }
7053 
7054     @Override
onSaveInstanceState()7055     public Parcelable onSaveInstanceState() {
7056         Parcelable superState = super.onSaveInstanceState();
7057 
7058         // Save state if we are forced to
7059         final boolean freezesText = getFreezesText();
7060         boolean hasSelection = false;
7061         int start = -1;
7062         int end = -1;
7063 
7064         if (mText != null) {
7065             start = getSelectionStart();
7066             end = getSelectionEnd();
7067             if (start >= 0 || end >= 0) {
7068                 // Or save state if there is a selection
7069                 hasSelection = true;
7070             }
7071         }
7072 
7073         if (freezesText || hasSelection) {
7074             SavedState ss = new SavedState(superState);
7075 
7076             if (freezesText) {
7077                 if (mText instanceof Spanned) {
7078                     final Spannable sp = new SpannableStringBuilder(mText);
7079 
7080                     if (mEditor != null) {
7081                         removeMisspelledSpans(sp);
7082                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
7083                     }
7084 
7085                     ss.text = sp;
7086                 } else {
7087                     ss.text = mText.toString();
7088                 }
7089             }
7090 
7091             if (hasSelection) {
7092                 // XXX Should also save the current scroll position!
7093                 ss.selStart = start;
7094                 ss.selEnd = end;
7095             }
7096 
7097             if (isFocused() && start >= 0 && end >= 0) {
7098                 ss.frozenWithFocus = true;
7099             }
7100 
7101             ss.error = getError();
7102 
7103             if (mEditor != null) {
7104                 ss.editorState = mEditor.saveInstanceState();
7105             }
7106             return ss;
7107         }
7108 
7109         return superState;
7110     }
7111 
removeMisspelledSpans(Spannable spannable)7112     void removeMisspelledSpans(Spannable spannable) {
7113         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
7114                 SuggestionSpan.class);
7115         for (int i = 0; i < suggestionSpans.length; i++) {
7116             int flags = suggestionSpans[i].getFlags();
7117             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
7118                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
7119                 spannable.removeSpan(suggestionSpans[i]);
7120             }
7121         }
7122     }
7123 
7124     @Override
onRestoreInstanceState(Parcelable state)7125     public void onRestoreInstanceState(Parcelable state) {
7126         if (!(state instanceof SavedState)) {
7127             super.onRestoreInstanceState(state);
7128             return;
7129         }
7130 
7131         SavedState ss = (SavedState) state;
7132         super.onRestoreInstanceState(ss.getSuperState());
7133 
7134         // XXX restore buffer type too, as well as lots of other stuff
7135         if (ss.text != null) {
7136             setText(ss.text);
7137         }
7138 
7139         if (ss.selStart >= 0 && ss.selEnd >= 0) {
7140             if (mSpannable != null) {
7141                 int len = mText.length();
7142 
7143                 if (ss.selStart > len || ss.selEnd > len) {
7144                     String restored = "";
7145 
7146                     if (ss.text != null) {
7147                         restored = "(restored) ";
7148                     }
7149 
7150                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
7151                             + " out of range for " + restored + "text " + mText);
7152                 } else {
7153                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
7154 
7155                     if (ss.frozenWithFocus) {
7156                         createEditorIfNeeded();
7157                         mEditor.mFrozenWithFocus = true;
7158                     }
7159                 }
7160             }
7161         }
7162 
7163         if (ss.error != null) {
7164             final CharSequence error = ss.error;
7165             // Display the error later, after the first layout pass
7166             post(new Runnable() {
7167                 public void run() {
7168                     if (mEditor == null || !mEditor.mErrorWasChanged) {
7169                         setError(error);
7170                     }
7171                 }
7172             });
7173         }
7174 
7175         if (ss.editorState != null) {
7176             createEditorIfNeeded();
7177             mEditor.restoreInstanceState(ss.editorState);
7178         }
7179     }
7180 
7181     /**
7182      * Control whether this text view saves its entire text contents when
7183      * freezing to an icicle, in addition to dynamic state such as cursor
7184      * position.  By default this is false, not saving the text.  Set to true
7185      * if the text in the text view is not being saved somewhere else in
7186      * persistent storage (such as in a content provider) so that if the
7187      * view is later thawed the user will not lose their data. For
7188      * {@link android.widget.EditText} it is always enabled, regardless of
7189      * the value of the attribute.
7190      *
7191      * @param freezesText Controls whether a frozen icicle should include the
7192      * entire text data: true to include it, false to not.
7193      *
7194      * @attr ref android.R.styleable#TextView_freezesText
7195      */
7196     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)7197     public void setFreezesText(boolean freezesText) {
7198         mFreezesText = freezesText;
7199     }
7200 
7201     /**
7202      * Return whether this text view is including its entire text contents
7203      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
7204      *
7205      * @return Returns true if text is included, false if it isn't.
7206      *
7207      * @see #setFreezesText
7208      */
7209     @InspectableProperty
getFreezesText()7210     public boolean getFreezesText() {
7211         return mFreezesText;
7212     }
7213 
7214     ///////////////////////////////////////////////////////////////////////////
7215 
7216     /**
7217      * Sets the Factory used to create new {@link Editable Editables}.
7218      *
7219      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
7220      *
7221      * @see android.text.Editable.Factory
7222      * @see android.widget.TextView.BufferType#EDITABLE
7223      */
setEditableFactory(Editable.Factory factory)7224     public final void setEditableFactory(Editable.Factory factory) {
7225         mEditableFactory = factory;
7226         setText(mText);
7227     }
7228 
7229     /**
7230      * Sets the Factory used to create new {@link Spannable Spannables}.
7231      *
7232      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
7233      *
7234      * @see android.text.Spannable.Factory
7235      * @see android.widget.TextView.BufferType#SPANNABLE
7236      */
setSpannableFactory(Spannable.Factory factory)7237     public final void setSpannableFactory(Spannable.Factory factory) {
7238         mSpannableFactory = factory;
7239         setText(mText);
7240     }
7241 
7242     /**
7243      * Sets the text to be displayed. TextView <em>does not</em> accept
7244      * HTML-like formatting, which you can do with text strings in XML resource files.
7245      * To style your strings, attach android.text.style.* objects to a
7246      * {@link android.text.SpannableString}, or see the
7247      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
7248      * Available Resource Types</a> documentation for an example of setting
7249      * formatted text in the XML resource file.
7250      * <p/>
7251      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7252      * intermediate {@link Spannable Spannables}. Likewise it will use
7253      * {@link android.text.Editable.Factory} to create final or intermediate
7254      * {@link Editable Editables}.
7255      *
7256      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
7257      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
7258      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
7259      *
7260      * @param text text to be displayed
7261      *
7262      * @attr ref android.R.styleable#TextView_text
7263      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
7264      *                                  parameters used to create the PrecomputedText mismatches
7265      *                                  with this TextView.
7266      */
7267     @android.view.RemotableViewMethod(asyncImpl = "setTextAsync")
setText(CharSequence text)7268     public final void setText(CharSequence text) {
7269         setText(text, mBufferType);
7270     }
7271 
7272     /**
7273      * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}.
7274      * This should be called on a background thread, and returns a Runnable which is then must be
7275      * called on the main thread to complete the operation and set text.
7276      * @param text text to be displayed
7277      * @return Runnable that sets text; must be called on the main thread by the caller of this
7278      * method to complete the operation
7279      * @hide
7280      */
7281     @NonNull
setTextAsync(@ullable CharSequence text)7282     public Runnable setTextAsync(@Nullable CharSequence text) {
7283         return () -> setText(text);
7284     }
7285 
7286     /**
7287      * Sets the text to be displayed but retains the cursor position. Same as
7288      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
7289      * new text.
7290      * <p/>
7291      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7292      * intermediate {@link Spannable Spannables}. Likewise it will use
7293      * {@link android.text.Editable.Factory} to create final or intermediate
7294      * {@link Editable Editables}.
7295      *
7296      * @param text text to be displayed
7297      *
7298      * @see #setText(CharSequence)
7299      */
7300     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)7301     public final void setTextKeepState(CharSequence text) {
7302         setTextKeepState(text, mBufferType);
7303     }
7304 
7305     /**
7306      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
7307      * <p/>
7308      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7309      * intermediate {@link Spannable Spannables}. Likewise it will use
7310      * {@link android.text.Editable.Factory} to create final or intermediate
7311      * {@link Editable Editables}.
7312      *
7313      * Subclasses overriding this method should ensure that the following post condition holds,
7314      * in order to guarantee the safety of the view's measurement and layout operations:
7315      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
7316      * will be different from {@code null}.
7317      *
7318      * @param text text to be displayed
7319      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7320      *              stored as a static text, styleable/spannable text, or editable text
7321      *
7322      * @see #setText(CharSequence)
7323      * @see android.widget.TextView.BufferType
7324      * @see #setSpannableFactory(Spannable.Factory)
7325      * @see #setEditableFactory(Editable.Factory)
7326      *
7327      * @attr ref android.R.styleable#TextView_text
7328      * @attr ref android.R.styleable#TextView_bufferType
7329      */
setText(CharSequence text, BufferType type)7330     public void setText(CharSequence text, BufferType type) {
7331         setText(text, type, true, 0);
7332 
7333         // drop any potential mCharWrappper leaks
7334         mCharWrapper = null;
7335     }
7336 
7337     @UnsupportedAppUsage
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)7338     private void setText(CharSequence text, BufferType type,
7339                          boolean notifyBefore, int oldlen) {
7340         if (mEditor != null) {
7341             mEditor.beforeSetText();
7342         }
7343         mTextSetFromXmlOrResourceId = false;
7344         if (text == null) {
7345             text = "";
7346         }
7347 
7348         // If suggestions are not enabled, remove the suggestion spans from the text
7349         if (!isSuggestionsEnabled()) {
7350             text = removeSuggestionSpans(text);
7351         }
7352 
7353         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
7354 
7355         if (text instanceof Spanned
7356                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
7357             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
7358                 setHorizontalFadingEdgeEnabled(true);
7359                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
7360             } else {
7361                 setHorizontalFadingEdgeEnabled(false);
7362                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7363             }
7364             setEllipsize(TextUtils.TruncateAt.MARQUEE);
7365         }
7366 
7367         int n = mFilters.length;
7368         for (int i = 0; i < n; i++) {
7369             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
7370             if (out != null) {
7371                 text = out;
7372             }
7373         }
7374 
7375         if (notifyBefore) {
7376             if (mText != null) {
7377                 oldlen = mText.length();
7378                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
7379             } else {
7380                 sendBeforeTextChanged("", 0, 0, text.length());
7381             }
7382         }
7383 
7384         boolean needEditableForNotification = false;
7385 
7386         if (mListeners != null && mListeners.size() != 0) {
7387             needEditableForNotification = true;
7388         }
7389 
7390         PrecomputedText precomputed =
7391                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
7392         if (type == BufferType.EDITABLE || getKeyListener() != null
7393                 || needEditableForNotification) {
7394             createEditorIfNeeded();
7395             mEditor.forgetUndoRedo();
7396             mEditor.scheduleRestartInputForSetText();
7397             Editable t = mEditableFactory.newEditable(text);
7398             text = t;
7399             setFilters(t, mFilters);
7400         } else if (precomputed != null) {
7401             if (mTextDir == null) {
7402                 mTextDir = getTextDirectionHeuristic();
7403             }
7404             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
7405                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
7406                             mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
7407                                     mLineBreakStyle, mLineBreakWordStyle));
7408             switch (checkResult) {
7409                 case PrecomputedText.Params.UNUSABLE:
7410                     throw new IllegalArgumentException(
7411                         "PrecomputedText's Parameters don't match the parameters of this TextView."
7412                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
7413                         + "to override the settings of this TextView: "
7414                         + "PrecomputedText: " + precomputed.getParams()
7415                         + "TextView: " + getTextMetricsParams());
7416                 case PrecomputedText.Params.NEED_RECOMPUTE:
7417                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
7418                     break;
7419                 case PrecomputedText.Params.USABLE:
7420                     // pass through
7421             }
7422         } else if (type == BufferType.SPANNABLE || mMovement != null) {
7423             text = mSpannableFactory.newSpannable(text);
7424         } else if (!(text instanceof CharWrapper)) {
7425             text = TextUtils.stringOrSpannedString(text);
7426         }
7427 
7428         @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE;
7429         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
7430             a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText);
7431         }
7432 
7433         if (mAutoLinkMask != 0) {
7434             Spannable s2;
7435 
7436             if (type == BufferType.EDITABLE || text instanceof Spannable) {
7437                 s2 = (Spannable) text;
7438             } else {
7439                 s2 = mSpannableFactory.newSpannable(text);
7440             }
7441 
7442             if (Linkify.addLinks(s2, mAutoLinkMask)) {
7443                 text = s2;
7444                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
7445 
7446                 /*
7447                  * We must go ahead and set the text before changing the
7448                  * movement method, because setMovementMethod() may call
7449                  * setText() again to try to upgrade the buffer type.
7450                  */
7451                 setTextInternal(text);
7452                 if (a11yTextChangeType == AccessibilityUtils.NONE) {
7453                     a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN;
7454                 }
7455 
7456                 // Do not change the movement method for text that support text selection as it
7457                 // would prevent an arbitrary cursor displacement.
7458                 if (mLinksClickable && !textCanBeSelected()) {
7459                     setMovementMethod(LinkMovementMethod.getInstance());
7460                 }
7461             }
7462         }
7463 
7464         mBufferType = type;
7465         setTextInternal(text);
7466 
7467         if (mTransformation == null) {
7468             mTransformed = text;
7469         } else {
7470             mTransformed = mTransformation.getTransformation(text, this);
7471         }
7472         if (mTransformed == null) {
7473             // Should not happen if the transformation method follows the non-null postcondition.
7474             mTransformed = "";
7475         }
7476 
7477         final int textLength = text.length();
7478         final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
7479 
7480         if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) {
7481             Spannable sp = (Spannable) text;
7482 
7483             // Remove any ChangeWatchers that might have come from other TextViews.
7484             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
7485             final int count = watchers.length;
7486             for (int i = 0; i < count; i++) {
7487                 sp.removeSpan(watchers[i]);
7488             }
7489 
7490             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
7491 
7492             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
7493                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
7494 
7495             if (mEditor != null) mEditor.addSpanWatchers(sp);
7496 
7497             if (mTransformation != null) {
7498                 final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
7499                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
7500                         | (priority << Spanned.SPAN_PRIORITY_SHIFT));
7501             }
7502 
7503             if (mMovement != null) {
7504                 mMovement.initialize(this, (Spannable) text);
7505 
7506                 /*
7507                  * Initializing the movement method will have set the
7508                  * selection, so reset mSelectionMoved to keep that from
7509                  * interfering with the normal on-focus selection-setting.
7510                  */
7511                 if (mEditor != null) mEditor.mSelectionMoved = false;
7512             }
7513         }
7514 
7515         if (mLayout != null) {
7516             checkForRelayout();
7517         }
7518 
7519         sendOnTextChanged(text, 0, oldlen, textLength);
7520         onTextChanged(text, 0, oldlen, textLength);
7521 
7522         mHideHint = false;
7523 
7524         if (a11yTextChangeType == AccessibilityUtils.TEXT) {
7525             notifyViewAccessibilityStateChangedIfNeeded(
7526                     AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
7527         } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) {
7528             notifyViewAccessibilityStateChangedIfNeeded(
7529                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7530         }
7531 
7532         if (needEditableForNotification) {
7533             sendAfterTextChanged((Editable) text);
7534         } else {
7535             notifyListeningManagersAfterTextChanged();
7536         }
7537 
7538         if (mEditor != null) {
7539             // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
7540             mEditor.prepareCursorControllers();
7541 
7542             mEditor.maybeFireScheduledRestartInputForSetText();
7543         }
7544     }
7545 
7546     /**
7547      * Sets the TextView to display the specified slice of the specified
7548      * char array. You must promise that you will not change the contents
7549      * of the array except for right before another call to setText(),
7550      * since the TextView has no way to know that the text
7551      * has changed and that it needs to invalidate and re-layout.
7552      *
7553      * @throws NullPointerException if text is null
7554      * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length
7555      *
7556      * @param text char array to be displayed
7557      * @param start start index in the char array
7558      * @param len length of char count after {@code start}
7559      */
setText(@onNull char[] text, int start, int len)7560     public final void setText(@NonNull char[] text, int start, int len) {
7561         int oldlen = 0;
7562 
7563         if (start < 0 || len < 0 || start + len > text.length) {
7564             throw new IndexOutOfBoundsException(start + ", " + len);
7565         }
7566 
7567         /*
7568          * We must do the before-notification here ourselves because if
7569          * the old text is a CharWrapper we destroy it before calling
7570          * into the normal path.
7571          */
7572         if (mText != null) {
7573             oldlen = mText.length();
7574             sendBeforeTextChanged(mText, 0, oldlen, len);
7575         } else {
7576             sendBeforeTextChanged("", 0, 0, len);
7577         }
7578 
7579         if (mCharWrapper == null) {
7580             mCharWrapper = new CharWrapper(text, start, len);
7581         } else {
7582             mCharWrapper.set(text, start, len);
7583         }
7584 
7585         setText(mCharWrapper, mBufferType, false, oldlen);
7586     }
7587 
7588     /**
7589      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
7590      * the cursor position. Same as
7591      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
7592      * position (if any) is retained in the new text.
7593      * <p/>
7594      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7595      * intermediate {@link Spannable Spannables}. Likewise it will use
7596      * {@link android.text.Editable.Factory} to create final or intermediate
7597      * {@link Editable Editables}.
7598      *
7599      * @param text text to be displayed
7600      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7601      *              stored as a static text, styleable/spannable text, or editable text
7602      *
7603      * @see #setText(CharSequence, android.widget.TextView.BufferType)
7604      */
setTextKeepState(CharSequence text, BufferType type)7605     public final void setTextKeepState(CharSequence text, BufferType type) {
7606         int start = getSelectionStart();
7607         int end = getSelectionEnd();
7608         int len = text.length();
7609 
7610         setText(text, type);
7611 
7612         if (start >= 0 || end >= 0) {
7613             if (mSpannable != null) {
7614                 Selection.setSelection(mSpannable,
7615                                        Math.max(0, Math.min(start, len)),
7616                                        Math.max(0, Math.min(end, len)));
7617             }
7618         }
7619     }
7620 
7621     /**
7622      * Sets the text to be displayed using a string resource identifier.
7623      *
7624      * @param resid the resource identifier of the string resource to be displayed
7625      *
7626      * @see #setText(CharSequence)
7627      *
7628      * @attr ref android.R.styleable#TextView_text
7629      */
7630     @android.view.RemotableViewMethod
setText(@tringRes int resid)7631     public final void setText(@StringRes int resid) {
7632         setText(getContext().getResources().getText(resid));
7633         mTextSetFromXmlOrResourceId = true;
7634         mTextId = resid;
7635     }
7636 
7637     /**
7638      * Sets the text to be displayed using a string resource identifier and the
7639      * {@link android.widget.TextView.BufferType}.
7640      * <p/>
7641      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7642      * intermediate {@link Spannable Spannables}. Likewise it will use
7643      * {@link android.text.Editable.Factory} to create final or intermediate
7644      * {@link Editable Editables}.
7645      *
7646      * @param resid the resource identifier of the string resource to be displayed
7647      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7648      *              stored as a static text, styleable/spannable text, or editable text
7649      *
7650      * @see #setText(int)
7651      * @see #setText(CharSequence)
7652      * @see android.widget.TextView.BufferType
7653      * @see #setSpannableFactory(Spannable.Factory)
7654      * @see #setEditableFactory(Editable.Factory)
7655      *
7656      * @attr ref android.R.styleable#TextView_text
7657      * @attr ref android.R.styleable#TextView_bufferType
7658      */
setText(@tringRes int resid, BufferType type)7659     public final void setText(@StringRes int resid, BufferType type) {
7660         setText(getContext().getResources().getText(resid), type);
7661         mTextSetFromXmlOrResourceId = true;
7662         mTextId = resid;
7663     }
7664 
7665     /**
7666      * Sets the text to be displayed when the text of the TextView is empty.
7667      * Null means to use the normal empty text. The hint does not currently
7668      * participate in determining the size of the view.
7669      *
7670      * @attr ref android.R.styleable#TextView_hint
7671      */
7672     @android.view.RemotableViewMethod
setHint(CharSequence hint)7673     public final void setHint(CharSequence hint) {
7674         setHintInternal(hint);
7675 
7676         if (mEditor != null && isInputMethodTarget()) {
7677             mEditor.reportExtractedText();
7678         }
7679     }
7680 
setHintInternal(CharSequence hint)7681     private void setHintInternal(CharSequence hint) {
7682         mHideHint = false;
7683         mHint = TextUtils.stringOrSpannedString(hint);
7684 
7685         if (mLayout != null) {
7686             checkForRelayout();
7687         }
7688 
7689         if (mText.length() == 0) {
7690             invalidate();
7691         }
7692 
7693         // Invalidate display list if hint is currently used
7694         if (mEditor != null && mText.length() == 0 && mHint != null) {
7695             mEditor.invalidateTextDisplayList();
7696         }
7697     }
7698 
7699     /**
7700      * Sets the text to be displayed when the text of the TextView is empty,
7701      * from a resource.
7702      *
7703      * @attr ref android.R.styleable#TextView_hint
7704      */
7705     @android.view.RemotableViewMethod
setHint(@tringRes int resid)7706     public final void setHint(@StringRes int resid) {
7707         mHintId = resid;
7708         setHint(getContext().getResources().getText(resid));
7709     }
7710 
7711     /**
7712      * Returns the hint that is displayed when the text of the TextView
7713      * is empty.
7714      *
7715      * @attr ref android.R.styleable#TextView_hint
7716      */
7717     @InspectableProperty
7718     @ViewDebug.CapturedViewProperty
getHint()7719     public CharSequence getHint() {
7720         return mHint;
7721     }
7722 
7723     /**
7724      * Temporarily hides the hint text until the text is modified, or the hint text is modified, or
7725      * the view gains or loses focus.
7726      *
7727      * @hide
7728      */
hideHint()7729     public void hideHint() {
7730         if (isShowingHint()) {
7731             mHideHint = true;
7732             invalidate();
7733         }
7734     }
7735 
7736     /**
7737      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
7738      * line characters instead of letting it wrap onto multiple lines.
7739      *
7740      * @attr ref android.R.styleable#TextView_singleLine
7741      */
7742     @InspectableProperty
isSingleLine()7743     public boolean isSingleLine() {
7744         return mSingleLine;
7745     }
7746 
isMultilineInputType(int type)7747     private static boolean isMultilineInputType(int type) {
7748         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
7749                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
7750     }
7751 
7752     /**
7753      * Removes the suggestion spans.
7754      */
removeSuggestionSpans(CharSequence text)7755     CharSequence removeSuggestionSpans(CharSequence text) {
7756         if (text instanceof Spanned) {
7757             Spannable spannable;
7758             if (text instanceof Spannable) {
7759                 spannable = (Spannable) text;
7760             } else {
7761                 spannable = mSpannableFactory.newSpannable(text);
7762             }
7763 
7764             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
7765             if (spans.length == 0) {
7766                 return text;
7767             } else {
7768                 text = spannable;
7769             }
7770 
7771             for (int i = 0; i < spans.length; i++) {
7772                 spannable.removeSpan(spans[i]);
7773             }
7774         }
7775         return text;
7776     }
7777 
7778     /**
7779      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
7780      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
7781      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
7782      * then a soft keyboard will not be displayed for this text view.
7783      *
7784      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
7785      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
7786      * type.
7787      *
7788      * @see #getInputType()
7789      * @see #setRawInputType(int)
7790      * @see android.text.InputType
7791      * @attr ref android.R.styleable#TextView_inputType
7792      */
setInputType(int type)7793     public void setInputType(int type) {
7794         final boolean wasPassword = isPasswordInputType(getInputType());
7795         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
7796         setInputType(type, false);
7797         final boolean isPassword = isPasswordInputType(type);
7798         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
7799         boolean forceUpdate = false;
7800         if (isPassword) {
7801             setTransformationMethod(PasswordTransformationMethod.getInstance());
7802             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
7803                     Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
7804         } else if (isVisiblePassword) {
7805             if (mTransformation == PasswordTransformationMethod.getInstance()) {
7806                 forceUpdate = true;
7807             }
7808             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
7809                     Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
7810         } else if (wasPassword || wasVisiblePassword) {
7811             // not in password mode, clean up typeface and transformation
7812             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
7813                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
7814                     FontStyle.FONT_WEIGHT_UNSPECIFIED);
7815             if (mTransformation == PasswordTransformationMethod.getInstance()) {
7816                 forceUpdate = true;
7817             }
7818         }
7819 
7820         boolean singleLine = !isMultilineInputType(type);
7821 
7822         // We need to update the single line mode if it has changed or we
7823         // were previously in password mode.
7824         if (mSingleLine != singleLine || forceUpdate) {
7825             // Change single line mode, but only change the transformation if
7826             // we are not in password mode.
7827             applySingleLine(singleLine, !isPassword, true, true);
7828         }
7829 
7830         if (!isSuggestionsEnabled()) {
7831             setTextInternal(removeSuggestionSpans(mText));
7832         }
7833 
7834         InputMethodManager imm = getInputMethodManager();
7835         if (imm != null) imm.restartInput(this);
7836     }
7837 
7838     /**
7839      * It would be better to rely on the input type for everything. A password inputType should have
7840      * a password transformation. We should hence use isPasswordInputType instead of this method.
7841      *
7842      * We should:
7843      * - Call setInputType in setKeyListener instead of changing the input type directly (which
7844      * would install the correct transformation).
7845      * - Refuse the installation of a non-password transformation in setTransformation if the input
7846      * type is password.
7847      *
7848      * However, this is like this for legacy reasons and we cannot break existing apps. This method
7849      * is useful since it matches what the user can see (obfuscated text or not).
7850      *
7851      * @return true if the current transformation method is of the password type.
7852      */
hasPasswordTransformationMethod()7853     boolean hasPasswordTransformationMethod() {
7854         return mTransformation instanceof PasswordTransformationMethod;
7855     }
7856 
7857     /**
7858      * Returns true if the current inputType is any type of password.
7859      *
7860      * @hide
7861      */
isAnyPasswordInputType()7862     public boolean isAnyPasswordInputType() {
7863         final int inputType = getInputType();
7864         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
7865     }
7866 
isPasswordInputType(int inputType)7867     static boolean isPasswordInputType(int inputType) {
7868         final int variation =
7869                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
7870         return variation
7871                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
7872                 || variation
7873                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
7874                 || variation
7875                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
7876     }
7877 
isVisiblePasswordInputType(int inputType)7878     private static boolean isVisiblePasswordInputType(int inputType) {
7879         final int variation =
7880                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
7881         return variation
7882                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
7883     }
7884 
7885     /**
7886      * Directly change the content type integer of the text view, without
7887      * modifying any other state.
7888      * @see #setInputType(int)
7889      * @see android.text.InputType
7890      * @attr ref android.R.styleable#TextView_inputType
7891      */
setRawInputType(int type)7892     public void setRawInputType(int type) {
7893         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
7894         createEditorIfNeeded();
7895         mEditor.mInputType = type;
7896         ensureEditorFocusedNotifiedToHandwritingInitiator();
7897     }
7898 
ensureEditorFocusedNotifiedToHandwritingInitiator()7899     private void ensureEditorFocusedNotifiedToHandwritingInitiator() {
7900         if (!initiationWithoutInputConnection() || isHandwritingDelegate()) {
7901             return;
7902         }
7903         ViewRootImpl viewRoot = getViewRootImpl();
7904         if (viewRoot == null) {
7905             return;
7906         }
7907         if (isFocused() && hasWindowFocus() && onCheckIsTextEditor()) {
7908             viewRoot.getHandwritingInitiator().onEditorFocused(this);
7909         }
7910     }
7911 
7912     @Override
getAutofillHints()7913     public String[] getAutofillHints() {
7914         String[] hints = super.getAutofillHints();
7915         if (isAnyPasswordInputType()) {
7916             if (!ArrayUtils.contains(hints, AUTOFILL_HINT_PASSWORD_AUTO)) {
7917                 hints = ArrayUtils.appendElement(String.class, hints,
7918                         AUTOFILL_HINT_PASSWORD_AUTO);
7919             }
7920         }
7921         return hints;
7922     }
7923 
7924     /**
7925      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
7926      *         a {@code Locale} object that can be used to customize key various listeners.
7927      * @see DateKeyListener#getInstance(Locale)
7928      * @see DateTimeKeyListener#getInstance(Locale)
7929      * @see DigitsKeyListener#getInstance(Locale)
7930      * @see TimeKeyListener#getInstance(Locale)
7931      */
7932     @Nullable
getCustomLocaleForKeyListenerOrNull()7933     private Locale getCustomLocaleForKeyListenerOrNull() {
7934         if (!mUseInternationalizedInput) {
7935             // If the application does not target O, stick to the previous behavior.
7936             return null;
7937         }
7938         final LocaleList locales = getImeHintLocales();
7939         if (locales == null) {
7940             // If the application does not explicitly specify IME hint locale, also stick to the
7941             // previous behavior.
7942             return null;
7943         }
7944         return locales.get(0);
7945     }
7946 
7947     @UnsupportedAppUsage
setInputType(int type, boolean direct)7948     private void setInputType(int type, boolean direct) {
7949         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
7950         KeyListener input;
7951         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
7952             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
7953             TextKeyListener.Capitalize cap;
7954             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
7955                 cap = TextKeyListener.Capitalize.CHARACTERS;
7956             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
7957                 cap = TextKeyListener.Capitalize.WORDS;
7958             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
7959                 cap = TextKeyListener.Capitalize.SENTENCES;
7960             } else {
7961                 cap = TextKeyListener.Capitalize.NONE;
7962             }
7963             input = TextKeyListener.getInstance(autotext, cap);
7964         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
7965             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7966             input = DigitsKeyListener.getInstance(
7967                     locale,
7968                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
7969                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
7970             if (locale != null) {
7971                 // Override type, if necessary for i18n.
7972                 int newType = input.getInputType();
7973                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
7974                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
7975                     // The class is different from the original class. So we need to override
7976                     // 'type'. But we want to keep the password flag if it's there.
7977                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
7978                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
7979                     }
7980                     type = newType;
7981                 }
7982             }
7983         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
7984             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7985             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
7986                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
7987                     input = DateKeyListener.getInstance(locale);
7988                     break;
7989                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
7990                     input = TimeKeyListener.getInstance(locale);
7991                     break;
7992                 default:
7993                     input = DateTimeKeyListener.getInstance(locale);
7994                     break;
7995             }
7996             if (mUseInternationalizedInput) {
7997                 type = input.getInputType(); // Override type, if necessary for i18n.
7998             }
7999         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
8000             input = DialerKeyListener.getInstance();
8001         } else {
8002             input = TextKeyListener.getInstance();
8003         }
8004         setRawInputType(type);
8005         mListenerChanged = false;
8006         if (direct) {
8007             createEditorIfNeeded();
8008             mEditor.mKeyListener = input;
8009         } else {
8010             setKeyListenerOnly(input);
8011         }
8012     }
8013 
8014     /**
8015      * Get the type of the editable content.
8016      *
8017      * @see #setInputType(int)
8018      * @see android.text.InputType
8019      */
8020     @InspectableProperty(flagMapping = {
8021             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
8022             @FlagEntry(
8023                     name = "text",
8024                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8025                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
8026             @FlagEntry(
8027                     name = "textUri",
8028                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8029                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
8030             @FlagEntry(
8031                     name = "textEmailAddress",
8032                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8033                     target = InputType.TYPE_CLASS_TEXT
8034                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
8035             @FlagEntry(
8036                     name = "textEmailSubject",
8037                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8038                     target = InputType.TYPE_CLASS_TEXT
8039                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
8040             @FlagEntry(
8041                     name = "textShortMessage",
8042                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8043                     target = InputType.TYPE_CLASS_TEXT
8044                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
8045             @FlagEntry(
8046                     name = "textLongMessage",
8047                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8048                     target = InputType.TYPE_CLASS_TEXT
8049                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
8050             @FlagEntry(
8051                     name = "textPersonName",
8052                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8053                     target = InputType.TYPE_CLASS_TEXT
8054                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
8055             @FlagEntry(
8056                     name = "textPostalAddress",
8057                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8058                     target = InputType.TYPE_CLASS_TEXT
8059                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
8060             @FlagEntry(
8061                     name = "textPassword",
8062                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8063                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
8064             @FlagEntry(
8065                     name = "textVisiblePassword",
8066                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8067                     target = InputType.TYPE_CLASS_TEXT
8068                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
8069             @FlagEntry(
8070                     name = "textWebEditText",
8071                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8072                     target = InputType.TYPE_CLASS_TEXT
8073                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
8074             @FlagEntry(
8075                     name = "textFilter",
8076                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8077                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
8078             @FlagEntry(
8079                     name = "textPhonetic",
8080                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8081                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
8082             @FlagEntry(
8083                     name = "textWebEmailAddress",
8084                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8085                     target = InputType.TYPE_CLASS_TEXT
8086                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
8087             @FlagEntry(
8088                     name = "textWebPassword",
8089                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8090                     target = InputType.TYPE_CLASS_TEXT
8091                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
8092             @FlagEntry(
8093                     name = "number",
8094                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8095                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
8096             @FlagEntry(
8097                     name = "numberPassword",
8098                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8099                     target = InputType.TYPE_CLASS_NUMBER
8100                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
8101             @FlagEntry(
8102                     name = "phone",
8103                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8104                     target = InputType.TYPE_CLASS_PHONE),
8105             @FlagEntry(
8106                     name = "datetime",
8107                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8108                     target = InputType.TYPE_CLASS_DATETIME
8109                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
8110             @FlagEntry(
8111                     name = "date",
8112                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8113                     target = InputType.TYPE_CLASS_DATETIME
8114                             | InputType.TYPE_DATETIME_VARIATION_DATE),
8115             @FlagEntry(
8116                     name = "time",
8117                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8118                     target = InputType.TYPE_CLASS_DATETIME
8119                             | InputType.TYPE_DATETIME_VARIATION_TIME),
8120             @FlagEntry(
8121                     name = "textCapCharacters",
8122                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8123                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
8124             @FlagEntry(
8125                     name = "textCapWords",
8126                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8127                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
8128             @FlagEntry(
8129                     name = "textCapSentences",
8130                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8131                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
8132             @FlagEntry(
8133                     name = "textAutoCorrect",
8134                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8135                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
8136             @FlagEntry(
8137                     name = "textAutoComplete",
8138                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8139                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
8140             @FlagEntry(
8141                     name = "textMultiLine",
8142                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8143                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
8144             @FlagEntry(
8145                     name = "textImeMultiLine",
8146                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8147                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
8148             @FlagEntry(
8149                     name = "textNoSuggestions",
8150                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8151                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
8152             @FlagEntry(
8153                     name = "numberSigned",
8154                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8155                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
8156             @FlagEntry(
8157                     name = "numberDecimal",
8158                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8159                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
8160     })
getInputType()8161     public int getInputType() {
8162         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
8163     }
8164 
8165     /**
8166      * Change the editor type integer associated with the text view, which
8167      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
8168      * when it has focus.
8169      * @see #getImeOptions
8170      * @see android.view.inputmethod.EditorInfo
8171      * @attr ref android.R.styleable#TextView_imeOptions
8172      */
setImeOptions(int imeOptions)8173     public void setImeOptions(int imeOptions) {
8174         createEditorIfNeeded();
8175         mEditor.createInputContentTypeIfNeeded();
8176         mEditor.mInputContentType.imeOptions = imeOptions;
8177     }
8178 
8179     /**
8180      * Get the type of the Input Method Editor (IME).
8181      * @return the type of the IME
8182      * @see #setImeOptions(int)
8183      * @see EditorInfo
8184      */
8185     @InspectableProperty(flagMapping = {
8186             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
8187             @FlagEntry(
8188                     name = "actionUnspecified",
8189                     mask = EditorInfo.IME_MASK_ACTION,
8190                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
8191             @FlagEntry(
8192                     name = "actionNone",
8193                     mask = EditorInfo.IME_MASK_ACTION,
8194                     target = EditorInfo.IME_ACTION_NONE),
8195             @FlagEntry(
8196                     name = "actionGo",
8197                     mask = EditorInfo.IME_MASK_ACTION,
8198                     target = EditorInfo.IME_ACTION_GO),
8199             @FlagEntry(
8200                     name = "actionSearch",
8201                     mask = EditorInfo.IME_MASK_ACTION,
8202                     target = EditorInfo.IME_ACTION_SEARCH),
8203             @FlagEntry(
8204                     name = "actionSend",
8205                     mask = EditorInfo.IME_MASK_ACTION,
8206                     target = EditorInfo.IME_ACTION_SEND),
8207             @FlagEntry(
8208                     name = "actionNext",
8209                     mask = EditorInfo.IME_MASK_ACTION,
8210                     target = EditorInfo.IME_ACTION_NEXT),
8211             @FlagEntry(
8212                     name = "actionDone",
8213                     mask = EditorInfo.IME_MASK_ACTION,
8214                     target = EditorInfo.IME_ACTION_DONE),
8215             @FlagEntry(
8216                     name = "actionPrevious",
8217                     mask = EditorInfo.IME_MASK_ACTION,
8218                     target = EditorInfo.IME_ACTION_PREVIOUS),
8219             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
8220             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
8221             @FlagEntry(
8222                     name = "flagNavigatePrevious",
8223                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
8224             @FlagEntry(
8225                     name = "flagNoAccessoryAction",
8226                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
8227             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
8228             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
8229             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
8230             @FlagEntry(
8231                     name = "flagNoPersonalizedLearning",
8232                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
8233     })
getImeOptions()8234     public int getImeOptions() {
8235         return mEditor != null && mEditor.mInputContentType != null
8236                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
8237     }
8238 
8239     /**
8240      * Change the custom IME action associated with the text view, which
8241      * will be reported to an IME with {@link EditorInfo#actionLabel}
8242      * and {@link EditorInfo#actionId} when it has focus.
8243      * @see #getImeActionLabel
8244      * @see #getImeActionId
8245      * @see android.view.inputmethod.EditorInfo
8246      * @attr ref android.R.styleable#TextView_imeActionLabel
8247      * @attr ref android.R.styleable#TextView_imeActionId
8248      */
setImeActionLabel(CharSequence label, int actionId)8249     public void setImeActionLabel(CharSequence label, int actionId) {
8250         createEditorIfNeeded();
8251         mEditor.createInputContentTypeIfNeeded();
8252         mEditor.mInputContentType.imeActionLabel = label;
8253         mEditor.mInputContentType.imeActionId = actionId;
8254     }
8255 
8256     /**
8257      * Get the IME action label previous set with {@link #setImeActionLabel}.
8258      *
8259      * @see #setImeActionLabel
8260      * @see android.view.inputmethod.EditorInfo
8261      */
8262     @InspectableProperty
getImeActionLabel()8263     public CharSequence getImeActionLabel() {
8264         return mEditor != null && mEditor.mInputContentType != null
8265                 ? mEditor.mInputContentType.imeActionLabel : null;
8266     }
8267 
8268     /**
8269      * Get the IME action ID previous set with {@link #setImeActionLabel}.
8270      *
8271      * @see #setImeActionLabel
8272      * @see android.view.inputmethod.EditorInfo
8273      */
8274     @InspectableProperty
getImeActionId()8275     public int getImeActionId() {
8276         return mEditor != null && mEditor.mInputContentType != null
8277                 ? mEditor.mInputContentType.imeActionId : 0;
8278     }
8279 
8280     /**
8281      * Set a special listener to be called when an action is performed
8282      * on the text view.  This will be called when the enter key is pressed,
8283      * or when an action supplied to the IME is selected by the user.  Setting
8284      * this means that the normal hard key event will not insert a newline
8285      * into the text view, even if it is multi-line; holding down the ALT
8286      * modifier will, however, allow the user to insert a newline character.
8287      */
setOnEditorActionListener(OnEditorActionListener l)8288     public void setOnEditorActionListener(OnEditorActionListener l) {
8289         createEditorIfNeeded();
8290         mEditor.createInputContentTypeIfNeeded();
8291         mEditor.mInputContentType.onEditorActionListener = l;
8292     }
8293 
8294     /**
8295      * Called when an attached input method calls
8296      * {@link InputConnection#performEditorAction(int)
8297      * InputConnection.performEditorAction()}
8298      * for this text view.  The default implementation will call your action
8299      * listener supplied to {@link #setOnEditorActionListener}, or perform
8300      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
8301      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
8302      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
8303      * EditorInfo.IME_ACTION_DONE}.
8304      *
8305      * <p>For backwards compatibility, if no IME options have been set and the
8306      * text view would not normally advance focus on enter, then
8307      * the NEXT and DONE actions received here will be turned into an enter
8308      * key down/up pair to go through the normal key handling.
8309      *
8310      * @param actionCode The code of the action being performed.
8311      *
8312      * @see #setOnEditorActionListener
8313      */
onEditorAction(int actionCode)8314     public void onEditorAction(int actionCode) {
8315         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
8316         if (ict != null) {
8317             if (ict.onEditorActionListener != null) {
8318                 if (ict.onEditorActionListener.onEditorAction(this,
8319                         actionCode, null)) {
8320                     return;
8321                 }
8322             }
8323 
8324             // This is the handling for some default action.
8325             // Note that for backwards compatibility we don't do this
8326             // default handling if explicit ime options have not been given,
8327             // instead turning this into the normal enter key codes that an
8328             // app may be expecting.
8329             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
8330                 View v = focusSearch(FOCUS_FORWARD);
8331                 if (v != null) {
8332                     if (!v.requestFocus(FOCUS_FORWARD)) {
8333                         throw new IllegalStateException("focus search returned a view "
8334                                 + "that wasn't able to take focus!");
8335                     }
8336                 }
8337                 return;
8338 
8339             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
8340                 View v = focusSearch(FOCUS_BACKWARD);
8341                 if (v != null) {
8342                     if (!v.requestFocus(FOCUS_BACKWARD)) {
8343                         throw new IllegalStateException("focus search returned a view "
8344                                 + "that wasn't able to take focus!");
8345                     }
8346                 }
8347                 return;
8348 
8349             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
8350                 InputMethodManager imm = getInputMethodManager();
8351                 if (imm != null) {
8352                     imm.hideSoftInputFromView(this, 0);
8353                 }
8354                 return;
8355             }
8356         }
8357 
8358         ViewRootImpl viewRootImpl = getViewRootImpl();
8359         if (viewRootImpl != null) {
8360             long eventTime = SystemClock.uptimeMillis();
8361             viewRootImpl.dispatchKeyFromIme(
8362                     new KeyEvent(eventTime, eventTime,
8363                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
8364                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
8365                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
8366                     | KeyEvent.FLAG_EDITOR_ACTION));
8367             viewRootImpl.dispatchKeyFromIme(
8368                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
8369                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
8370                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
8371                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
8372                     | KeyEvent.FLAG_EDITOR_ACTION));
8373         }
8374     }
8375 
8376     /**
8377      * Set the private content type of the text, which is the
8378      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
8379      * field that will be filled in when creating an input connection.
8380      *
8381      * @see #getPrivateImeOptions()
8382      * @see EditorInfo#privateImeOptions
8383      * @attr ref android.R.styleable#TextView_privateImeOptions
8384      */
setPrivateImeOptions(String type)8385     public void setPrivateImeOptions(String type) {
8386         createEditorIfNeeded();
8387         mEditor.createInputContentTypeIfNeeded();
8388         mEditor.mInputContentType.privateImeOptions = type;
8389     }
8390 
8391     /**
8392      * Get the private type of the content.
8393      *
8394      * @see #setPrivateImeOptions(String)
8395      * @see EditorInfo#privateImeOptions
8396      */
8397     @InspectableProperty
getPrivateImeOptions()8398     public String getPrivateImeOptions() {
8399         return mEditor != null && mEditor.mInputContentType != null
8400                 ? mEditor.mInputContentType.privateImeOptions : null;
8401     }
8402 
8403     /**
8404      * Set the extra input data of the text, which is the
8405      * {@link EditorInfo#extras TextBoxAttribute.extras}
8406      * Bundle that will be filled in when creating an input connection.  The
8407      * given integer is the resource identifier of an XML resource holding an
8408      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
8409      *
8410      * @see #getInputExtras(boolean)
8411      * @see EditorInfo#extras
8412      * @attr ref android.R.styleable#TextView_editorExtras
8413      */
setInputExtras(@mlRes int xmlResId)8414     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
8415         createEditorIfNeeded();
8416         XmlResourceParser parser = getResources().getXml(xmlResId);
8417         mEditor.createInputContentTypeIfNeeded();
8418         mEditor.mInputContentType.extras = new Bundle();
8419         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
8420     }
8421 
8422     /**
8423      * Retrieve the input extras currently associated with the text view, which
8424      * can be viewed as well as modified.
8425      *
8426      * @param create If true, the extras will be created if they don't already
8427      * exist.  Otherwise, null will be returned if none have been created.
8428      * @see #setInputExtras(int)
8429      * @see EditorInfo#extras
8430      * @attr ref android.R.styleable#TextView_editorExtras
8431      */
getInputExtras(boolean create)8432     public Bundle getInputExtras(boolean create) {
8433         if (mEditor == null && !create) return null;
8434         createEditorIfNeeded();
8435         if (mEditor.mInputContentType == null) {
8436             if (!create) return null;
8437             mEditor.createInputContentTypeIfNeeded();
8438         }
8439         if (mEditor.mInputContentType.extras == null) {
8440             if (!create) return null;
8441             mEditor.mInputContentType.extras = new Bundle();
8442         }
8443         return mEditor.mInputContentType.extras;
8444     }
8445 
8446     /**
8447      * Change "hint" locales associated with the text view, which will be reported to an IME with
8448      * {@link EditorInfo#hintLocales} when it has focus.
8449      *
8450      * Starting with Android O, this also causes internationalized listeners to be created (or
8451      * change locale) based on the first locale in the input locale list.
8452      *
8453      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
8454      * call {@link InputMethodManager#restartInput(View)}.</p>
8455      * @param hintLocales List of the languages that the user is supposed to switch to no matter
8456      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
8457      * @see #getImeHintLocales()
8458      * @see android.view.inputmethod.EditorInfo#hintLocales
8459      */
setImeHintLocales(@ullable LocaleList hintLocales)8460     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
8461         createEditorIfNeeded();
8462         mEditor.createInputContentTypeIfNeeded();
8463         mEditor.mInputContentType.imeHintLocales = hintLocales;
8464         if (mUseInternationalizedInput) {
8465             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
8466         }
8467     }
8468 
8469     /**
8470      * @return The current languages list "hint". {@code null} when no "hint" is available.
8471      * @see #setImeHintLocales(LocaleList)
8472      * @see android.view.inputmethod.EditorInfo#hintLocales
8473      */
8474     @Nullable
getImeHintLocales()8475     public LocaleList getImeHintLocales() {
8476         if (mEditor == null) {
8477             return null;
8478         }
8479         if (mEditor.mInputContentType == null) {
8480             return null;
8481         }
8482         return mEditor.mInputContentType.imeHintLocales;
8483     }
8484 
8485     /**
8486      * Returns the error message that was set to be displayed with
8487      * {@link #setError}, or <code>null</code> if no error was set
8488      * or if it the error was cleared by the widget after user input.
8489      */
getError()8490     public CharSequence getError() {
8491         return mEditor == null ? null : mEditor.mError;
8492     }
8493 
8494     /**
8495      * Sets the right-hand compound drawable of the TextView to the "error"
8496      * icon and sets an error message that will be displayed in a popup when
8497      * the TextView has focus.  The icon and error message will be reset to
8498      * null when any key events cause changes to the TextView's text.  If the
8499      * <code>error</code> is <code>null</code>, the error message and icon
8500      * will be cleared.
8501      */
8502     @android.view.RemotableViewMethod
setError(CharSequence error)8503     public void setError(CharSequence error) {
8504         if (error == null) {
8505             setError(null, null);
8506         } else {
8507             Drawable dr = getContext().getDrawable(
8508                     com.android.internal.R.drawable.indicator_input_error);
8509 
8510             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
8511             setError(error, dr);
8512         }
8513     }
8514 
8515     /**
8516      * Sets the right-hand compound drawable of the TextView to the specified
8517      * icon and sets an error message that will be displayed in a popup when
8518      * the TextView has focus.  The icon and error message will be reset to
8519      * null when any key events cause changes to the TextView's text.  The
8520      * drawable must already have had {@link Drawable#setBounds} set on it.
8521      * If the <code>error</code> is <code>null</code>, the error message will
8522      * be cleared (and you should provide a <code>null</code> icon as well).
8523      */
setError(CharSequence error, Drawable icon)8524     public void setError(CharSequence error, Drawable icon) {
8525         createEditorIfNeeded();
8526         mEditor.setError(error, icon);
8527         notifyViewAccessibilityStateChangedIfNeeded(
8528                 AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR
8529                         | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID);
8530     }
8531 
8532     @Override
setFrame(int l, int t, int r, int b)8533     protected boolean setFrame(int l, int t, int r, int b) {
8534         boolean result = super.setFrame(l, t, r, b);
8535 
8536         if (mEditor != null) mEditor.setFrame();
8537 
8538         restartMarqueeIfNeeded();
8539 
8540         return result;
8541     }
8542 
restartMarqueeIfNeeded()8543     private void restartMarqueeIfNeeded() {
8544         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8545             mRestartMarquee = false;
8546             startMarquee();
8547         }
8548     }
8549 
8550     /**
8551      * Sets the list of input filters that will be used if the buffer is
8552      * Editable. Has no effect otherwise.
8553      *
8554      * @attr ref android.R.styleable#TextView_maxLength
8555      */
setFilters(InputFilter[] filters)8556     public void setFilters(InputFilter[] filters) {
8557         if (filters == null) {
8558             throw new IllegalArgumentException();
8559         }
8560 
8561         mFilters = filters;
8562 
8563         if (mText instanceof Editable) {
8564             setFilters((Editable) mText, filters);
8565         }
8566     }
8567 
8568     /**
8569      * Sets the list of input filters on the specified Editable,
8570      * and includes mInput in the list if it is an InputFilter.
8571      */
setFilters(Editable e, InputFilter[] filters)8572     private void setFilters(Editable e, InputFilter[] filters) {
8573         if (mEditor != null) {
8574             final boolean undoFilter = mEditor.mUndoInputFilter != null;
8575             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
8576             int num = 0;
8577             if (undoFilter) num++;
8578             if (keyFilter) num++;
8579             if (num > 0) {
8580                 InputFilter[] nf = new InputFilter[filters.length + num];
8581 
8582                 System.arraycopy(filters, 0, nf, 0, filters.length);
8583                 num = 0;
8584                 if (undoFilter) {
8585                     nf[filters.length] = mEditor.mUndoInputFilter;
8586                     num++;
8587                 }
8588                 if (keyFilter) {
8589                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
8590                 }
8591 
8592                 e.setFilters(nf);
8593                 return;
8594             }
8595         }
8596         e.setFilters(filters);
8597     }
8598 
8599     /**
8600      * Returns the current list of input filters.
8601      *
8602      * @attr ref android.R.styleable#TextView_maxLength
8603      */
getFilters()8604     public InputFilter[] getFilters() {
8605         return mFilters;
8606     }
8607 
8608     /////////////////////////////////////////////////////////////////////////
8609 
getBoxHeight(Layout l)8610     private int getBoxHeight(Layout l) {
8611         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
8612         int padding = (l == mHintLayout)
8613                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
8614                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
8615         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
8616     }
8617 
8618     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)8619     int getVerticalOffset(boolean forceNormal) {
8620         int voffset = 0;
8621         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
8622 
8623         Layout l = mLayout;
8624         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
8625             l = mHintLayout;
8626         }
8627 
8628         if (gravity != Gravity.TOP) {
8629             int boxht = getBoxHeight(l);
8630             int textht = l.getHeight();
8631 
8632             if (textht < boxht) {
8633                 if (gravity == Gravity.BOTTOM) {
8634                     voffset = boxht - textht;
8635                 } else { // (gravity == Gravity.CENTER_VERTICAL)
8636                     voffset = (boxht - textht) >> 1;
8637                 }
8638             }
8639         }
8640         return voffset;
8641     }
8642 
getBottomVerticalOffset(boolean forceNormal)8643     private int getBottomVerticalOffset(boolean forceNormal) {
8644         int voffset = 0;
8645         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
8646 
8647         Layout l = mLayout;
8648         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
8649             l = mHintLayout;
8650         }
8651 
8652         if (gravity != Gravity.BOTTOM) {
8653             int boxht = getBoxHeight(l);
8654             int textht = l.getHeight();
8655 
8656             if (textht < boxht) {
8657                 if (gravity == Gravity.TOP) {
8658                     voffset = boxht - textht;
8659                 } else { // (gravity == Gravity.CENTER_VERTICAL)
8660                     voffset = (boxht - textht) >> 1;
8661                 }
8662             }
8663         }
8664         return voffset;
8665     }
8666 
invalidateCursorPath()8667     void invalidateCursorPath() {
8668         if (mHighlightPathBogus) {
8669             invalidateCursor();
8670         } else {
8671             final int horizontalPadding = getCompoundPaddingLeft();
8672             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
8673 
8674             if (mEditor.mDrawableForCursor == null) {
8675                 synchronized (TEMP_RECTF) {
8676                     /*
8677                      * The reason for this concern about the thickness of the
8678                      * cursor and doing the floor/ceil on the coordinates is that
8679                      * some EditTexts (notably textfields in the Browser) have
8680                      * anti-aliased text where not all the characters are
8681                      * necessarily at integer-multiple locations.  This should
8682                      * make sure the entire cursor gets invalidated instead of
8683                      * sometimes missing half a pixel.
8684                      */
8685                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
8686                     if (thick < 1.0f) {
8687                         thick = 1.0f;
8688                     }
8689 
8690                     thick /= 2.0f;
8691 
8692                     // mHighlightPath is guaranteed to be non null at that point.
8693                     mHighlightPath.computeBounds(TEMP_RECTF, false);
8694 
8695                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
8696                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
8697                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
8698                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
8699                 }
8700             } else {
8701                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
8702                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
8703                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
8704             }
8705         }
8706     }
8707 
invalidateCursor()8708     void invalidateCursor() {
8709         int where = getSelectionEnd();
8710 
8711         invalidateCursor(where, where, where);
8712     }
8713 
invalidateCursor(int a, int b, int c)8714     private void invalidateCursor(int a, int b, int c) {
8715         if (a >= 0 || b >= 0 || c >= 0) {
8716             int start = Math.min(Math.min(a, b), c);
8717             int end = Math.max(Math.max(a, b), c);
8718             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
8719         }
8720     }
8721 
8722     /**
8723      * Invalidates the region of text enclosed between the start and end text offsets.
8724      */
invalidateRegion(int start, int end, boolean invalidateCursor)8725     void invalidateRegion(int start, int end, boolean invalidateCursor) {
8726         if (mLayout == null) {
8727             invalidate();
8728         } else {
8729             start = originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
8730             end = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
8731             int lineStart = mLayout.getLineForOffset(start);
8732             int top = mLayout.getLineTop(lineStart);
8733 
8734             // This is ridiculous, but the descent from the line above
8735             // can hang down into the line we really want to redraw,
8736             // so we have to invalidate part of the line above to make
8737             // sure everything that needs to be redrawn really is.
8738             // (But not the whole line above, because that would cause
8739             // the same problem with the descenders on the line above it!)
8740             if (lineStart > 0) {
8741                 top -= mLayout.getLineDescent(lineStart - 1);
8742             }
8743 
8744             int lineEnd;
8745 
8746             if (start == end) {
8747                 lineEnd = lineStart;
8748             } else {
8749                 lineEnd = mLayout.getLineForOffset(end);
8750             }
8751 
8752             int bottom = mLayout.getLineBottom(lineEnd);
8753 
8754             // mEditor can be null in case selection is set programmatically.
8755             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
8756                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
8757                 top = Math.min(top, bounds.top);
8758                 bottom = Math.max(bottom, bounds.bottom);
8759             }
8760 
8761             final int compoundPaddingLeft = getCompoundPaddingLeft();
8762             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
8763 
8764             int left, right;
8765             if (lineStart == lineEnd && !invalidateCursor) {
8766                 left = (int) mLayout.getPrimaryHorizontal(start);
8767                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
8768                 left += compoundPaddingLeft;
8769                 right += compoundPaddingLeft;
8770             } else {
8771                 // Rectangle bounding box when the region spans several lines
8772                 left = compoundPaddingLeft;
8773                 right = getWidth() - getCompoundPaddingRight();
8774             }
8775 
8776             invalidate(mScrollX + left, verticalPadding + top,
8777                     mScrollX + right, verticalPadding + bottom);
8778         }
8779     }
8780 
registerForPreDraw()8781     private void registerForPreDraw() {
8782         if (!mPreDrawRegistered) {
8783             getViewTreeObserver().addOnPreDrawListener(this);
8784             mPreDrawRegistered = true;
8785         }
8786     }
8787 
unregisterForPreDraw()8788     private void unregisterForPreDraw() {
8789         getViewTreeObserver().removeOnPreDrawListener(this);
8790         mPreDrawRegistered = false;
8791         mPreDrawListenerDetached = false;
8792     }
8793 
8794     /**
8795      * {@inheritDoc}
8796      */
8797     @Override
onPreDraw()8798     public boolean onPreDraw() {
8799         if (mLayout == null) {
8800             assumeLayout();
8801         }
8802 
8803         if (mMovement != null) {
8804             /* This code also provides auto-scrolling when a cursor is moved using a
8805              * CursorController (insertion point or selection limits).
8806              * For selection, ensure start or end is visible depending on controller's state.
8807              */
8808             int curs = getSelectionEnd();
8809             // Do not create the controller if it is not already created.
8810             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
8811                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
8812                 curs = getSelectionStart();
8813             }
8814 
8815             /*
8816              * TODO: This should really only keep the end in view if
8817              * it already was before the text changed.  I'm not sure
8818              * of a good way to tell from here if it was.
8819              */
8820             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8821                 curs = mText.length();
8822             }
8823 
8824             if (curs >= 0) {
8825                 bringPointIntoView(curs);
8826             }
8827         } else {
8828             bringTextIntoView();
8829         }
8830 
8831         // This has to be checked here since:
8832         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
8833         //   a screen rotation) since layout is not yet initialized at that point.
8834         if (mEditor != null && mEditor.mCreatedWithASelection) {
8835             mEditor.refreshTextActionMode();
8836             mEditor.mCreatedWithASelection = false;
8837         }
8838 
8839         unregisterForPreDraw();
8840 
8841         return true;
8842     }
8843 
8844     @Override
onAttachedToWindow()8845     protected void onAttachedToWindow() {
8846         super.onAttachedToWindow();
8847 
8848         if (mEditor != null) mEditor.onAttachedToWindow();
8849 
8850         if (mPreDrawListenerDetached) {
8851             getViewTreeObserver().addOnPreDrawListener(this);
8852             mPreDrawListenerDetached = false;
8853         }
8854     }
8855 
8856     /** @hide */
8857     @Override
onDetachedFromWindowInternal()8858     protected void onDetachedFromWindowInternal() {
8859         if (mPreDrawRegistered) {
8860             getViewTreeObserver().removeOnPreDrawListener(this);
8861             mPreDrawListenerDetached = true;
8862         }
8863 
8864         resetResolvedDrawables();
8865 
8866         if (mEditor != null) mEditor.onDetachedFromWindow();
8867 
8868         super.onDetachedFromWindowInternal();
8869     }
8870 
8871     @Override
onScreenStateChanged(int screenState)8872     public void onScreenStateChanged(int screenState) {
8873         super.onScreenStateChanged(screenState);
8874         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
8875     }
8876 
8877     @Override
isPaddingOffsetRequired()8878     protected boolean isPaddingOffsetRequired() {
8879         return mShadowRadius != 0 || mDrawables != null;
8880     }
8881 
8882     @Override
getLeftPaddingOffset()8883     protected int getLeftPaddingOffset() {
8884         return getCompoundPaddingLeft() - mPaddingLeft
8885                 + (int) Math.min(0, mShadowDx - mShadowRadius);
8886     }
8887 
8888     @Override
getTopPaddingOffset()8889     protected int getTopPaddingOffset() {
8890         return (int) Math.min(0, mShadowDy - mShadowRadius);
8891     }
8892 
8893     @Override
getBottomPaddingOffset()8894     protected int getBottomPaddingOffset() {
8895         return (int) Math.max(0, mShadowDy + mShadowRadius);
8896     }
8897 
8898     @Override
getRightPaddingOffset()8899     protected int getRightPaddingOffset() {
8900         return -(getCompoundPaddingRight() - mPaddingRight)
8901                 + (int) Math.max(0, mShadowDx + mShadowRadius);
8902     }
8903 
8904     @Override
verifyDrawable(@onNull Drawable who)8905     protected boolean verifyDrawable(@NonNull Drawable who) {
8906         final boolean verified = super.verifyDrawable(who);
8907         if (!verified && mDrawables != null) {
8908             for (Drawable dr : mDrawables.mShowing) {
8909                 if (who == dr) {
8910                     return true;
8911                 }
8912             }
8913         }
8914         return verified;
8915     }
8916 
8917     @Override
jumpDrawablesToCurrentState()8918     public void jumpDrawablesToCurrentState() {
8919         super.jumpDrawablesToCurrentState();
8920         if (mDrawables != null) {
8921             for (Drawable dr : mDrawables.mShowing) {
8922                 if (dr != null) {
8923                     dr.jumpToCurrentState();
8924                 }
8925             }
8926         }
8927     }
8928 
8929     @Override
invalidateDrawable(@onNull Drawable drawable)8930     public void invalidateDrawable(@NonNull Drawable drawable) {
8931         boolean handled = false;
8932 
8933         if (verifyDrawable(drawable)) {
8934             final Rect dirty = drawable.getBounds();
8935             int scrollX = mScrollX;
8936             int scrollY = mScrollY;
8937 
8938             // IMPORTANT: The coordinates below are based on the coordinates computed
8939             // for each compound drawable in onDraw(). Make sure to update each section
8940             // accordingly.
8941             final TextView.Drawables drawables = mDrawables;
8942             if (drawables != null) {
8943                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
8944                     final int compoundPaddingTop = getCompoundPaddingTop();
8945                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8946                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8947 
8948                     scrollX += mPaddingLeft;
8949                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
8950                     handled = true;
8951                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
8952                     final int compoundPaddingTop = getCompoundPaddingTop();
8953                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8954                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8955 
8956                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
8957                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
8958                     handled = true;
8959                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
8960                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8961                     final int compoundPaddingRight = getCompoundPaddingRight();
8962                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8963 
8964                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
8965                     scrollY += mPaddingTop;
8966                     handled = true;
8967                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
8968                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8969                     final int compoundPaddingRight = getCompoundPaddingRight();
8970                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8971 
8972                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
8973                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
8974                     handled = true;
8975                 }
8976             }
8977 
8978             if (handled) {
8979                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
8980                         dirty.right + scrollX, dirty.bottom + scrollY);
8981             }
8982         }
8983 
8984         if (!handled) {
8985             super.invalidateDrawable(drawable);
8986         }
8987     }
8988 
8989     @Override
hasOverlappingRendering()8990     public boolean hasOverlappingRendering() {
8991         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
8992         return ((getBackground() != null && getBackground().getCurrent() != null)
8993                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
8994                 || mShadowColor != 0);
8995     }
8996 
8997     /**
8998      *
8999      * Returns the state of the {@code textIsSelectable} flag (See
9000      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
9001      * to allow users to select and copy text in a non-editable TextView, the content of an
9002      * {@link EditText} can always be selected, independently of the value of this flag.
9003      * <p>
9004      *
9005      * @return True if the text displayed in this TextView can be selected by the user.
9006      *
9007      * @attr ref android.R.styleable#TextView_textIsSelectable
9008      */
9009     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()9010     public boolean isTextSelectable() {
9011         return mEditor == null ? false : mEditor.mTextIsSelectable;
9012     }
9013 
9014     /**
9015      * Sets whether the content of this view is selectable by the user. The default is
9016      * {@code false}, meaning that the content is not selectable.
9017      * <p>
9018      * When you use a TextView to display a useful piece of information to the user (such as a
9019      * contact's address), make it selectable, so that the user can select and copy its
9020      * content. You can also use set the XML attribute
9021      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
9022      * <p>
9023      * When you call this method to set the value of {@code textIsSelectable}, it sets
9024      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
9025      * and {@code longClickable} to the same value. These flags correspond to the attributes
9026      * {@link android.R.styleable#View_focusable android:focusable},
9027      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
9028      * {@link android.R.styleable#View_clickable android:clickable}, and
9029      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
9030      * flags to a state you had set previously, call one or more of the following methods:
9031      * {@link #setFocusable(boolean) setFocusable()},
9032      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
9033      * {@link #setClickable(boolean) setClickable()} or
9034      * {@link #setLongClickable(boolean) setLongClickable()}.
9035      *
9036      * @param selectable Whether the content of this TextView should be selectable.
9037      */
setTextIsSelectable(boolean selectable)9038     public void setTextIsSelectable(boolean selectable) {
9039         if (!selectable && mEditor == null) return; // false is default value with no edit data
9040 
9041         createEditorIfNeeded();
9042         if (mEditor.mTextIsSelectable == selectable) return;
9043 
9044         mEditor.mTextIsSelectable = selectable;
9045         setFocusableInTouchMode(selectable);
9046         setFocusable(FOCUSABLE_AUTO);
9047         setClickable(selectable);
9048         setLongClickable(selectable);
9049 
9050         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
9051 
9052         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
9053         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
9054 
9055         // Called by setText above, but safer in case of future code changes
9056         mEditor.prepareCursorControllers();
9057     }
9058 
9059     @Override
onCreateDrawableState(int extraSpace)9060     protected int[] onCreateDrawableState(int extraSpace) {
9061         final int[] drawableState;
9062 
9063         if (mSingleLine) {
9064             drawableState = super.onCreateDrawableState(extraSpace);
9065         } else {
9066             drawableState = super.onCreateDrawableState(extraSpace + 1);
9067             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
9068         }
9069 
9070         if (isTextSelectable()) {
9071             // Disable pressed state, which was introduced when TextView was made clickable.
9072             // Prevents text color change.
9073             // setClickable(false) would have a similar effect, but it also disables focus changes
9074             // and long press actions, which are both needed by text selection.
9075             final int length = drawableState.length;
9076             for (int i = 0; i < length; i++) {
9077                 if (drawableState[i] == R.attr.state_pressed) {
9078                     final int[] nonPressedState = new int[length - 1];
9079                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
9080                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
9081                     return nonPressedState;
9082                 }
9083             }
9084         }
9085 
9086         return drawableState;
9087     }
9088 
maybeUpdateHighlightPaths()9089     private void maybeUpdateHighlightPaths() {
9090         if (!mHighlightPathsBogus) {
9091             return;
9092         }
9093 
9094         if (mHighlightPaths != null) {
9095             mPathRecyclePool.addAll(mHighlightPaths);
9096             mHighlightPaths.clear();
9097             mHighlightPaints.clear();
9098         } else {
9099             mHighlightPaths = new ArrayList<>();
9100             mHighlightPaints = new ArrayList<>();
9101         }
9102 
9103         if (mHighlights != null) {
9104             for (int i = 0; i < mHighlights.getSize(); ++i) {
9105                 final int[] ranges = mHighlights.getRanges(i);
9106                 final Paint paint = mHighlights.getPaint(i);
9107                 final Path path;
9108                 if (mPathRecyclePool.isEmpty()) {
9109                     path = new Path();
9110                 } else {
9111                     path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9112                     mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9113                     path.reset();
9114                 }
9115 
9116                 boolean atLeastOnePathAdded = false;
9117                 for (int j = 0; j < ranges.length / 2; ++j) {
9118                     final int start = ranges[2 * j];
9119                     final int end = ranges[2 * j + 1];
9120                     if (start < end) {
9121                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
9122                                 path.addRect(left, top, right, bottom, Path.Direction.CW)
9123                         );
9124                         atLeastOnePathAdded = true;
9125                     }
9126                 }
9127                 if (atLeastOnePathAdded) {
9128                     mHighlightPaths.add(path);
9129                     mHighlightPaints.add(paint);
9130                 }
9131             }
9132         }
9133 
9134         addSearchHighlightPaths();
9135 
9136         if (hasGesturePreviewHighlight()) {
9137             final Path path;
9138             if (mPathRecyclePool.isEmpty()) {
9139                 path = new Path();
9140             } else {
9141                 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9142                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9143                 path.reset();
9144             }
9145             mLayout.getSelectionPath(
9146                     mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path);
9147             mHighlightPaths.add(path);
9148             mHighlightPaints.add(mGesturePreviewHighlightPaint);
9149         }
9150 
9151         mHighlightPathsBogus = false;
9152     }
9153 
addSearchHighlightPaths()9154     private void addSearchHighlightPaths() {
9155         if (mSearchResultHighlights != null) {
9156             final Path searchResultPath;
9157             if (mPathRecyclePool.isEmpty()) {
9158                 searchResultPath = new Path();
9159             } else {
9160                 searchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9161                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9162                 searchResultPath.reset();
9163             }
9164             final Path focusedSearchResultPath;
9165             if (mFocusedSearchResultIndex == FOCUSED_SEARCH_RESULT_INDEX_NONE) {
9166                 focusedSearchResultPath = null;
9167             } else if (mPathRecyclePool.isEmpty()) {
9168                 focusedSearchResultPath = new Path();
9169             } else {
9170                 focusedSearchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9171                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9172                 focusedSearchResultPath.reset();
9173             }
9174 
9175             boolean atLeastOnePathAdded = false;
9176             for (int j = 0; j < mSearchResultHighlights.length / 2; ++j) {
9177                 final int start = mSearchResultHighlights[2 * j];
9178                 final int end = mSearchResultHighlights[2 * j + 1];
9179                 if (start < end) {
9180                     if (j == mFocusedSearchResultIndex) {
9181                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
9182                                 focusedSearchResultPath.addRect(left, top, right, bottom,
9183                                         Path.Direction.CW)
9184                         );
9185                     } else {
9186                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
9187                                 searchResultPath.addRect(left, top, right, bottom,
9188                                         Path.Direction.CW)
9189                         );
9190                         atLeastOnePathAdded = true;
9191                     }
9192                 }
9193             }
9194             if (atLeastOnePathAdded) {
9195                 if (mSearchResultHighlightPaint == null) {
9196                     mSearchResultHighlightPaint = new Paint();
9197                 }
9198                 mSearchResultHighlightPaint.setColor(mSearchResultHighlightColor);
9199                 mSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
9200                 mHighlightPaths.add(searchResultPath);
9201                 mHighlightPaints.add(mSearchResultHighlightPaint);
9202             }
9203             if (focusedSearchResultPath != null) {
9204                 if (mFocusedSearchResultHighlightPaint == null) {
9205                     mFocusedSearchResultHighlightPaint = new Paint();
9206                 }
9207                 mFocusedSearchResultHighlightPaint.setColor(mFocusedSearchResultHighlightColor);
9208                 mFocusedSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
9209                 mHighlightPaths.add(focusedSearchResultPath);
9210                 mHighlightPaints.add(mFocusedSearchResultHighlightPaint);
9211             }
9212         }
9213     }
9214 
9215     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getUpdatedHighlightPath()9216     private Path getUpdatedHighlightPath() {
9217         Path highlight = null;
9218         Paint highlightPaint = mHighlightPaint;
9219 
9220         final int selStart = getSelectionStartTransformed();
9221         final int selEnd = getSelectionEndTransformed();
9222         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
9223             if (selStart == selEnd) {
9224                 if (mEditor != null && mEditor.shouldRenderCursor()) {
9225                     if (mHighlightPathBogus) {
9226                         if (mHighlightPath == null) mHighlightPath = new Path();
9227                         mHighlightPath.reset();
9228                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
9229                         mEditor.updateCursorPosition();
9230                         mHighlightPathBogus = false;
9231                     }
9232 
9233                     // XXX should pass to skin instead of drawing directly
9234                     highlightPaint.setColor(mCurTextColor);
9235                     highlightPaint.setStyle(Paint.Style.STROKE);
9236                     highlight = mHighlightPath;
9237                 }
9238             } else {
9239                 if (mHighlightPathBogus) {
9240                     if (mHighlightPath == null) mHighlightPath = new Path();
9241                     mHighlightPath.reset();
9242                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
9243                     mHighlightPathBogus = false;
9244                 }
9245 
9246                 // XXX should pass to skin instead of drawing directly
9247                 highlightPaint.setColor(mHighlightColor);
9248                 highlightPaint.setStyle(Paint.Style.FILL);
9249 
9250                 highlight = mHighlightPath;
9251             }
9252         }
9253         return highlight;
9254     }
9255 
9256     /**
9257      * @hide
9258      */
getHorizontalOffsetForDrawables()9259     public int getHorizontalOffsetForDrawables() {
9260         return 0;
9261     }
9262 
9263     @Override
onDraw(Canvas canvas)9264     protected void onDraw(Canvas canvas) {
9265         restartMarqueeIfNeeded();
9266 
9267         // Draw the background for this view
9268         super.onDraw(canvas);
9269 
9270         final int compoundPaddingLeft = getCompoundPaddingLeft();
9271         final int compoundPaddingTop = getCompoundPaddingTop();
9272         final int compoundPaddingRight = getCompoundPaddingRight();
9273         final int compoundPaddingBottom = getCompoundPaddingBottom();
9274         final int scrollX = mScrollX;
9275         final int scrollY = mScrollY;
9276         final int right = mRight;
9277         final int left = mLeft;
9278         final int bottom = mBottom;
9279         final int top = mTop;
9280         final boolean isLayoutRtl = isLayoutRtl();
9281         final int offset = getHorizontalOffsetForDrawables();
9282         final int leftOffset = isLayoutRtl ? 0 : offset;
9283         final int rightOffset = isLayoutRtl ? offset : 0;
9284 
9285         final Drawables dr = mDrawables;
9286         if (dr != null) {
9287             /*
9288              * Compound, not extended, because the icon is not clipped
9289              * if the text height is smaller.
9290              */
9291 
9292             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
9293             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
9294 
9295             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9296             // Make sure to update invalidateDrawable() when changing this code.
9297             if (dr.mShowing[Drawables.LEFT] != null) {
9298                 canvas.save();
9299                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
9300                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
9301                 dr.mShowing[Drawables.LEFT].draw(canvas);
9302                 canvas.restore();
9303             }
9304 
9305             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9306             // Make sure to update invalidateDrawable() when changing this code.
9307             if (dr.mShowing[Drawables.RIGHT] != null) {
9308                 canvas.save();
9309                 canvas.translate(scrollX + right - left - mPaddingRight
9310                         - dr.mDrawableSizeRight - rightOffset,
9311                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
9312                 dr.mShowing[Drawables.RIGHT].draw(canvas);
9313                 canvas.restore();
9314             }
9315 
9316             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9317             // Make sure to update invalidateDrawable() when changing this code.
9318             if (dr.mShowing[Drawables.TOP] != null) {
9319                 canvas.save();
9320                 canvas.translate(scrollX + compoundPaddingLeft
9321                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
9322                 dr.mShowing[Drawables.TOP].draw(canvas);
9323                 canvas.restore();
9324             }
9325 
9326             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9327             // Make sure to update invalidateDrawable() when changing this code.
9328             if (dr.mShowing[Drawables.BOTTOM] != null) {
9329                 canvas.save();
9330                 canvas.translate(scrollX + compoundPaddingLeft
9331                         + (hspace - dr.mDrawableWidthBottom) / 2,
9332                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
9333                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
9334                 canvas.restore();
9335             }
9336         }
9337 
9338         int color = mCurTextColor;
9339 
9340         if (mLayout == null) {
9341             assumeLayout();
9342         }
9343 
9344         Layout layout = mLayout;
9345 
9346         if (mHint != null && !mHideHint && mText.length() == 0) {
9347             if (mHintTextColor != null) {
9348                 color = mCurHintTextColor;
9349             }
9350 
9351             layout = mHintLayout;
9352         }
9353 
9354         mTextPaint.setColor(color);
9355         mTextPaint.drawableState = getDrawableState();
9356 
9357         canvas.save();
9358         /*  Would be faster if we didn't have to do this. Can we chop the
9359             (displayable) text so that we don't need to do this ever?
9360         */
9361 
9362         int extendedPaddingTop = getExtendedPaddingTop();
9363         int extendedPaddingBottom = getExtendedPaddingBottom();
9364 
9365         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
9366         final int maxScrollY = mLayout.getHeight() - vspace;
9367 
9368         float clipLeft = compoundPaddingLeft + scrollX;
9369         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
9370         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
9371         float clipBottom = bottom - top + scrollY
9372                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
9373 
9374         if (mShadowRadius != 0) {
9375             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
9376             clipRight += Math.max(0, mShadowDx + mShadowRadius);
9377 
9378             clipTop += Math.min(0, mShadowDy - mShadowRadius);
9379             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
9380         }
9381 
9382         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
9383 
9384         int voffsetText = 0;
9385         int voffsetCursor = 0;
9386 
9387         // translate in by our padding
9388         /* shortcircuit calling getVerticaOffset() */
9389         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9390             voffsetText = getVerticalOffset(false);
9391             voffsetCursor = getVerticalOffset(true);
9392         }
9393         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
9394 
9395         final int layoutDirection = getLayoutDirection();
9396         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
9397         if (isMarqueeFadeEnabled()) {
9398             if (!mSingleLine && getLineCount() == 1 && canMarquee()
9399                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
9400                 final int width = mRight - mLeft;
9401                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
9402                 final float dx = mLayout.getLineRight(0) - (width - padding);
9403                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9404             }
9405 
9406             if (mMarquee != null && mMarquee.isRunning()) {
9407                 final float dx = -mMarquee.getScroll();
9408                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9409             }
9410         }
9411 
9412         final int cursorOffsetVertical = voffsetCursor - voffsetText;
9413 
9414         maybeUpdateHighlightPaths();
9415         // If there is a gesture preview highlight, then the selection or cursor is not drawn.
9416         Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
9417         if (mEditor != null) {
9418             mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
9419                     mHighlightPaint, cursorOffsetVertical);
9420         } else {
9421             layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
9422                     cursorOffsetVertical);
9423         }
9424 
9425         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
9426             final float dx = mMarquee.getGhostOffset();
9427             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9428             layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
9429                     cursorOffsetVertical);
9430         }
9431 
9432         canvas.restore();
9433     }
9434 
9435     @Override
getFocusedRect(Rect r)9436     public void getFocusedRect(Rect r) {
9437         if (mLayout == null) {
9438             super.getFocusedRect(r);
9439             return;
9440         }
9441 
9442         int selEnd = getSelectionEndTransformed();
9443         if (selEnd < 0) {
9444             super.getFocusedRect(r);
9445             return;
9446         }
9447 
9448         int selStart = getSelectionStartTransformed();
9449         if (selStart < 0 || selStart >= selEnd) {
9450             int line = mLayout.getLineForOffset(selEnd);
9451             r.top = mLayout.getLineTop(line);
9452             r.bottom = mLayout.getLineBottom(line);
9453             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
9454             r.right = r.left + 4;
9455         } else {
9456             int lineStart = mLayout.getLineForOffset(selStart);
9457             int lineEnd = mLayout.getLineForOffset(selEnd);
9458             r.top = mLayout.getLineTop(lineStart);
9459             r.bottom = mLayout.getLineBottom(lineEnd);
9460             if (lineStart == lineEnd) {
9461                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
9462                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
9463             } else {
9464                 // Selection extends across multiple lines -- make the focused
9465                 // rect cover the entire width.
9466                 if (mHighlightPathBogus) {
9467                     if (mHighlightPath == null) mHighlightPath = new Path();
9468                     mHighlightPath.reset();
9469                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
9470                     mHighlightPathBogus = false;
9471                 }
9472                 synchronized (TEMP_RECTF) {
9473                     mHighlightPath.computeBounds(TEMP_RECTF, true);
9474                     r.left = (int) TEMP_RECTF.left - 1;
9475                     r.right = (int) TEMP_RECTF.right + 1;
9476                 }
9477             }
9478         }
9479 
9480         // Adjust for padding and gravity.
9481         int paddingLeft = getCompoundPaddingLeft();
9482         int paddingTop = getExtendedPaddingTop();
9483         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9484             paddingTop += getVerticalOffset(false);
9485         }
9486         r.offset(paddingLeft, paddingTop);
9487         int paddingBottom = getExtendedPaddingBottom();
9488         r.bottom += paddingBottom;
9489     }
9490 
9491     /**
9492      * Return the number of lines of text, or 0 if the internal Layout has not
9493      * been built.
9494      */
getLineCount()9495     public int getLineCount() {
9496         return mLayout != null ? mLayout.getLineCount() : 0;
9497     }
9498 
9499     /**
9500      * Return the baseline for the specified line (0...getLineCount() - 1)
9501      * If bounds is not null, return the top, left, right, bottom extents
9502      * of the specified line in it. If the internal Layout has not been built,
9503      * return 0 and set bounds to (0, 0, 0, 0)
9504      * @param line which line to examine (0..getLineCount() - 1)
9505      * @param bounds Optional. If not null, it returns the extent of the line
9506      * @return the Y-coordinate of the baseline
9507      */
getLineBounds(int line, Rect bounds)9508     public int getLineBounds(int line, Rect bounds) {
9509         if (mLayout == null) {
9510             if (bounds != null) {
9511                 bounds.set(0, 0, 0, 0);
9512             }
9513             return 0;
9514         } else {
9515             int baseline = mLayout.getLineBounds(line, bounds);
9516 
9517             int voffset = getExtendedPaddingTop();
9518             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9519                 voffset += getVerticalOffset(true);
9520             }
9521             if (bounds != null) {
9522                 bounds.offset(getCompoundPaddingLeft(), voffset);
9523             }
9524             return baseline + voffset;
9525         }
9526     }
9527 
9528     @Override
getBaseline()9529     public int getBaseline() {
9530         if (mLayout == null) {
9531             return super.getBaseline();
9532         }
9533 
9534         return getBaselineOffset() + mLayout.getLineBaseline(0);
9535     }
9536 
getBaselineOffset()9537     int getBaselineOffset() {
9538         int voffset = 0;
9539         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9540             voffset = getVerticalOffset(true);
9541         }
9542 
9543         if (isLayoutModeOptical(mParent)) {
9544             voffset -= getOpticalInsets().top;
9545         }
9546 
9547         return getExtendedPaddingTop() + voffset;
9548     }
9549 
9550     /**
9551      * @hide
9552      */
9553     @Override
getFadeTop(boolean offsetRequired)9554     protected int getFadeTop(boolean offsetRequired) {
9555         if (mLayout == null) return 0;
9556 
9557         int voffset = 0;
9558         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9559             voffset = getVerticalOffset(true);
9560         }
9561 
9562         if (offsetRequired) voffset += getTopPaddingOffset();
9563 
9564         return getExtendedPaddingTop() + voffset;
9565     }
9566 
9567     /**
9568      * @hide
9569      */
9570     @Override
getFadeHeight(boolean offsetRequired)9571     protected int getFadeHeight(boolean offsetRequired) {
9572         return mLayout != null ? mLayout.getHeight() : 0;
9573     }
9574 
9575     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)9576     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
9577         if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
9578             if (mSpannable != null && mLinksClickable) {
9579                 final float x = event.getX(pointerIndex);
9580                 final float y = event.getY(pointerIndex);
9581                 final int offset = getOffsetForPosition(x, y);
9582                 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
9583                         ClickableSpan.class);
9584                 if (clickables.length > 0) {
9585                     return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
9586                 }
9587             }
9588             if (isTextSelectable() || isTextEditable()) {
9589                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
9590             }
9591         }
9592         return super.onResolvePointerIcon(event, pointerIndex);
9593     }
9594 
9595     @Override
onKeyPreIme(int keyCode, KeyEvent event)9596     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
9597         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
9598         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
9599         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
9600         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
9601             return true;
9602         }
9603         return super.onKeyPreIme(keyCode, event);
9604     }
9605 
9606     /**
9607      * @hide
9608      */
handleBackInTextActionModeIfNeeded(KeyEvent event)9609     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
9610         // Do nothing unless mEditor is in text action mode.
9611         if (mEditor == null || mEditor.getTextActionMode() == null) {
9612             return false;
9613         }
9614 
9615         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
9616             KeyEvent.DispatcherState state = getKeyDispatcherState();
9617             if (state != null) {
9618                 state.startTracking(event, this);
9619             }
9620             return true;
9621         } else if (event.getAction() == KeyEvent.ACTION_UP) {
9622             KeyEvent.DispatcherState state = getKeyDispatcherState();
9623             if (state != null) {
9624                 state.handleUpEvent(event);
9625             }
9626             if (event.isTracking() && !event.isCanceled()) {
9627                 stopTextActionMode();
9628                 return true;
9629             }
9630         }
9631         return false;
9632     }
9633 
9634     @Override
onKeyDown(int keyCode, KeyEvent event)9635     public boolean onKeyDown(int keyCode, KeyEvent event) {
9636         final int which = doKeyDown(keyCode, event, null);
9637         if (which == KEY_EVENT_NOT_HANDLED) {
9638             return super.onKeyDown(keyCode, event);
9639         }
9640 
9641         return true;
9642     }
9643 
9644     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)9645     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
9646         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
9647         final int which = doKeyDown(keyCode, down, event);
9648         if (which == KEY_EVENT_NOT_HANDLED) {
9649             // Go through default dispatching.
9650             return super.onKeyMultiple(keyCode, repeatCount, event);
9651         }
9652         if (which == KEY_EVENT_HANDLED) {
9653             // Consumed the whole thing.
9654             return true;
9655         }
9656 
9657         repeatCount--;
9658 
9659         // We are going to dispatch the remaining events to either the input
9660         // or movement method.  To do this, we will just send a repeated stream
9661         // of down and up events until we have done the complete repeatCount.
9662         // It would be nice if those interfaces had an onKeyMultiple() method,
9663         // but adding that is a more complicated change.
9664         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
9665         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
9666             // mEditor and mEditor.mInput are not null from doKeyDown
9667             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
9668             while (--repeatCount > 0) {
9669                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
9670                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
9671             }
9672             hideErrorIfUnchanged();
9673 
9674         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
9675             // mMovement is not null from doKeyDown
9676             mMovement.onKeyUp(this, mSpannable, keyCode, up);
9677             while (--repeatCount > 0) {
9678                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
9679                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
9680             }
9681         }
9682 
9683         return true;
9684     }
9685 
9686     /**
9687      * Returns true if pressing ENTER in this field advances focus instead
9688      * of inserting the character.  This is true mostly in single-line fields,
9689      * but also in mail addresses and subjects which will display on multiple
9690      * lines but where it doesn't make sense to insert newlines.
9691      */
shouldAdvanceFocusOnEnter()9692     private boolean shouldAdvanceFocusOnEnter() {
9693         if (getKeyListener() == null) {
9694             return false;
9695         }
9696 
9697         if (mSingleLine) {
9698             return true;
9699         }
9700 
9701         if (mEditor != null
9702                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
9703                         == EditorInfo.TYPE_CLASS_TEXT) {
9704             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9705             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
9706                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
9707                 return true;
9708             }
9709         }
9710 
9711         return false;
9712     }
9713 
isDirectionalNavigationKey(int keyCode)9714     private boolean isDirectionalNavigationKey(int keyCode) {
9715         switch(keyCode) {
9716             case KeyEvent.KEYCODE_DPAD_UP:
9717             case KeyEvent.KEYCODE_DPAD_DOWN:
9718             case KeyEvent.KEYCODE_DPAD_LEFT:
9719             case KeyEvent.KEYCODE_DPAD_RIGHT:
9720                 return true;
9721         }
9722         return false;
9723     }
9724 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)9725     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
9726         if (!isEnabled()) {
9727             return KEY_EVENT_NOT_HANDLED;
9728         }
9729 
9730         // If this is the initial keydown, we don't want to prevent a movement away from this view.
9731         // While this shouldn't be necessary because any time we're preventing default movement we
9732         // should be restricting the focus to remain within this view, thus we'll also receive
9733         // the key up event, occasionally key up events will get dropped and we don't want to
9734         // prevent the user from traversing out of this on the next key down.
9735         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
9736             mPreventDefaultMovement = false;
9737         }
9738 
9739         switch (keyCode) {
9740             case KeyEvent.KEYCODE_ENTER:
9741             case KeyEvent.KEYCODE_NUMPAD_ENTER:
9742                 if (event.hasNoModifiers()) {
9743                     // When mInputContentType is set, we know that we are
9744                     // running in a "modern" cupcake environment, so don't need
9745                     // to worry about the application trying to capture
9746                     // enter key events.
9747                     if (mEditor != null && mEditor.mInputContentType != null) {
9748                         // If there is an action listener, given them a
9749                         // chance to consume the event.
9750                         if (mEditor.mInputContentType.onEditorActionListener != null
9751                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
9752                                         this,
9753                                         getActionIdForEnterEvent(),
9754                                         event)) {
9755                             mEditor.mInputContentType.enterDown = true;
9756                             // We are consuming the enter key for them.
9757                             return KEY_EVENT_HANDLED;
9758                         }
9759                     }
9760 
9761                     // If our editor should move focus when enter is pressed, or
9762                     // this is a generated event from an IME action button, then
9763                     // don't let it be inserted into the text.
9764                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
9765                             || shouldAdvanceFocusOnEnter()) {
9766                         if (hasOnClickListeners()) {
9767                             return KEY_EVENT_NOT_HANDLED;
9768                         }
9769                         return KEY_EVENT_HANDLED;
9770                     }
9771                 }
9772                 break;
9773 
9774             case KeyEvent.KEYCODE_DPAD_CENTER:
9775                 if (event.hasNoModifiers()) {
9776                     if (shouldAdvanceFocusOnEnter()) {
9777                         return KEY_EVENT_NOT_HANDLED;
9778                     }
9779                 }
9780                 break;
9781 
9782             case KeyEvent.KEYCODE_TAB:
9783                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
9784                     // Tab is used to move focus.
9785                     return KEY_EVENT_NOT_HANDLED;
9786                 }
9787                 break;
9788 
9789                 // Has to be done on key down (and not on key up) to correctly be intercepted.
9790             case KeyEvent.KEYCODE_BACK:
9791                 if (mEditor != null && mEditor.getTextActionMode() != null) {
9792                     stopTextActionMode();
9793                     return KEY_EVENT_HANDLED;
9794                 }
9795                 break;
9796 
9797             case KeyEvent.KEYCODE_ESCAPE:
9798                 if (Flags.escapeClearsFocus() && event.hasNoModifiers()) {
9799                     if (mEditor != null && mEditor.getTextActionMode() != null) {
9800                         stopTextActionMode();
9801                         return KEY_EVENT_HANDLED;
9802                     }
9803                     if (hasFocus()) {
9804                         clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
9805                         InputMethodManager imm = getInputMethodManager();
9806                         if (imm != null) {
9807                             imm.hideSoftInputFromView(this, 0);
9808                         }
9809                         return KEY_EVENT_HANDLED;
9810                     }
9811                 }
9812                 break;
9813 
9814             case KeyEvent.KEYCODE_CUT:
9815                 if (event.hasNoModifiers() && canCut()) {
9816                     if (onTextContextMenuItem(ID_CUT)) {
9817                         return KEY_EVENT_HANDLED;
9818                     }
9819                 }
9820                 break;
9821 
9822             case KeyEvent.KEYCODE_COPY:
9823                 if (event.hasNoModifiers() && canCopy()) {
9824                     if (onTextContextMenuItem(ID_COPY)) {
9825                         return KEY_EVENT_HANDLED;
9826                     }
9827                 }
9828                 break;
9829 
9830             case KeyEvent.KEYCODE_PASTE:
9831                 if (event.hasNoModifiers() && canPaste()) {
9832                     if (onTextContextMenuItem(ID_PASTE)) {
9833                         return KEY_EVENT_HANDLED;
9834                     }
9835                 }
9836                 break;
9837 
9838             case KeyEvent.KEYCODE_FORWARD_DEL:
9839                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
9840                     if (onTextContextMenuItem(ID_CUT)) {
9841                         return KEY_EVENT_HANDLED;
9842                     }
9843                 }
9844                 break;
9845 
9846             case KeyEvent.KEYCODE_INSERT:
9847                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
9848                     if (onTextContextMenuItem(ID_COPY)) {
9849                         return KEY_EVENT_HANDLED;
9850                     }
9851                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
9852                     if (onTextContextMenuItem(ID_PASTE)) {
9853                         return KEY_EVENT_HANDLED;
9854                     }
9855                 }
9856                 break;
9857         }
9858 
9859         if (mEditor != null && mEditor.mKeyListener != null) {
9860             boolean doDown = true;
9861             if (otherEvent != null) {
9862                 try {
9863                     beginBatchEdit();
9864                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
9865                             otherEvent);
9866                     hideErrorIfUnchanged();
9867                     doDown = false;
9868                     if (handled) {
9869                         return KEY_EVENT_HANDLED;
9870                     }
9871                 } catch (AbstractMethodError e) {
9872                     // onKeyOther was added after 1.0, so if it isn't
9873                     // implemented we need to try to dispatch as a regular down.
9874                 } finally {
9875                     endBatchEdit();
9876                 }
9877             }
9878 
9879             if (doDown) {
9880                 beginBatchEdit();
9881                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
9882                         keyCode, event);
9883                 endBatchEdit();
9884                 hideErrorIfUnchanged();
9885                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
9886             }
9887         }
9888 
9889         // bug 650865: sometimes we get a key event before a layout.
9890         // don't try to move around if we don't know the layout.
9891 
9892         if (mMovement != null && mLayout != null) {
9893             boolean doDown = true;
9894             if (otherEvent != null) {
9895                 try {
9896                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
9897                     doDown = false;
9898                     if (handled) {
9899                         return KEY_EVENT_HANDLED;
9900                     }
9901                 } catch (AbstractMethodError e) {
9902                     // onKeyOther was added after 1.0, so if it isn't
9903                     // implemented we need to try to dispatch as a regular down.
9904                 }
9905             }
9906             if (doDown) {
9907                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
9908                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
9909                         mPreventDefaultMovement = true;
9910                     }
9911                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
9912                 }
9913             }
9914             // Consume arrows from keyboard devices to prevent focus leaving the editor.
9915             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
9916             // to move focus with arrows.
9917             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
9918                     && isDirectionalNavigationKey(keyCode)) {
9919                 return KEY_EVENT_HANDLED;
9920             }
9921         }
9922 
9923         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
9924                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
9925     }
9926 
9927     /**
9928      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
9929      * can be recorded.
9930      * @hide
9931      */
resetErrorChangedFlag()9932     public void resetErrorChangedFlag() {
9933         /*
9934          * Keep track of what the error was before doing the input
9935          * so that if an input filter changed the error, we leave
9936          * that error showing.  Otherwise, we take down whatever
9937          * error was showing when the user types something.
9938          */
9939         if (mEditor != null) mEditor.mErrorWasChanged = false;
9940     }
9941 
9942     /**
9943      * @hide
9944      */
hideErrorIfUnchanged()9945     public void hideErrorIfUnchanged() {
9946         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
9947             setError(null, null);
9948         }
9949     }
9950 
9951     @Override
onKeyUp(int keyCode, KeyEvent event)9952     public boolean onKeyUp(int keyCode, KeyEvent event) {
9953         if (!isEnabled()) {
9954             return super.onKeyUp(keyCode, event);
9955         }
9956 
9957         if (!KeyEvent.isModifierKey(keyCode)) {
9958             mPreventDefaultMovement = false;
9959         }
9960 
9961         switch (keyCode) {
9962             case KeyEvent.KEYCODE_DPAD_CENTER:
9963                 if (event.hasNoModifiers()) {
9964                     /*
9965                      * If there is a click listener, just call through to
9966                      * super, which will invoke it.
9967                      *
9968                      * If there isn't a click listener, try to show the soft
9969                      * input method.  (It will also
9970                      * call performClick(), but that won't do anything in
9971                      * this case.)
9972                      */
9973                     if (!hasOnClickListeners()) {
9974                         if (mMovement != null && mText instanceof Editable
9975                                 && mLayout != null && onCheckIsTextEditor()) {
9976                             InputMethodManager imm = getInputMethodManager();
9977                             viewClicked(imm);
9978                             if (imm != null && getShowSoftInputOnFocus()) {
9979                                 imm.showSoftInput(this, 0);
9980                             }
9981                         }
9982                     }
9983                 }
9984                 return super.onKeyUp(keyCode, event);
9985 
9986             case KeyEvent.KEYCODE_ENTER:
9987             case KeyEvent.KEYCODE_NUMPAD_ENTER:
9988                 if (event.hasNoModifiers()) {
9989                     if (mEditor != null && mEditor.mInputContentType != null
9990                             && mEditor.mInputContentType.onEditorActionListener != null
9991                             && mEditor.mInputContentType.enterDown) {
9992                         mEditor.mInputContentType.enterDown = false;
9993                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
9994                                 this, getActionIdForEnterEvent(), event)) {
9995                             return true;
9996                         }
9997                     }
9998 
9999                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
10000                             || shouldAdvanceFocusOnEnter()) {
10001                         /*
10002                          * If there is a click listener, just call through to
10003                          * super, which will invoke it.
10004                          *
10005                          * If there isn't a click listener, try to advance focus,
10006                          * but still call through to super, which will reset the
10007                          * pressed state and longpress state.  (It will also
10008                          * call performClick(), but that won't do anything in
10009                          * this case.)
10010                          */
10011                         if (!hasOnClickListeners()) {
10012                             View v = focusSearch(FOCUS_DOWN);
10013 
10014                             if (v != null) {
10015                                 if (!v.requestFocus(FOCUS_DOWN)) {
10016                                     throw new IllegalStateException("focus search returned a view "
10017                                             + "that wasn't able to take focus!");
10018                                 }
10019 
10020                                 /*
10021                                  * Return true because we handled the key; super
10022                                  * will return false because there was no click
10023                                  * listener.
10024                                  */
10025                                 super.onKeyUp(keyCode, event);
10026                                 return true;
10027                             } else if ((event.getFlags()
10028                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
10029                                 // No target for next focus, but make sure the IME
10030                                 // if this came from it.
10031                                 InputMethodManager imm = getInputMethodManager();
10032                                 if (imm != null) {
10033                                     imm.hideSoftInputFromView(this, 0);
10034                                 }
10035                             }
10036                         }
10037                     }
10038                     return super.onKeyUp(keyCode, event);
10039                 }
10040                 break;
10041         }
10042 
10043         if (mEditor != null && mEditor.mKeyListener != null) {
10044             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
10045                 return true;
10046             }
10047         }
10048 
10049         if (mMovement != null && mLayout != null) {
10050             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
10051                 return true;
10052             }
10053         }
10054 
10055         return super.onKeyUp(keyCode, event);
10056     }
10057 
getActionIdForEnterEvent()10058     private int getActionIdForEnterEvent() {
10059         // If it's not single line, no action
10060         if (!isSingleLine()) {
10061             return EditorInfo.IME_NULL;
10062         }
10063         // Return the action that was specified for Enter
10064         return getImeOptions() & EditorInfo.IME_MASK_ACTION;
10065     }
10066 
10067     @Override
onCheckIsTextEditor()10068     public boolean onCheckIsTextEditor() {
10069         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
10070     }
10071 
hasEditorInFocusSearchDirection(@ocusRealDirection int direction)10072     private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) {
10073         final View nextView = focusSearch(direction);
10074         return nextView != null && nextView.onCheckIsTextEditor();
10075     }
10076 
10077     @Override
onCreateInputConnection(EditorInfo outAttrs)10078     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
10079         if (onCheckIsTextEditor() && isEnabled()) {
10080             mEditor.createInputMethodStateIfNeeded();
10081             mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = 0;
10082             mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = 0;
10083 
10084             outAttrs.inputType = getInputType();
10085             if (mEditor.mInputContentType != null) {
10086                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
10087                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
10088                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
10089                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
10090                 outAttrs.extras = mEditor.mInputContentType.extras;
10091                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
10092             } else {
10093                 outAttrs.imeOptions = EditorInfo.IME_NULL;
10094                 outAttrs.hintLocales = null;
10095             }
10096             if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) {
10097                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
10098             }
10099             if (hasEditorInFocusSearchDirection(FOCUS_UP)) {
10100                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
10101             }
10102             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
10103                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
10104                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
10105                     // An action has not been set, but the enter key will move to
10106                     // the next focus, so set the action to that.
10107                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
10108                 } else {
10109                     // An action has not been set, and there is no focus to move
10110                     // to, so let's just supply a "done" action.
10111                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
10112                 }
10113                 if (!shouldAdvanceFocusOnEnter()) {
10114                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
10115                 }
10116             }
10117             if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) {
10118                 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT;
10119             }
10120             if (isMultilineInputType(outAttrs.inputType)) {
10121                 // Multi-line text editors should always show an enter key.
10122                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
10123             }
10124             outAttrs.hintText = mHint;
10125             outAttrs.targetInputMethodUser = mTextOperationUser;
10126             if (mText instanceof Editable) {
10127                 InputConnection ic = new EditableInputConnection(this);
10128                 outAttrs.initialSelStart = getSelectionStart();
10129                 outAttrs.initialSelEnd = getSelectionEnd();
10130                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
10131                 outAttrs.setInitialSurroundingText(mText);
10132                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
10133                 if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) {
10134                     boolean handwritingEnabled = isAutoHandwritingEnabled();
10135                     outAttrs.setStylusHandwritingEnabled(handwritingEnabled);
10136                     // AndroidX Core library 1.13.0 introduced
10137                     // EditorInfoCompat#setStylusHandwritingEnabled and
10138                     // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the
10139                     // EditorInfo extras bundle. These methods do not set or check the Android V
10140                     // property since the Android V SDK was not yet available. In order for
10141                     // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for
10142                     // EditorInfo created by Android V TextView, the extras bundle value is also set
10143                     // here.
10144                     if (outAttrs.extras == null) {
10145                         outAttrs.extras = new Bundle();
10146                     }
10147                     outAttrs.extras.putBoolean(
10148                             STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled);
10149                 }
10150                 if (android.view.inputmethod.Flags.writingTools()) {
10151                     // default to same behavior as isSuggestionsEnabled().
10152                     outAttrs.setWritingToolsEnabled(isSuggestionsEnabled());
10153                 }
10154                 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
10155                 gestures.add(SelectGesture.class);
10156                 gestures.add(SelectRangeGesture.class);
10157                 gestures.add(DeleteGesture.class);
10158                 gestures.add(DeleteRangeGesture.class);
10159                 gestures.add(InsertGesture.class);
10160                 gestures.add(RemoveSpaceGesture.class);
10161                 gestures.add(JoinOrSplitGesture.class);
10162                 gestures.add(InsertModeGesture.class);
10163                 outAttrs.setSupportedHandwritingGestures(gestures);
10164 
10165                 Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>();
10166                 previews.add(SelectGesture.class);
10167                 previews.add(SelectRangeGesture.class);
10168                 previews.add(DeleteGesture.class);
10169                 previews.add(DeleteRangeGesture.class);
10170                 outAttrs.setSupportedHandwritingGesturePreviews(previews);
10171 
10172                 return ic;
10173             }
10174         }
10175         return null;
10176     }
10177 
10178     /**
10179      * Called back by the system to handle {@link InputConnection#requestCursorUpdates(int, int)}.
10180      *
10181      * @param cursorUpdateMode modes defined in {@link InputConnection.CursorUpdateMode}.
10182      * @param cursorUpdateFilter modes defined in {@link InputConnection.CursorUpdateFilter}.
10183      *
10184      * @hide
10185      */
onRequestCursorUpdatesInternal( @nputConnection.CursorUpdateMode int cursorUpdateMode, @InputConnection.CursorUpdateFilter int cursorUpdateFilter)10186     public void onRequestCursorUpdatesInternal(
10187             @InputConnection.CursorUpdateMode int cursorUpdateMode,
10188             @InputConnection.CursorUpdateFilter int cursorUpdateFilter) {
10189         mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = cursorUpdateMode;
10190         mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = cursorUpdateFilter;
10191         if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) == 0) {
10192             return;
10193         }
10194         if (isInLayout()) {
10195             // In this case, the view hierarchy is currently undergoing a layout pass.
10196             // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
10197             // pass is finished.
10198         } else {
10199             // This will schedule a layout pass of the view tree, and the layout event
10200             // eventually triggers IMM#updateCursorAnchorInfo.
10201             requestLayout();
10202         }
10203     }
10204 
10205     /**
10206      * If this TextView contains editable content, extract a portion of it
10207      * based on the information in <var>request</var> in to <var>outText</var>.
10208      * @return Returns true if the text was successfully extracted, else false.
10209      */
extractText(ExtractedTextRequest request, ExtractedText outText)10210     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
10211         createEditorIfNeeded();
10212         return mEditor.extractText(request, outText);
10213     }
10214 
10215     /**
10216      * This is used to remove all style-impacting spans from text before new
10217      * extracted text is being replaced into it, so that we don't have any
10218      * lingering spans applied during the replace.
10219      */
removeParcelableSpans(Spannable spannable, int start, int end)10220     static void removeParcelableSpans(Spannable spannable, int start, int end) {
10221         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
10222         int i = spans.length;
10223         while (i > 0) {
10224             i--;
10225             spannable.removeSpan(spans[i]);
10226         }
10227     }
10228 
10229     /**
10230      * Apply to this text view the given extracted text, as previously
10231      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
10232      */
setExtractedText(ExtractedText text)10233     public void setExtractedText(ExtractedText text) {
10234         Editable content = getEditableText();
10235         if (text.text != null) {
10236             if (content == null) {
10237                 setText(text.text, TextView.BufferType.EDITABLE);
10238             } else {
10239                 int start = 0;
10240                 int end = content.length();
10241 
10242                 if (text.partialStartOffset >= 0) {
10243                     final int N = content.length();
10244                     start = text.partialStartOffset;
10245                     if (start > N) start = N;
10246                     end = text.partialEndOffset;
10247                     if (end > N) end = N;
10248                 }
10249 
10250                 removeParcelableSpans(content, start, end);
10251                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
10252                     if (text.text instanceof Spanned) {
10253                         // OK to copy spans only.
10254                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
10255                                 Object.class, content, start);
10256                     }
10257                 } else {
10258                     content.replace(start, end, text.text);
10259                 }
10260             }
10261         }
10262 
10263         // Now set the selection position...  make sure it is in range, to
10264         // avoid crashes.  If this is a partial update, it is possible that
10265         // the underlying text may have changed, causing us problems here.
10266         // Also we just don't want to trust clients to do the right thing.
10267         Spannable sp = (Spannable) getText();
10268         final int N = sp.length();
10269         int start = text.selectionStart;
10270         if (start < 0) {
10271             start = 0;
10272         } else if (start > N) {
10273             start = N;
10274         }
10275         int end = text.selectionEnd;
10276         if (end < 0) {
10277             end = 0;
10278         } else if (end > N) {
10279             end = N;
10280         }
10281         Selection.setSelection(sp, start, end);
10282 
10283         // Finally, update the selection mode.
10284         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
10285             MetaKeyKeyListener.startSelecting(this, sp);
10286         } else {
10287             MetaKeyKeyListener.stopSelecting(this, sp);
10288         }
10289 
10290         setHintInternal(text.hint);
10291     }
10292 
10293     /**
10294      * @hide
10295      */
setExtracting(ExtractedTextRequest req)10296     public void setExtracting(ExtractedTextRequest req) {
10297         if (mEditor.mInputMethodState != null) {
10298             mEditor.mInputMethodState.mExtractedTextRequest = req;
10299         }
10300         // This would stop a possible selection mode, but no such mode is started in case
10301         // extracted mode will start. Some text is selected though, and will trigger an action mode
10302         // in the extracted view.
10303         mEditor.hideCursorAndSpanControllers();
10304         stopTextActionMode();
10305         if (mEditor.mSelectionModifierCursorController != null) {
10306             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
10307         }
10308     }
10309 
10310     /**
10311      * Called by the framework in response to a text completion from
10312      * the current input method, provided by it calling
10313      * {@link InputConnection#commitCompletion
10314      * InputConnection.commitCompletion()}.  The default implementation does
10315      * nothing; text views that are supporting auto-completion should override
10316      * this to do their desired behavior.
10317      *
10318      * @param text The auto complete text the user has selected.
10319      */
onCommitCompletion(CompletionInfo text)10320     public void onCommitCompletion(CompletionInfo text) {
10321         // intentionally empty
10322     }
10323 
10324     /**
10325      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
10326      * dictionary) from the current input method, provided by it calling
10327      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
10328      * The default implementation flashes the background of the corrected word to provide
10329      * feedback to the user.
10330      *
10331      * @param info The auto correct info about the text that was corrected.
10332      */
onCommitCorrection(CorrectionInfo info)10333     public void onCommitCorrection(CorrectionInfo info) {
10334         if (mEditor != null) mEditor.onCommitCorrection(info);
10335     }
10336 
beginBatchEdit()10337     public void beginBatchEdit() {
10338         if (mEditor != null) mEditor.beginBatchEdit();
10339     }
10340 
endBatchEdit()10341     public void endBatchEdit() {
10342         if (mEditor != null) mEditor.endBatchEdit();
10343     }
10344 
10345     /**
10346      * Called by the framework in response to a request to begin a batch
10347      * of edit operations through a call to link {@link #beginBatchEdit()}.
10348      */
onBeginBatchEdit()10349     public void onBeginBatchEdit() {
10350         // intentionally empty
10351     }
10352 
10353     /**
10354      * Called by the framework in response to a request to end a batch
10355      * of edit operations through a call to link {@link #endBatchEdit}.
10356      */
onEndBatchEdit()10357     public void onEndBatchEdit() {
10358         // intentionally empty
10359     }
10360 
10361     /** @hide */
onPerformSpellCheck()10362     public void onPerformSpellCheck() {
10363         if (mEditor != null && mEditor.mSpellChecker != null) {
10364             mEditor.mSpellChecker.onPerformSpellCheck();
10365         }
10366     }
10367 
10368     /**
10369      * Called by the framework in response to a private command from the
10370      * current method, provided by it calling
10371      * {@link InputConnection#performPrivateCommand
10372      * InputConnection.performPrivateCommand()}.
10373      *
10374      * @param action The action name of the command.
10375      * @param data Any additional data for the command.  This may be null.
10376      * @return Return true if you handled the command, else false.
10377      */
onPrivateIMECommand(String action, Bundle data)10378     public boolean onPrivateIMECommand(String action, Bundle data) {
10379         return false;
10380     }
10381 
10382     /**
10383      * Return whether the text is transformed and has {@link OffsetMapping}.
10384      * @hide
10385      */
isOffsetMappingAvailable()10386     public boolean isOffsetMappingAvailable() {
10387         return mTransformation != null && mTransformed instanceof OffsetMapping;
10388     }
10389 
10390     /** @hide */
previewHandwritingGesture( @onNull PreviewableHandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal)10391     public boolean previewHandwritingGesture(
10392             @NonNull PreviewableHandwritingGesture gesture,
10393             @Nullable CancellationSignal cancellationSignal) {
10394         if (gesture instanceof SelectGesture) {
10395             performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true);
10396         } else if (gesture instanceof SelectRangeGesture) {
10397             performHandwritingSelectRangeGesture(
10398                     (SelectRangeGesture) gesture, /* isPreview= */ true);
10399         } else if (gesture instanceof DeleteGesture) {
10400             performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true);
10401         } else if (gesture instanceof DeleteRangeGesture) {
10402             performHandwritingDeleteRangeGesture(
10403                     (DeleteRangeGesture) gesture, /* isPreview= */ true);
10404         } else {
10405             return false;
10406         }
10407         if (cancellationSignal != null) {
10408             cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight);
10409         }
10410         return true;
10411     }
10412 
10413     /** @hide */
performHandwritingSelectGesture(@onNull SelectGesture gesture)10414     public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
10415         return performHandwritingSelectGesture(gesture, /* isPreview= */ false);
10416     }
10417 
performHandwritingSelectGesture(@onNull SelectGesture gesture, boolean isPreview)10418     private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) {
10419         if (isOffsetMappingAvailable()) {
10420             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10421         }
10422         int[] range = getRangeForRect(
10423                 convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
10424                 gesture.getGranularity());
10425         if (range == null) {
10426             return handleGestureFailure(gesture, isPreview);
10427         }
10428         return performHandwritingSelectGesture(range, isPreview);
10429     }
10430 
performHandwritingSelectGesture(int[] range, boolean isPreview)10431     private int performHandwritingSelectGesture(int[] range, boolean isPreview) {
10432         if (isPreview) {
10433             setSelectGesturePreviewHighlight(range[0], range[1]);
10434         } else {
10435             Selection.setSelection(getEditableText(), range[0], range[1]);
10436             mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
10437         }
10438         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10439     }
10440 
10441     /** @hide */
performHandwritingSelectRangeGesture(@onNull SelectRangeGesture gesture)10442     public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) {
10443         return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false);
10444     }
10445 
performHandwritingSelectRangeGesture( @onNull SelectRangeGesture gesture, boolean isPreview)10446     private int performHandwritingSelectRangeGesture(
10447             @NonNull SelectRangeGesture gesture, boolean isPreview) {
10448         if (isOffsetMappingAvailable()) {
10449             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10450         }
10451         int[] startRange = getRangeForRect(
10452                 convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
10453                 gesture.getGranularity());
10454         if (startRange == null) {
10455             return handleGestureFailure(gesture, isPreview);
10456         }
10457         int[] endRange = getRangeForRect(
10458                 convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()),
10459                 gesture.getGranularity());
10460         if (endRange == null) {
10461             return handleGestureFailure(gesture, isPreview);
10462         }
10463         int[] range = new int[] {
10464                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
10465         };
10466         return performHandwritingSelectGesture(range, isPreview);
10467     }
10468 
10469     /** @hide */
performHandwritingDeleteGesture(@onNull DeleteGesture gesture)10470     public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
10471         return performHandwritingDeleteGesture(gesture, /* isPreview= */ false);
10472     }
10473 
performHandwritingDeleteGesture(@onNull DeleteGesture gesture, boolean isPreview)10474     private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) {
10475         if (isOffsetMappingAvailable()) {
10476             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10477         }
10478         int[] range = getRangeForRect(
10479                 convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
10480                 gesture.getGranularity());
10481         if (range == null) {
10482             return handleGestureFailure(gesture, isPreview);
10483         }
10484         return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
10485     }
10486 
performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview)10487     private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) {
10488         if (isPreview) {
10489             setDeleteGesturePreviewHighlight(range[0], range[1]);
10490         } else {
10491             if (granularity == HandwritingGesture.GRANULARITY_WORD) {
10492                 range = adjustHandwritingDeleteGestureRange(range);
10493             }
10494 
10495             Selection.setSelection(getEditableText(), range[0]);
10496             getEditableText().delete(range[0], range[1]);
10497         }
10498         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10499     }
10500 
10501     /** @hide */
performHandwritingDeleteRangeGesture(@onNull DeleteRangeGesture gesture)10502     public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) {
10503         return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false);
10504     }
10505 
performHandwritingDeleteRangeGesture( @onNull DeleteRangeGesture gesture, boolean isPreview)10506     private int performHandwritingDeleteRangeGesture(
10507             @NonNull DeleteRangeGesture gesture, boolean isPreview) {
10508         if (isOffsetMappingAvailable()) {
10509             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10510         }
10511         int[] startRange = getRangeForRect(
10512                 convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
10513                 gesture.getGranularity());
10514         if (startRange == null) {
10515             return handleGestureFailure(gesture, isPreview);
10516         }
10517         int[] endRange = getRangeForRect(
10518                 convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()),
10519                 gesture.getGranularity());
10520         if (endRange == null) {
10521             return handleGestureFailure(gesture, isPreview);
10522         }
10523         int[] range = new int[] {
10524                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
10525         };
10526         return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
10527     }
10528 
adjustHandwritingDeleteGestureRange(int[] range)10529     private int[] adjustHandwritingDeleteGestureRange(int[] range) {
10530         // For handwriting delete gestures with word granularity, adjust the start and end offsets
10531         // to remove extra whitespace around the deleted text.
10532 
10533         int start = range[0];
10534         int end = range[1];
10535 
10536         // If the deleted text is at the start of the text, the behavior is the same as the case
10537         // where the deleted text follows a new line character.
10538         int codePointBeforeStart = start > 0
10539                 ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT;
10540         // If the deleted text is at the end of the text, the behavior is the same as the case where
10541         // the deleted text precedes a new line character.
10542         int codePointAtEnd = end < mText.length()
10543                 ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT;
10544 
10545         if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)
10546                 && (TextUtils.isWhitespace(codePointAtEnd)
10547                         || TextUtils.isPunctuation(codePointAtEnd))) {
10548             // Remove whitespace (except new lines) before the deleted text, in these cases:
10549             // - There is whitespace following the deleted text
10550             //     e.g. "one [deleted] three" -> "one | three" -> "one| three"
10551             // - There is punctuation following the deleted text
10552             //     e.g. "one [deleted]!" -> "one |!" -> "one|!"
10553             // - There is a new line following the deleted text
10554             //     e.g. "one [deleted]\n" -> "one |\n" -> "one|\n"
10555             // - The deleted text is at the end of the text
10556             //     e.g. "one [deleted]" -> "one |" -> "one|"
10557             // (The pipe | indicates the cursor position.)
10558             do {
10559                 start -= Character.charCount(codePointBeforeStart);
10560                 if (start == 0) break;
10561                 codePointBeforeStart = Character.codePointBefore(mText, start);
10562             } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart));
10563             return new int[] {start, end};
10564         }
10565 
10566         if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)
10567                 && (TextUtils.isWhitespace(codePointBeforeStart)
10568                         || TextUtils.isPunctuation(codePointBeforeStart))) {
10569             // Remove whitespace (except new lines) after the deleted text, in these cases:
10570             // - There is punctuation preceding the deleted text
10571             //     e.g. "([deleted] two)" -> "(| two)" -> "(|two)"
10572             // - There is a new line preceding the deleted text
10573             //     e.g. "\n[deleted] two" -> "\n| two" -> "\n|two"
10574             // - The deleted text is at the start of the text
10575             //     e.g. "[deleted] two" -> "| two" -> "|two"
10576             // (The pipe | indicates the cursor position.)
10577             do {
10578                 end += Character.charCount(codePointAtEnd);
10579                 if (end == mText.length()) break;
10580                 codePointAtEnd = Character.codePointAt(mText, end);
10581             } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd));
10582             return new int[] {start, end};
10583         }
10584 
10585         // Return the original range.
10586         return range;
10587     }
10588 
10589     /** @hide */
10590     public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
10591         if (isOffsetMappingAvailable()) {
10592             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10593         }
10594         PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
10595         int line = getLineForHandwritingGesture(point);
10596         if (line == -1) {
10597             return handleGestureFailure(gesture);
10598         }
10599         int offset = mLayout.getOffsetForHorizontal(line, point.x);
10600         String textToInsert = gesture.getTextToInsert();
10601         return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture);
10602         // TODO(b/243980426): Insert extra spaces if necessary.
10603     }
10604 
10605     /** @hide */
10606     public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) {
10607         if (isOffsetMappingAvailable()) {
10608             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10609         }
10610         PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());
10611         PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint());
10612 
10613         // The operation should be applied to the first line of text containing one of the points.
10614         int startPointLine = getLineForHandwritingGesture(startPoint);
10615         int endPointLine = getLineForHandwritingGesture(endPoint);
10616         int line;
10617         if (startPointLine == -1) {
10618             if (endPointLine == -1) {
10619                 return handleGestureFailure(gesture);
10620             }
10621             line = endPointLine;
10622         } else {
10623             line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine);
10624         }
10625 
10626         // The operation should be applied to all characters touched by the line joining the points.
10627         float lineVerticalCenter = (mLayout.getLineTop(line)
10628                 + mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) / 2f;
10629         // Create a rectangle which is +/-0.1f around the line's vertical center, so that the
10630         // rectangle doesn't touch the line above or below. (The line height is at least 1f.)
10631         RectF area = new RectF(
10632                 Math.min(startPoint.x, endPoint.x),
10633                 lineVerticalCenter + 0.1f,
10634                 Math.max(startPoint.x, endPoint.x),
10635                 lineVerticalCenter - 0.1f);
10636         int[] range = mLayout.getRangeForRect(
10637                 area, new GraphemeClusterSegmentFinder(mText, mTextPaint),
10638                 Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
10639         if (range == null) {
10640             return handleGestureFailure(gesture);
10641         }
10642         int startOffset = range[0];
10643         int endOffset = range[1];
10644         // TODO(b/247557062): This doesn't handle bidirectional text correctly.
10645 
10646         Pattern whitespacePattern = getWhitespacePattern();
10647         Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
10648         int lastRemoveOffset = -1;
10649         while (matcher.find()) {
10650             lastRemoveOffset = startOffset + matcher.start();
10651             getEditableText().delete(lastRemoveOffset, startOffset + matcher.end());
10652             startOffset = lastRemoveOffset;
10653             endOffset -= matcher.end() - matcher.start();
10654             if (startOffset == endOffset) {
10655                 break;
10656             }
10657             matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
10658         }
10659         if (lastRemoveOffset == -1) {
10660             return handleGestureFailure(gesture);
10661         }
10662         Selection.setSelection(getEditableText(), lastRemoveOffset);
10663         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10664     }
10665 
10666     /** @hide */
10667     public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {
10668         if (isOffsetMappingAvailable()) {
10669             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10670         }
10671         PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint());
10672 
10673         int line = getLineForHandwritingGesture(point);
10674         if (line == -1) {
10675             return handleGestureFailure(gesture);
10676         }
10677 
10678         int startOffset = mLayout.getOffsetForHorizontal(line, point.x);
10679         if (mLayout.isLevelBoundary(startOffset)) {
10680             // Gesture at level boundaries is not supported.
10681             return handleGestureFailure(gesture);
10682         }
10683 
10684         int endOffset = startOffset;
10685         while (startOffset > 0) {
10686             int codePointBeforeStart = Character.codePointBefore(mText, startOffset);
10687             if (!TextUtils.isWhitespace(codePointBeforeStart)) {
10688                 break;
10689             }
10690             startOffset -= Character.charCount(codePointBeforeStart);
10691         }
10692         while (endOffset < mText.length()) {
10693             int codePointAtEnd = Character.codePointAt(mText, endOffset);
10694             if (!TextUtils.isWhitespace(codePointAtEnd)) {
10695                 break;
10696             }
10697             endOffset += Character.charCount(codePointAtEnd);
10698         }
10699         if (startOffset < endOffset) {
10700             Selection.setSelection(getEditableText(), startOffset);
10701             getEditableText().delete(startOffset, endOffset);
10702             return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10703         } else {
10704             // No whitespace found, so insert a space.
10705             return tryInsertTextForHandwritingGesture(startOffset, " ", gesture);
10706         }
10707     }
10708 
10709     /** @hide */
10710     public int performHandwritingInsertModeGesture(@NonNull InsertModeGesture gesture) {
10711         final PointF insertPoint =
10712                 convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
10713         final int line = getLineForHandwritingGesture(insertPoint);
10714         final CancellationSignal cancellationSignal = gesture.getCancellationSignal();
10715 
10716         // If no cancellationSignal is provided, don't enter the insert mode.
10717         if (line == -1 || cancellationSignal == null) {
10718             return handleGestureFailure(gesture);
10719         }
10720 
10721         final int offset = mLayout.getOffsetForHorizontal(line, insertPoint.x);
10722 
10723         if (!mEditor.enterInsertMode(offset)) {
10724             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10725         }
10726         cancellationSignal.setOnCancelListener(() -> mEditor.exitInsertMode());
10727         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10728     }
10729 
10730     private int handleGestureFailure(HandwritingGesture gesture) {
10731         return handleGestureFailure(gesture, /* isPreview= */ false);
10732     }
10733 
10734     private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) {
10735         clearGesturePreviewHighlight();
10736         if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) {
10737             getEditableText()
10738                     .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText());
10739             return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK;
10740         }
10741         return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10742     }
10743 
10744     /**
10745      * Returns the closest line such that the point is either inside the line bounds or within
10746      * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns
10747      * -1 if the point is not within the margin of any line bounds.
10748      */
10749     private int getLineForHandwritingGesture(PointF point) {
10750         int line = mLayout.getLineForVertical((int) point.y);
10751         int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin();
10752         if (line < mLayout.getLineCount() - 1
10753                 && point.y > mLayout.getLineBottom(line) - lineMargin
10754                 && point.y
10755                         > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) {
10756             // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical
10757             // returns i. If the point is within lineMargin of line (i + 1), and closer to line
10758             // (i + 1) than line i, then the gesture operation should be applied to line (i + 1).
10759             line++;
10760         } else if (point.y < mLayout.getLineTop(line) - lineMargin
10761                 || point.y
10762                         > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)
10763                                 + lineMargin) {
10764             // The point is not within lineMargin of a line.
10765             return -1;
10766         }
10767         if (point.x < -lineMargin || point.x > mLayout.getWidth() + lineMargin) {
10768             // The point is not within lineMargin of a line.
10769             return -1;
10770         }
10771         return line;
10772     }
10773 
10774     @Nullable
10775     private int[] getRangeForRect(@NonNull RectF area, int granularity) {
10776         SegmentFinder segmentFinder;
10777         if (granularity == HandwritingGesture.GRANULARITY_WORD) {
10778             WordIterator wordIterator = getWordIterator();
10779             wordIterator.setCharSequence(mText, 0, mText.length());
10780             segmentFinder = new WordSegmentFinder(mText, wordIterator);
10781         } else {
10782             segmentFinder = new GraphemeClusterSegmentFinder(mText, mTextPaint);
10783         }
10784 
10785         return mLayout.getRangeForRect(
10786                 area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
10787     }
10788 
10789     private int tryInsertTextForHandwritingGesture(
10790             int offset, String textToInsert, HandwritingGesture gesture) {
10791         // A temporary cursor span is placed at the insertion offset. The span will be pushed
10792         // forward when text is inserted, then the real cursor can be placed after the inserted
10793         // text. A temporary cursor span is used in order to avoid modifying the real selection span
10794         // in the case that the text is filtered out.
10795         Editable editableText = getEditableText();
10796         if (mTempCursor == null) {
10797             mTempCursor = new NoCopySpan.Concrete();
10798         }
10799         editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT);
10800 
10801         editableText.insert(offset, textToInsert);
10802 
10803         int newOffset = editableText.getSpanStart(mTempCursor);
10804         editableText.removeSpan(mTempCursor);
10805         if (newOffset == offset) {
10806             // The inserted text was filtered out.
10807             return handleGestureFailure(gesture);
10808         } else {
10809             // Place the cursor after the inserted text.
10810             Selection.setSelection(editableText, newOffset);
10811             return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10812         }
10813     }
10814 
10815     private Pattern getWhitespacePattern() {
10816         if (mWhitespacePattern == null) {
10817             mWhitespacePattern = Pattern.compile("\\s+");
10818         }
10819         return mWhitespacePattern;
10820     }
10821 
10822     /** @hide */
10823     @VisibleForTesting
10824     @UnsupportedAppUsage
10825     public void nullLayouts() {
10826         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
10827             mSavedLayout = (BoringLayout) mLayout;
10828         }
10829         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
10830             mSavedHintLayout = (BoringLayout) mHintLayout;
10831         }
10832 
10833         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
10834 
10835         mBoring = mHintBoring = null;
10836 
10837         // Since it depends on the value of mLayout
10838         if (mEditor != null) mEditor.prepareCursorControllers();
10839     }
10840 
10841     /**
10842      * Make a new Layout based on the already-measured size of the view,
10843      * on the assumption that it was measured correctly at some point.
10844      */
10845     @UnsupportedAppUsage
10846     private void assumeLayout() {
10847         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10848 
10849         if (width < 1) {
10850             width = 0;
10851         }
10852 
10853         int physicalWidth = width;
10854 
10855         if (mHorizontallyScrolling) {
10856             width = VERY_WIDE;
10857         }
10858 
10859         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
10860                       physicalWidth, false);
10861     }
10862 
10863     @UnsupportedAppUsage
10864     private Layout.Alignment getLayoutAlignment() {
10865         Layout.Alignment alignment;
10866         switch (getTextAlignment()) {
10867             case TEXT_ALIGNMENT_GRAVITY:
10868                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
10869                     case Gravity.START:
10870                         alignment = Layout.Alignment.ALIGN_NORMAL;
10871                         break;
10872                     case Gravity.END:
10873                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
10874                         break;
10875                     case Gravity.LEFT:
10876                         alignment = Layout.Alignment.ALIGN_LEFT;
10877                         break;
10878                     case Gravity.RIGHT:
10879                         alignment = Layout.Alignment.ALIGN_RIGHT;
10880                         break;
10881                     case Gravity.CENTER_HORIZONTAL:
10882                         alignment = Layout.Alignment.ALIGN_CENTER;
10883                         break;
10884                     default:
10885                         alignment = Layout.Alignment.ALIGN_NORMAL;
10886                         break;
10887                 }
10888                 break;
10889             case TEXT_ALIGNMENT_TEXT_START:
10890                 alignment = Layout.Alignment.ALIGN_NORMAL;
10891                 break;
10892             case TEXT_ALIGNMENT_TEXT_END:
10893                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
10894                 break;
10895             case TEXT_ALIGNMENT_CENTER:
10896                 alignment = Layout.Alignment.ALIGN_CENTER;
10897                 break;
10898             case TEXT_ALIGNMENT_VIEW_START:
10899                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
10900                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
10901                 break;
10902             case TEXT_ALIGNMENT_VIEW_END:
10903                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
10904                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
10905                 break;
10906             case TEXT_ALIGNMENT_INHERIT:
10907                 // This should never happen as we have already resolved the text alignment
10908                 // but better safe than sorry so we just fall through
10909             default:
10910                 alignment = Layout.Alignment.ALIGN_NORMAL;
10911                 break;
10912         }
10913         return alignment;
10914     }
10915 
10916     private Paint.FontMetrics getResolvedMinimumFontMetrics() {
10917         if (mMinimumFontMetrics != null) {
10918             return mMinimumFontMetrics;
10919         }
10920         if (!mUseLocalePreferredLineHeightForMinimum) {
10921             return null;
10922         }
10923 
10924         if (mLocalePreferredFontMetrics == null) {
10925             mLocalePreferredFontMetrics = new Paint.FontMetrics();
10926         }
10927         mTextPaint.getFontMetricsForLocale(mLocalePreferredFontMetrics);
10928         return mLocalePreferredFontMetrics;
10929     }
10930 
10931     /**
10932      * The width passed in is now the desired layout width,
10933      * not the full view width with padding.
10934      * {@hide}
10935      */
10936     @VisibleForTesting
10937     @UnsupportedAppUsage
10938     public void makeNewLayout(int wantWidth, int hintWidth,
10939                                  BoringLayout.Metrics boring,
10940                                  BoringLayout.Metrics hintBoring,
10941                                  int ellipsisWidth, boolean bringIntoView) {
10942         stopMarquee();
10943 
10944         // Update "old" cached values
10945         mOldMaximum = mMaximum;
10946         mOldMaxMode = mMaxMode;
10947 
10948         mHighlightPathBogus = true;
10949         mHighlightPathsBogus = true;
10950 
10951         if (wantWidth < 0) {
10952             wantWidth = 0;
10953         }
10954         if (hintWidth < 0) {
10955             hintWidth = 0;
10956         }
10957 
10958         Layout.Alignment alignment = getLayoutAlignment();
10959         final boolean testDirChange = mSingleLine && mLayout != null
10960                 && (alignment == Layout.Alignment.ALIGN_NORMAL
10961                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
10962         int oldDir = 0;
10963         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
10964         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
10965         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
10966                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
10967         TruncateAt effectiveEllipsize = mEllipsize;
10968         if (mEllipsize == TruncateAt.MARQUEE
10969                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10970             effectiveEllipsize = TruncateAt.END_SMALL;
10971         }
10972 
10973         if (mTextDir == null) {
10974             mTextDir = getTextDirectionHeuristic();
10975         }
10976 
10977         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
10978                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
10979         if (switchEllipsize) {
10980             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
10981                     ? TruncateAt.END : TruncateAt.MARQUEE;
10982             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
10983                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
10984         }
10985 
10986         shouldEllipsize = mEllipsize != null;
10987         mHintLayout = null;
10988 
10989         if (mHint != null) {
10990             if (shouldEllipsize) hintWidth = wantWidth;
10991 
10992             if (hintBoring == UNKNOWN_BORING) {
10993                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
10994                         isFallbackLineSpacingForBoringLayout(),
10995                         getResolvedMinimumFontMetrics(), mHintBoring);
10996 
10997                 if (hintBoring != null) {
10998                     mHintBoring = hintBoring;
10999                 }
11000             }
11001 
11002             if (hintBoring != null) {
11003                 if (hintBoring.width <= hintWidth
11004                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
11005                     if (mSavedHintLayout != null) {
11006                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
11007                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
11008                                 hintBoring, mIncludePad);
11009                     } else {
11010                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
11011                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
11012                                 hintBoring, mIncludePad);
11013                     }
11014 
11015                     mSavedHintLayout = (BoringLayout) mHintLayout;
11016                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
11017                     if (mSavedHintLayout != null) {
11018                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
11019                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
11020                                 hintBoring, mIncludePad, mEllipsize,
11021                                 ellipsisWidth);
11022                     } else {
11023                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
11024                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
11025                                 hintBoring, mIncludePad, mEllipsize,
11026                                 ellipsisWidth);
11027                     }
11028                 }
11029             }
11030             // TODO: code duplication with makeSingleLayout()
11031             if (mHintLayout == null) {
11032                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
11033                         mHint.length(), mTextPaint, hintWidth)
11034                         .setAlignment(alignment)
11035                         .setTextDirection(mTextDir)
11036                         .setLineSpacing(mSpacingAdd, mSpacingMult)
11037                         .setIncludePad(mIncludePad)
11038                         .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11039                         .setBreakStrategy(mBreakStrategy)
11040                         .setHyphenationFrequency(mHyphenationFrequency)
11041                         .setJustificationMode(mJustificationMode)
11042                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
11043                         .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11044                                 mLineBreakStyle, mLineBreakWordStyle))
11045                         .setUseBoundsForWidth(mUseBoundsForWidth)
11046                         .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11047 
11048                 if (shouldEllipsize) {
11049                     builder.setEllipsize(mEllipsize)
11050                             .setEllipsizedWidth(ellipsisWidth);
11051                 }
11052                 mHintLayout = builder.build();
11053             }
11054         }
11055 
11056         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
11057             registerForPreDraw();
11058         }
11059 
11060         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
11061             if (!compressText(ellipsisWidth)) {
11062                 final int height = mLayoutParams.height;
11063                 // If the size of the view does not depend on the size of the text, try to
11064                 // start the marquee immediately
11065                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
11066                     startMarquee();
11067                 } else {
11068                     // Defer the start of the marquee until we know our width (see setFrame())
11069                     mRestartMarquee = true;
11070                 }
11071             }
11072         }
11073 
11074         // CursorControllers need a non-null mLayout
11075         if (mEditor != null) mEditor.prepareCursorControllers();
11076     }
11077 
11078     /**
11079      * Returns true if DynamicLayout is required
11080      *
11081      * @hide
11082      */
11083     @VisibleForTesting
11084     public boolean useDynamicLayout() {
11085         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
11086     }
11087 
11088     /**
11089      * @hide
11090      */
11091     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
11092             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
11093             boolean useSaved) {
11094         Layout result = null;
11095         if (useDynamicLayout()) {
11096             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
11097                     wantWidth)
11098                     .setDisplayText(mTransformed)
11099                     .setAlignment(alignment)
11100                     .setTextDirection(mTextDir)
11101                     .setLineSpacing(mSpacingAdd, mSpacingMult)
11102                     .setIncludePad(mIncludePad)
11103                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11104                     .setBreakStrategy(mBreakStrategy)
11105                     .setHyphenationFrequency(mHyphenationFrequency)
11106                     .setJustificationMode(mJustificationMode)
11107                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11108                             mLineBreakStyle, mLineBreakWordStyle))
11109                     .setUseBoundsForWidth(mUseBoundsForWidth)
11110                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
11111                     .setEllipsizedWidth(ellipsisWidth)
11112                     .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11113             result = builder.build();
11114         } else {
11115             if (boring == UNKNOWN_BORING) {
11116                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
11117                         isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
11118                         mBoring);
11119                 if (boring != null) {
11120                     mBoring = boring;
11121                 }
11122             }
11123 
11124             if (boring != null) {
11125                 if (boring.width <= wantWidth
11126                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
11127                     if (useSaved && mSavedLayout != null) {
11128                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
11129                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
11130                                 boring, mIncludePad, null, wantWidth,
11131                                 isFallbackLineSpacingForBoringLayout(),
11132                                 mUseBoundsForWidth, getResolvedMinimumFontMetrics());
11133                     } else {
11134                         result = new BoringLayout(
11135                                 mTransformed,
11136                                 mTextPaint,
11137                                 wantWidth,
11138                                 alignment,
11139                                 mSpacingMult,
11140                                 mSpacingAdd,
11141                                 mIncludePad,
11142                                 isFallbackLineSpacingForBoringLayout(),
11143                                 wantWidth,
11144                                 null,
11145                                 boring,
11146                                 mUseBoundsForWidth,
11147                                 mShiftDrawingOffsetForStartOverhang,
11148                                 getResolvedMinimumFontMetrics());
11149                     }
11150 
11151                     if (useSaved) {
11152                         mSavedLayout = (BoringLayout) result;
11153                     }
11154                 } else if (shouldEllipsize && boring.width <= wantWidth) {
11155                     if (useSaved && mSavedLayout != null) {
11156                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
11157                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
11158                                 boring, mIncludePad, effectiveEllipsize,
11159                                 ellipsisWidth, isFallbackLineSpacingForBoringLayout(),
11160                                 mUseBoundsForWidth, getResolvedMinimumFontMetrics());
11161                     } else {
11162                         result = new BoringLayout(
11163                                 mTransformed,
11164                                 mTextPaint,
11165                                 wantWidth,
11166                                 alignment,
11167                                 mSpacingMult,
11168                                 mSpacingAdd,
11169                                 mIncludePad,
11170                                 isFallbackLineSpacingForBoringLayout(),
11171                                 ellipsisWidth,
11172                                 effectiveEllipsize,
11173                                 boring,
11174                                 mUseBoundsForWidth,
11175                                 mShiftDrawingOffsetForStartOverhang,
11176                                 getResolvedMinimumFontMetrics());
11177                     }
11178                 }
11179             }
11180         }
11181         if (result == null) {
11182             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
11183                     0, mTransformed.length(), mTextPaint, wantWidth)
11184                     .setAlignment(alignment)
11185                     .setTextDirection(mTextDir)
11186                     .setLineSpacing(mSpacingAdd, mSpacingMult)
11187                     .setIncludePad(mIncludePad)
11188                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11189                     .setBreakStrategy(mBreakStrategy)
11190                     .setHyphenationFrequency(mHyphenationFrequency)
11191                     .setJustificationMode(mJustificationMode)
11192                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
11193                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11194                             mLineBreakStyle, mLineBreakWordStyle))
11195                     .setUseBoundsForWidth(mUseBoundsForWidth)
11196                     .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11197             if (shouldEllipsize) {
11198                 builder.setEllipsize(effectiveEllipsize)
11199                         .setEllipsizedWidth(ellipsisWidth);
11200             }
11201             result = builder.build();
11202         }
11203         return result;
11204     }
11205 
11206     @UnsupportedAppUsage
11207     private boolean compressText(float width) {
11208         if (isHardwareAccelerated()) return false;
11209 
11210         // Only compress the text if it hasn't been compressed by the previous pass
11211         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
11212                 && mTextPaint.getTextScaleX() == 1.0f) {
11213             final float textWidth = mLayout.getLineWidth(0);
11214             final float overflow = (textWidth + 1.0f - width) / width;
11215             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
11216                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
11217                 post(new Runnable() {
11218                     public void run() {
11219                         requestLayout();
11220                     }
11221                 });
11222                 return true;
11223             }
11224         }
11225 
11226         return false;
11227     }
11228 
11229     private static int desired(Layout layout, boolean useBoundsForWidth) {
11230         int n = layout.getLineCount();
11231         CharSequence text = layout.getText();
11232         float max = 0;
11233 
11234         // if any line was wrapped, we can't use it.
11235         // but it's ok for the last line not to have a newline
11236 
11237         for (int i = 0; i < n - 1; i++) {
11238             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
11239                 return -1;
11240             }
11241         }
11242 
11243         for (int i = 0; i < n; i++) {
11244             max = Math.max(max, layout.getLineMax(i));
11245         }
11246 
11247         if (useBoundsForWidth) {
11248             max = Math.max(max, layout.computeDrawingBoundingBox().width());
11249         }
11250 
11251         return (int) Math.ceil(max);
11252     }
11253 
11254     /**
11255      * Set whether the TextView includes extra top and bottom padding to make
11256      * room for accents that go above the normal ascent and descent.
11257      * The default is true.
11258      *
11259      * @see #getIncludeFontPadding()
11260      *
11261      * @attr ref android.R.styleable#TextView_includeFontPadding
11262      */
11263     public void setIncludeFontPadding(boolean includepad) {
11264         if (mIncludePad != includepad) {
11265             mIncludePad = includepad;
11266 
11267             if (mLayout != null) {
11268                 nullLayouts();
11269                 requestLayout();
11270                 invalidate();
11271             }
11272         }
11273     }
11274 
11275     /**
11276      * Gets whether the TextView includes extra top and bottom padding to make
11277      * room for accents that go above the normal ascent and descent.
11278      *
11279      * @see #setIncludeFontPadding(boolean)
11280      *
11281      * @attr ref android.R.styleable#TextView_includeFontPadding
11282      */
11283     @InspectableProperty
11284     public boolean getIncludeFontPadding() {
11285         return mIncludePad;
11286     }
11287 
11288     /** @hide */
11289     @VisibleForTesting
11290     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
11291 
11292     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)11293     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
11294         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
11295         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
11296         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
11297         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
11298 
11299         int width;
11300         int height;
11301 
11302         BoringLayout.Metrics boring = UNKNOWN_BORING;
11303         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
11304 
11305         if (mTextDir == null) {
11306             mTextDir = getTextDirectionHeuristic();
11307         }
11308 
11309         int des = -1;
11310         boolean fromexisting = false;
11311         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
11312                 ?  (float) widthSize : Float.MAX_VALUE;
11313 
11314         if (widthMode == MeasureSpec.EXACTLY) {
11315             // Parent has told us how big to be. So be it.
11316             width = widthSize;
11317         } else {
11318             if (mLayout != null && mEllipsize == null) {
11319                 des = desired(mLayout, mUseBoundsForWidth);
11320             }
11321 
11322             if (des < 0) {
11323                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
11324                         isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
11325                         mBoring);
11326                 if (boring != null) {
11327                     mBoring = boring;
11328                 }
11329             } else {
11330                 fromexisting = true;
11331             }
11332 
11333             if (boring == null || boring == UNKNOWN_BORING) {
11334                 if (des < 0) {
11335                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
11336                             mTransformed.length(), mTextPaint, mTextDir, widthLimit,
11337                             mUseBoundsForWidth));
11338                 }
11339                 width = des;
11340             } else {
11341                 if (mUseBoundsForWidth) {
11342                     RectF bbox = boring.getDrawingBoundingBox();
11343                     float rightMax = Math.max(bbox.right, boring.width);
11344                     float leftMin = Math.min(bbox.left, 0);
11345                     width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
11346                 } else {
11347                     width = boring.width;
11348                 }
11349             }
11350 
11351             final Drawables dr = mDrawables;
11352             if (dr != null) {
11353                 width = Math.max(width, dr.mDrawableWidthTop);
11354                 width = Math.max(width, dr.mDrawableWidthBottom);
11355             }
11356 
11357             if (mHint != null) {
11358                 int hintDes = -1;
11359                 int hintWidth;
11360 
11361                 if (mHintLayout != null && mEllipsize == null) {
11362                     hintDes = desired(mHintLayout, mUseBoundsForWidth);
11363                 }
11364 
11365                 if (hintDes < 0) {
11366                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
11367                             isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
11368                             mHintBoring);
11369                     if (hintBoring != null) {
11370                         mHintBoring = hintBoring;
11371                     }
11372                 }
11373 
11374                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
11375                     if (hintDes < 0) {
11376                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
11377                                 mHint.length(), mTextPaint, mTextDir, widthLimit,
11378                                 mUseBoundsForWidth));
11379                     }
11380                     hintWidth = hintDes;
11381                 } else {
11382                     hintWidth = hintBoring.width;
11383                 }
11384 
11385                 if (hintWidth > width) {
11386                     width = hintWidth;
11387                 }
11388             }
11389 
11390             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
11391 
11392             if (mMaxWidthMode == EMS) {
11393                 width = Math.min(width, mMaxWidth * getLineHeight());
11394             } else {
11395                 width = Math.min(width, mMaxWidth);
11396             }
11397 
11398             if (mMinWidthMode == EMS) {
11399                 width = Math.max(width, mMinWidth * getLineHeight());
11400             } else {
11401                 width = Math.max(width, mMinWidth);
11402             }
11403 
11404             // Check against our minimum width
11405             width = Math.max(width, getSuggestedMinimumWidth());
11406 
11407             if (widthMode == MeasureSpec.AT_MOST) {
11408                 width = Math.min(widthSize, width);
11409             }
11410         }
11411 
11412         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
11413         int unpaddedWidth = want;
11414 
11415         if (mHorizontallyScrolling) want = VERY_WIDE;
11416 
11417         int hintWant = want;
11418         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
11419 
11420         if (mLayout == null) {
11421             makeNewLayout(want, hintWant, boring, hintBoring,
11422                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
11423         } else {
11424             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
11425                     || (mLayout.getEllipsizedWidth()
11426                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
11427 
11428             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
11429                     && (want > mLayout.getWidth())
11430                     && (mLayout instanceof BoringLayout
11431                             || (fromexisting && des >= 0 && des <= want));
11432 
11433             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
11434 
11435             if (layoutChanged || maximumChanged) {
11436                 if (!maximumChanged && widthChanged) {
11437                     mLayout.increaseWidthTo(want);
11438                 } else {
11439                     makeNewLayout(want, hintWant, boring, hintBoring,
11440                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
11441                 }
11442             } else {
11443                 // Nothing has changed
11444             }
11445         }
11446 
11447         if (heightMode == MeasureSpec.EXACTLY) {
11448             // Parent has told us how big to be. So be it.
11449             height = heightSize;
11450             mDesiredHeightAtMeasure = -1;
11451         } else {
11452             int desired = getDesiredHeight();
11453 
11454             height = desired;
11455             mDesiredHeightAtMeasure = desired;
11456 
11457             if (heightMode == MeasureSpec.AT_MOST) {
11458                 height = Math.min(desired, heightSize);
11459             }
11460         }
11461 
11462         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
11463         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
11464             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
11465         }
11466 
11467         /*
11468          * We didn't let makeNewLayout() register to bring the cursor into view,
11469          * so do it here if there is any possibility that it is needed.
11470          */
11471         if (mMovement != null
11472                 || mLayout.getWidth() > unpaddedWidth
11473                 || mLayout.getHeight() > unpaddedHeight) {
11474             registerForPreDraw();
11475         } else {
11476             scrollTo(0, 0);
11477         }
11478 
11479         setMeasuredDimension(width, height);
11480     }
11481 
11482     /**
11483      * Automatically computes and sets the text size.
11484      */
autoSizeText()11485     private void autoSizeText() {
11486         if (!isAutoSizeEnabled()) {
11487             return;
11488         }
11489 
11490         if (mNeedsAutoSizeText) {
11491             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
11492                 return;
11493             }
11494 
11495             final int availableWidth = mHorizontallyScrolling
11496                     ? VERY_WIDE
11497                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
11498             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
11499                     - getExtendedPaddingTop();
11500 
11501             if (availableWidth <= 0 || availableHeight <= 0) {
11502                 return;
11503             }
11504 
11505             synchronized (TEMP_RECTF) {
11506                 TEMP_RECTF.setEmpty();
11507                 TEMP_RECTF.right = availableWidth;
11508                 TEMP_RECTF.bottom = availableHeight;
11509                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
11510 
11511                 if (optimalTextSize != getTextSize()) {
11512                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
11513                             false /* shouldRequestLayout */);
11514 
11515                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
11516                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
11517                             false /* bringIntoView */);
11518                 }
11519             }
11520         }
11521         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
11522         // after the next layout pass should set this to false.
11523         mNeedsAutoSizeText = true;
11524     }
11525 
11526     /**
11527      * Performs a binary search to find the largest text size that will still fit within the size
11528      * available to this view.
11529      */
findLargestTextSizeWhichFits(RectF availableSpace)11530     private int findLargestTextSizeWhichFits(RectF availableSpace) {
11531         final int sizesCount = mAutoSizeTextSizesInPx.length;
11532         if (sizesCount == 0) {
11533             throw new IllegalStateException("No available text sizes to choose from.");
11534         }
11535 
11536         int bestSizeIndex = 0;
11537         int lowIndex = bestSizeIndex + 1;
11538         int highIndex = sizesCount - 1;
11539         int sizeToTryIndex;
11540         while (lowIndex <= highIndex) {
11541             sizeToTryIndex = (lowIndex + highIndex) / 2;
11542             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
11543                 bestSizeIndex = lowIndex;
11544                 lowIndex = sizeToTryIndex + 1;
11545             } else {
11546                 highIndex = sizeToTryIndex - 1;
11547                 bestSizeIndex = highIndex;
11548             }
11549         }
11550 
11551         return mAutoSizeTextSizesInPx[bestSizeIndex];
11552     }
11553 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)11554     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
11555         final CharSequence text = mTransformed != null
11556                 ? mTransformed
11557                 : getText();
11558         final int maxLines = getMaxLines();
11559         if (mTempTextPaint == null) {
11560             mTempTextPaint = new TextPaint();
11561         } else {
11562             mTempTextPaint.reset();
11563         }
11564         mTempTextPaint.set(getPaint());
11565         mTempTextPaint.setTextSize(suggestedSizeInPx);
11566 
11567         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
11568                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
11569         layoutBuilder.setAlignment(getLayoutAlignment())
11570                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
11571                 .setIncludePad(getIncludeFontPadding())
11572                 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11573                 .setBreakStrategy(getBreakStrategy())
11574                 .setHyphenationFrequency(getHyphenationFrequency())
11575                 .setJustificationMode(getJustificationMode())
11576                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
11577                 .setTextDirection(getTextDirectionHeuristic())
11578                 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11579                         mLineBreakStyle, mLineBreakWordStyle))
11580                 .setUseBoundsForWidth(mUseBoundsForWidth)
11581                 .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11582 
11583         final StaticLayout layout = layoutBuilder.build();
11584 
11585         // Lines overflow.
11586         if (maxLines != -1 && layout.getLineCount() > maxLines) {
11587             return false;
11588         }
11589 
11590         // Height overflow.
11591         if (layout.getHeight() > availableSpace.bottom) {
11592             return false;
11593         }
11594 
11595         return true;
11596     }
11597 
getDesiredHeight()11598     private int getDesiredHeight() {
11599         return Math.max(
11600                 getDesiredHeight(mLayout, true),
11601                 getDesiredHeight(mHintLayout, mEllipsize != null));
11602     }
11603 
getDesiredHeight(Layout layout, boolean cap)11604     private int getDesiredHeight(Layout layout, boolean cap) {
11605         if (layout == null) {
11606             return 0;
11607         }
11608 
11609         /*
11610         * Don't cap the hint to a certain number of lines.
11611         * (Do cap it, though, if we have a maximum pixel height.)
11612         */
11613         int desired = layout.getHeight(cap);
11614 
11615         final Drawables dr = mDrawables;
11616         if (dr != null) {
11617             desired = Math.max(desired, dr.mDrawableHeightLeft);
11618             desired = Math.max(desired, dr.mDrawableHeightRight);
11619         }
11620 
11621         int linecount = layout.getLineCount();
11622         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
11623         desired += padding;
11624 
11625         if (mMaxMode != LINES) {
11626             desired = Math.min(desired, mMaximum);
11627         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
11628                 || layout instanceof BoringLayout)) {
11629             desired = layout.getLineTop(mMaximum);
11630 
11631             if (dr != null) {
11632                 desired = Math.max(desired, dr.mDrawableHeightLeft);
11633                 desired = Math.max(desired, dr.mDrawableHeightRight);
11634             }
11635 
11636             desired += padding;
11637             linecount = mMaximum;
11638         }
11639 
11640         if (mMinMode == LINES) {
11641             if (linecount < mMinimum) {
11642                 desired += getLineHeight() * (mMinimum - linecount);
11643             }
11644         } else {
11645             desired = Math.max(desired, mMinimum);
11646         }
11647 
11648         // Check against our minimum height
11649         desired = Math.max(desired, getSuggestedMinimumHeight());
11650 
11651         return desired;
11652     }
11653 
11654     /**
11655      * Check whether a change to the existing text layout requires a
11656      * new view layout.
11657      */
checkForResize()11658     private void checkForResize() {
11659         boolean sizeChanged = false;
11660 
11661         if (mLayout != null) {
11662             // Check if our width changed
11663             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
11664                 sizeChanged = true;
11665                 invalidate();
11666             }
11667 
11668             // Check if our height changed
11669             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
11670                 int desiredHeight = getDesiredHeight();
11671 
11672                 if (desiredHeight != this.getHeight()) {
11673                     sizeChanged = true;
11674                 }
11675             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
11676                 if (mDesiredHeightAtMeasure >= 0) {
11677                     int desiredHeight = getDesiredHeight();
11678 
11679                     if (desiredHeight != mDesiredHeightAtMeasure) {
11680                         sizeChanged = true;
11681                     }
11682                 }
11683             }
11684         }
11685 
11686         if (sizeChanged) {
11687             requestLayout();
11688             // caller will have already invalidated
11689         }
11690     }
11691 
11692     /**
11693      * Check whether entirely new text requires a new view layout
11694      * or merely a new text layout.
11695      */
11696     @UnsupportedAppUsage
checkForRelayout()11697     private void checkForRelayout() {
11698         // If we have a fixed width, we can just swap in a new text layout
11699         // if the text height stays the same or if the view height is fixed.
11700 
11701         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
11702                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
11703                 && (mHint == null || mHintLayout != null)
11704                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
11705             // Static width, so try making a new text layout.
11706 
11707             int oldht = mLayout.getHeight();
11708             int want = mLayout.getWidth();
11709             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
11710 
11711             /*
11712              * No need to bring the text into view, since the size is not
11713              * changing (unless we do the requestLayout(), in which case it
11714              * will happen at measure).
11715              */
11716             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
11717                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
11718                           false);
11719 
11720             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
11721                 // In a fixed-height view, so use our new text layout.
11722                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
11723                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
11724                     autoSizeText();
11725                     invalidate();
11726                     return;
11727                 }
11728 
11729                 // Dynamic height, but height has stayed the same,
11730                 // so use our new text layout.
11731                 if (mLayout.getHeight() == oldht
11732                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
11733                     autoSizeText();
11734                     invalidate();
11735                     return;
11736                 }
11737             }
11738 
11739             // We lose: the height has changed and we have a dynamic height.
11740             // Request a new view layout using our new text layout.
11741             requestLayout();
11742             invalidate();
11743         } else {
11744             // Dynamic width, so we have no choice but to request a new
11745             // view layout with a new text layout.
11746             nullLayouts();
11747             requestLayout();
11748             invalidate();
11749         }
11750     }
11751 
11752     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)11753     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
11754         super.onLayout(changed, left, top, right, bottom);
11755         if (mDeferScroll >= 0) {
11756             int curs = mDeferScroll;
11757             mDeferScroll = -1;
11758             bringPointIntoView(Math.min(curs, mText.length()));
11759         }
11760         // Call auto-size after the width and height have been calculated.
11761         autoSizeText();
11762     }
11763 
isShowingHint()11764     private boolean isShowingHint() {
11765         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint) && !mHideHint;
11766     }
11767 
11768     /**
11769      * Returns true if anything changed.
11770      */
11771     @UnsupportedAppUsage
bringTextIntoView()11772     private boolean bringTextIntoView() {
11773         Layout layout = isShowingHint() ? mHintLayout : mLayout;
11774         int line = 0;
11775         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
11776             line = layout.getLineCount() - 1;
11777         }
11778 
11779         Layout.Alignment a = layout.getParagraphAlignment(line);
11780         int dir = layout.getParagraphDirection(line);
11781         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
11782         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
11783         int ht = layout.getHeight();
11784 
11785         int scrollx, scrolly;
11786 
11787         // Convert to left, center, or right alignment.
11788         if (a == Layout.Alignment.ALIGN_NORMAL) {
11789             a = dir == Layout.DIR_LEFT_TO_RIGHT
11790                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
11791         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
11792             a = dir == Layout.DIR_LEFT_TO_RIGHT
11793                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
11794         }
11795 
11796         if (a == Layout.Alignment.ALIGN_CENTER) {
11797             /*
11798              * Keep centered if possible, or, if it is too wide to fit,
11799              * keep leading edge in view.
11800              */
11801 
11802             int left = (int) Math.floor(layout.getLineLeft(line));
11803             int right = (int) Math.ceil(layout.getLineRight(line));
11804 
11805             if (right - left < hspace) {
11806                 scrollx = (right + left) / 2 - hspace / 2;
11807             } else {
11808                 if (dir < 0) {
11809                     scrollx = right - hspace;
11810                 } else {
11811                     scrollx = left;
11812                 }
11813             }
11814         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
11815             int right = (int) Math.ceil(layout.getLineRight(line));
11816             scrollx = right - hspace;
11817         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
11818             scrollx = (int) Math.floor(layout.getLineLeft(line));
11819         }
11820 
11821         if (ht < vspace) {
11822             scrolly = 0;
11823         } else {
11824             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
11825                 scrolly = ht - vspace;
11826             } else {
11827                 scrolly = 0;
11828             }
11829         }
11830 
11831         if (scrollx != mScrollX || scrolly != mScrollY) {
11832             scrollTo(scrollx, scrolly);
11833             return true;
11834         } else {
11835             return false;
11836         }
11837     }
11838 
11839     /**
11840      * Move the point, specified by the offset, into the view if it is needed.
11841      * This has to be called after layout. Returns true if anything changed.
11842      */
bringPointIntoView(int offset)11843     public boolean bringPointIntoView(int offset) {
11844         return bringPointIntoView(offset, false);
11845     }
11846 
11847     /**
11848      * Move the insertion position of the given offset into visible area of the View.
11849      *
11850      * If the View is focused or {@code requestRectWithoutFocus} is set to true, this API may call
11851      * {@link View#requestRectangleOnScreen(Rect)} to bring the point to the visible area if
11852      * necessary.
11853      *
11854      * @param offset an offset of the character.
11855      * @param requestRectWithoutFocus True for calling {@link View#requestRectangleOnScreen(Rect)}
11856      *                                in the unfocused state. False for calling it only the View has
11857      *                                the focus.
11858      * @return true if anything changed, otherwise false.
11859      *
11860      * @see #bringPointIntoView(int)
11861      */
bringPointIntoView(@ntRangefrom = 0) int offset, boolean requestRectWithoutFocus)11862     public boolean bringPointIntoView(@IntRange(from = 0) int offset,
11863             boolean requestRectWithoutFocus) {
11864         if (isLayoutRequested()) {
11865             mDeferScroll = offset;
11866             return false;
11867         }
11868         final int offsetTransformed =
11869                 originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
11870         boolean changed = false;
11871 
11872         Layout layout = isShowingHint() ? mHintLayout : mLayout;
11873 
11874         if (layout == null) return changed;
11875 
11876         int line = layout.getLineForOffset(offsetTransformed);
11877 
11878         int grav;
11879 
11880         switch (layout.getParagraphAlignment(line)) {
11881             case ALIGN_LEFT:
11882                 grav = 1;
11883                 break;
11884             case ALIGN_RIGHT:
11885                 grav = -1;
11886                 break;
11887             case ALIGN_NORMAL:
11888                 grav = layout.getParagraphDirection(line);
11889                 break;
11890             case ALIGN_OPPOSITE:
11891                 grav = -layout.getParagraphDirection(line);
11892                 break;
11893             case ALIGN_CENTER:
11894             default:
11895                 grav = 0;
11896                 break;
11897         }
11898 
11899         // We only want to clamp the cursor to fit within the layout width
11900         // in left-to-right modes, because in a right to left alignment,
11901         // we want to scroll to keep the line-right on the screen, as other
11902         // lines are likely to have text flush with the right margin, which
11903         // we want to keep visible.
11904         // A better long-term solution would probably be to measure both
11905         // the full line and a blank-trimmed version, and, for example, use
11906         // the latter measurement for centering and right alignment, but for
11907         // the time being we only implement the cursor clamping in left to
11908         // right where it is most likely to be annoying.
11909         final boolean clamped = grav > 0;
11910         // FIXME: Is it okay to truncate this, or should we round?
11911         final int x = (int) layout.getPrimaryHorizontal(offsetTransformed, clamped);
11912         final int top = layout.getLineTop(line);
11913         final int bottom = layout.getLineTop(line + 1);
11914 
11915         int left = (int) Math.floor(layout.getLineLeft(line));
11916         int right = (int) Math.ceil(layout.getLineRight(line));
11917         int ht = layout.getHeight();
11918 
11919         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
11920         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
11921         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
11922             // If cursor has been clamped, make sure we don't scroll.
11923             right = Math.max(x, left + hspace);
11924         }
11925 
11926         int hslack = (bottom - top) / 2;
11927         int vslack = hslack;
11928 
11929         if (vslack > vspace / 4) {
11930             vslack = vspace / 4;
11931         }
11932         if (hslack > hspace / 4) {
11933             hslack = hspace / 4;
11934         }
11935 
11936         int hs = mScrollX;
11937         int vs = mScrollY;
11938 
11939         if (top - vs < vslack) {
11940             vs = top - vslack;
11941         }
11942         if (bottom - vs > vspace - vslack) {
11943             vs = bottom - (vspace - vslack);
11944         }
11945         if (ht - vs < vspace) {
11946             vs = ht - vspace;
11947         }
11948         if (0 - vs > 0) {
11949             vs = 0;
11950         }
11951 
11952         if (grav != 0) {
11953             if (x - hs < hslack) {
11954                 hs = x - hslack;
11955             }
11956             if (x - hs > hspace - hslack) {
11957                 hs = x - (hspace - hslack);
11958             }
11959         }
11960 
11961         if (grav < 0) {
11962             if (left - hs > 0) {
11963                 hs = left;
11964             }
11965             if (right - hs < hspace) {
11966                 hs = right - hspace;
11967             }
11968         } else if (grav > 0) {
11969             if (right - hs < hspace) {
11970                 hs = right - hspace;
11971             }
11972             if (left - hs > 0) {
11973                 hs = left;
11974             }
11975         } else /* grav == 0 */ {
11976             if (right - left <= hspace) {
11977                 /*
11978                  * If the entire text fits, center it exactly.
11979                  */
11980                 hs = left - (hspace - (right - left)) / 2;
11981             } else if (x > right - hslack) {
11982                 /*
11983                  * If we are near the right edge, keep the right edge
11984                  * at the edge of the view.
11985                  */
11986                 hs = right - hspace;
11987             } else if (x < left + hslack) {
11988                 /*
11989                  * If we are near the left edge, keep the left edge
11990                  * at the edge of the view.
11991                  */
11992                 hs = left;
11993             } else if (left > hs) {
11994                 /*
11995                  * Is there whitespace visible at the left?  Fix it if so.
11996                  */
11997                 hs = left;
11998             } else if (right < hs + hspace) {
11999                 /*
12000                  * Is there whitespace visible at the right?  Fix it if so.
12001                  */
12002                 hs = right - hspace;
12003             } else {
12004                 /*
12005                  * Otherwise, float as needed.
12006                  */
12007                 if (x - hs < hslack) {
12008                     hs = x - hslack;
12009                 }
12010                 if (x - hs > hspace - hslack) {
12011                     hs = x - (hspace - hslack);
12012                 }
12013             }
12014         }
12015 
12016         if (hs != mScrollX || vs != mScrollY) {
12017             if (mScroller == null) {
12018                 scrollTo(hs, vs);
12019             } else {
12020                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
12021                 int dx = hs - mScrollX;
12022                 int dy = vs - mScrollY;
12023 
12024                 if (duration > ANIMATED_SCROLL_GAP) {
12025                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
12026                     awakenScrollBars(mScroller.getDuration());
12027                     invalidate();
12028                 } else {
12029                     if (!mScroller.isFinished()) {
12030                         mScroller.abortAnimation();
12031                     }
12032 
12033                     scrollBy(dx, dy);
12034                 }
12035 
12036                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
12037             }
12038 
12039             changed = true;
12040         }
12041 
12042         if (requestRectWithoutFocus || isFocused()) {
12043             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
12044             // requestRectangleOnScreen() is in terms of content coordinates.
12045 
12046             // The offsets here are to ensure the rectangle we are using is
12047             // within our view bounds, in case the cursor is on the far left
12048             // or right.  If it isn't withing the bounds, then this request
12049             // will be ignored.
12050             if (mTempRect == null) mTempRect = new Rect();
12051             mTempRect.set(x - 2, top, x + 2, bottom);
12052             getInterestingRect(mTempRect, line);
12053             mTempRect.offset(mScrollX, mScrollY);
12054 
12055             if (requestRectangleOnScreen(mTempRect)) {
12056                 changed = true;
12057             }
12058         }
12059 
12060         return changed;
12061     }
12062 
12063     /**
12064      * Move the cursor, if needed, so that it is at an offset that is visible
12065      * to the user.  This will not move the cursor if it represents more than
12066      * one character (a selection range).  This will only work if the
12067      * TextView contains spannable text; otherwise it will do nothing.
12068      *
12069      * @return True if the cursor was actually moved, false otherwise.
12070      */
moveCursorToVisibleOffset()12071     public boolean moveCursorToVisibleOffset() {
12072         if (!(mText instanceof Spannable)) {
12073             return false;
12074         }
12075         int start = getSelectionStartTransformed();
12076         int end = getSelectionEndTransformed();
12077         if (start != end) {
12078             return false;
12079         }
12080 
12081         // First: make sure the line is visible on screen:
12082 
12083         int line = mLayout.getLineForOffset(start);
12084 
12085         final int top = mLayout.getLineTop(line);
12086         final int bottom = mLayout.getLineTop(line + 1);
12087         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
12088         int vslack = (bottom - top) / 2;
12089         if (vslack > vspace / 4) {
12090             vslack = vspace / 4;
12091         }
12092         final int vs = mScrollY;
12093 
12094         if (top < (vs + vslack)) {
12095             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
12096         } else if (bottom > (vspace + vs - vslack)) {
12097             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
12098         }
12099 
12100         // Next: make sure the character is visible on screen:
12101 
12102         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
12103         final int hs = mScrollX;
12104         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
12105         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
12106 
12107         // line might contain bidirectional text
12108         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
12109         final int highChar = leftChar > rightChar ? leftChar : rightChar;
12110 
12111         int newStart = start;
12112         if (newStart < lowChar) {
12113             newStart = lowChar;
12114         } else if (newStart > highChar) {
12115             newStart = highChar;
12116         }
12117 
12118         if (newStart != start) {
12119             Selection.setSelection(mSpannable,
12120                     transformedToOriginal(newStart, OffsetMapping.MAP_STRATEGY_CURSOR));
12121             return true;
12122         }
12123 
12124         return false;
12125     }
12126 
12127     @Override
computeScroll()12128     public void computeScroll() {
12129         if (mScroller != null) {
12130             if (mScroller.computeScrollOffset()) {
12131                 mScrollX = mScroller.getCurrX();
12132                 mScrollY = mScroller.getCurrY();
12133                 invalidateParentCaches();
12134                 postInvalidate();  // So we draw again
12135             }
12136         }
12137     }
12138 
getInterestingRect(Rect r, int line)12139     private void getInterestingRect(Rect r, int line) {
12140         convertFromViewportToContentCoordinates(r);
12141 
12142         // Rectangle can can be expanded on first and last line to take
12143         // padding into account.
12144         // TODO Take left/right padding into account too?
12145         if (line == 0) r.top -= getExtendedPaddingTop();
12146         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
12147     }
12148 
convertFromViewportToContentCoordinates(Rect r)12149     private void convertFromViewportToContentCoordinates(Rect r) {
12150         final int horizontalOffset = viewportToContentHorizontalOffset();
12151         r.left += horizontalOffset;
12152         r.right += horizontalOffset;
12153 
12154         final int verticalOffset = viewportToContentVerticalOffset();
12155         r.top += verticalOffset;
12156         r.bottom += verticalOffset;
12157     }
12158 
convertFromScreenToContentCoordinates(PointF point)12159     private PointF convertFromScreenToContentCoordinates(PointF point) {
12160         if (Flags.handwritingGestureWithTransformation()) {
12161             if (mTempMatrix == null) {
12162                 mTempMatrix = new Matrix();
12163             }
12164             Matrix matrix = mTempMatrix;
12165             matrix.reset();
12166             transformMatrixToLocal(matrix);
12167             matrix.postTranslate(
12168                     -viewportToContentHorizontalOffset(),
12169                     -viewportToContentVerticalOffset()
12170             );
12171 
12172             float[] copy = new float[] { point.x, point.y };
12173             matrix.mapPoints(copy);
12174             return new PointF(copy[0], copy[1]);
12175         }
12176         int[] screenToViewport = getLocationOnScreen();
12177         PointF copy = new PointF(point);
12178         copy.offset(
12179                 -(screenToViewport[0] + viewportToContentHorizontalOffset()),
12180                 -(screenToViewport[1] + viewportToContentVerticalOffset()));
12181         return copy;
12182     }
12183 
convertFromScreenToContentCoordinates(RectF rect)12184     private RectF convertFromScreenToContentCoordinates(RectF rect) {
12185         if (Flags.handwritingGestureWithTransformation()) {
12186             if (mTempMatrix == null) {
12187                 mTempMatrix = new Matrix();
12188             }
12189             Matrix matrix = mTempMatrix;
12190             matrix.reset();
12191             transformMatrixToLocal(matrix);
12192             matrix.postTranslate(
12193                     -viewportToContentHorizontalOffset(),
12194                     -viewportToContentVerticalOffset()
12195             );
12196 
12197             RectF copy = new RectF(rect);
12198             matrix.mapRect(copy);
12199             return copy;
12200         }
12201         int[] screenToViewport = getLocationOnScreen();
12202         RectF copy = new RectF(rect);
12203         copy.offset(
12204                 -(screenToViewport[0] + viewportToContentHorizontalOffset()),
12205                 -(screenToViewport[1] + viewportToContentVerticalOffset()));
12206         return copy;
12207     }
12208 
viewportToContentHorizontalOffset()12209     int viewportToContentHorizontalOffset() {
12210         return getCompoundPaddingLeft() - mScrollX;
12211     }
12212 
12213     @UnsupportedAppUsage
viewportToContentVerticalOffset()12214     int viewportToContentVerticalOffset() {
12215         int offset = getExtendedPaddingTop() - mScrollY;
12216         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
12217             offset += getVerticalOffset(false);
12218         }
12219         return offset;
12220     }
12221 
12222     @Override
debug(int depth)12223     public void debug(int depth) {
12224         super.debug(depth);
12225 
12226         String output = debugIndent(depth);
12227         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
12228                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
12229                 + "} ";
12230 
12231         if (mText != null) {
12232 
12233             output += "mText=\"" + mText + "\" ";
12234             if (mLayout != null) {
12235                 output += "mLayout width=" + mLayout.getWidth()
12236                         + " height=" + mLayout.getHeight();
12237             }
12238         } else {
12239             output += "mText=NULL";
12240         }
12241         Log.d(VIEW_LOG_TAG, output);
12242     }
12243 
12244     /**
12245      * Convenience for {@link Selection#getSelectionStart}.
12246      */
12247     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()12248     public int getSelectionStart() {
12249         return Selection.getSelectionStart(getText());
12250     }
12251 
12252     /**
12253      * Convenience for {@link Selection#getSelectionEnd}.
12254      */
12255     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()12256     public int getSelectionEnd() {
12257         return Selection.getSelectionEnd(getText());
12258     }
12259 
12260     /**
12261      * Calculates the rectangles which should be highlighted to indicate a selection between start
12262      * and end and feeds them into the given {@link Layout.SelectionRectangleConsumer}.
12263      *
12264      * @param start    the starting index of the selection
12265      * @param end      the ending index of the selection
12266      * @param consumer the {@link Layout.SelectionRectangleConsumer} which will receive the
12267      *                 generated rectangles. It will be called every time a rectangle is generated.
12268      * @hide
12269      */
getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer)12270     public void getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer) {
12271         final int transformedStart =
12272                 originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
12273         final int transformedEnd = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
12274         mLayout.getSelection(transformedStart, transformedEnd, consumer);
12275     }
12276 
getSelectionStartTransformed()12277     int getSelectionStartTransformed() {
12278         final int start = getSelectionStart();
12279         if (start < 0) return start;
12280         return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
12281     }
12282 
getSelectionEndTransformed()12283     int getSelectionEndTransformed() {
12284         final int end = getSelectionEnd();
12285         if (end < 0) return end;
12286         return originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
12287     }
12288 
12289     /**
12290      * Return true iff there is a selection of nonzero length inside this text view.
12291      */
hasSelection()12292     public boolean hasSelection() {
12293         final int selectionStart = getSelectionStart();
12294         final int selectionEnd = getSelectionEnd();
12295         final int selectionMin;
12296         final int selectionMax;
12297         if (selectionStart < selectionEnd) {
12298             selectionMin = selectionStart;
12299             selectionMax = selectionEnd;
12300         } else {
12301             selectionMin = selectionEnd;
12302             selectionMax = selectionStart;
12303         }
12304 
12305         return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax;
12306     }
12307 
12308     /**
12309      * @hide
12310      */
12311     @VisibleForTesting
getSelectedText()12312     public String getSelectedText() {
12313         if (!hasSelection()) {
12314             return null;
12315         }
12316 
12317         final int start = getSelectionStart();
12318         final int end = getSelectionEnd();
12319         return String.valueOf(
12320                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
12321     }
12322 
12323     /**
12324      * Sets the properties of this field (lines, horizontally scrolling,
12325      * transformation method) to be for a single-line input.
12326      *
12327      * @attr ref android.R.styleable#TextView_singleLine
12328      */
setSingleLine()12329     public void setSingleLine() {
12330         setSingleLine(true);
12331     }
12332 
12333     /**
12334      * Sets the properties of this field to transform input to ALL CAPS
12335      * display. This may use a "small caps" formatting if available.
12336      * This setting will be ignored if this field is editable or selectable.
12337      *
12338      * This call replaces the current transformation method. Disabling this
12339      * will not necessarily restore the previous behavior from before this
12340      * was enabled.
12341      *
12342      * @see #setTransformationMethod(TransformationMethod)
12343      * @attr ref android.R.styleable#TextView_textAllCaps
12344      */
12345     @android.view.RemotableViewMethod
setAllCaps(boolean allCaps)12346     public void setAllCaps(boolean allCaps) {
12347         if (allCaps) {
12348             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
12349         } else {
12350             setTransformationMethod(null);
12351         }
12352     }
12353 
12354     /**
12355      *
12356      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
12357      * @return Whether the current transformation method is for ALL CAPS.
12358      *
12359      * @see #setAllCaps(boolean)
12360      * @see #setTransformationMethod(TransformationMethod)
12361      */
12362     @InspectableProperty(name = "textAllCaps")
isAllCaps()12363     public boolean isAllCaps() {
12364         final TransformationMethod method = getTransformationMethod();
12365         return method != null && method instanceof AllCapsTransformationMethod;
12366     }
12367 
12368     /**
12369      * If true, sets the properties of this field (number of lines, horizontally scrolling,
12370      * transformation method) to be for a single-line input; if false, restores these to the default
12371      * conditions.
12372      *
12373      * Note that the default conditions are not necessarily those that were in effect prior this
12374      * method, and you may want to reset these properties to your custom values.
12375      *
12376      * Note that due to performance reasons, by setting single line for the EditText, the maximum
12377      * text length is set to 5000 if no other character limitation are applied.
12378      *
12379      * @attr ref android.R.styleable#TextView_singleLine
12380      */
12381     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)12382     public void setSingleLine(boolean singleLine) {
12383         // Could be used, but may break backward compatibility.
12384         // if (mSingleLine == singleLine) return;
12385         setInputTypeSingleLine(singleLine);
12386         applySingleLine(singleLine, true, true, true);
12387     }
12388 
12389     /**
12390      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
12391      * @param singleLine
12392      */
setInputTypeSingleLine(boolean singleLine)12393     private void setInputTypeSingleLine(boolean singleLine) {
12394         if (mEditor != null
12395                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
12396                         == EditorInfo.TYPE_CLASS_TEXT) {
12397             if (singleLine) {
12398                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
12399             } else {
12400                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
12401             }
12402         }
12403     }
12404 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)12405     private void applySingleLine(boolean singleLine, boolean applyTransformation,
12406             boolean changeMaxLines, boolean changeMaxLength) {
12407         mSingleLine = singleLine;
12408 
12409         if (singleLine) {
12410             setLines(1);
12411             setHorizontallyScrolling(true);
12412             if (applyTransformation) {
12413                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
12414             }
12415 
12416             if (!changeMaxLength) return;
12417 
12418             // Single line length filter is only applicable editable text.
12419             if (mBufferType != BufferType.EDITABLE) return;
12420 
12421             final InputFilter[] prevFilters = getFilters();
12422             for (InputFilter filter: getFilters()) {
12423                 // We don't add LengthFilter if already there.
12424                 if (filter instanceof InputFilter.LengthFilter) return;
12425             }
12426 
12427             if (mSingleLineLengthFilter == null) {
12428                 mSingleLineLengthFilter = new InputFilter.LengthFilter(
12429                     MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
12430             }
12431 
12432             final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1];
12433             System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length);
12434             newFilters[prevFilters.length] = mSingleLineLengthFilter;
12435 
12436             setFilters(newFilters);
12437 
12438             // Since filter doesn't apply to existing text, trigger filter by setting text.
12439             setText(getText());
12440         } else {
12441             if (changeMaxLines) {
12442                 setMaxLines(Integer.MAX_VALUE);
12443             }
12444             setHorizontallyScrolling(false);
12445             if (applyTransformation) {
12446                 setTransformationMethod(null);
12447             }
12448 
12449             if (!changeMaxLength) return;
12450 
12451             // Single line length filter is only applicable editable text.
12452             if (mBufferType != BufferType.EDITABLE) return;
12453 
12454             final InputFilter[] prevFilters = getFilters();
12455             if (prevFilters.length == 0) return;
12456 
12457             // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated
12458             // single line char limit filter.
12459             if (mSingleLineLengthFilter == null) return;
12460 
12461             // If we need to remove mSingleLineLengthFilter, we need to allocate another array.
12462             // Since filter list is expected to be small and want to avoid unnecessary array
12463             // allocation, check if there is mSingleLengthFilter first.
12464             int targetIndex = -1;
12465             for (int i = 0; i < prevFilters.length; ++i) {
12466                 if (prevFilters[i] == mSingleLineLengthFilter) {
12467                     targetIndex = i;
12468                     break;
12469                 }
12470             }
12471             if (targetIndex == -1) return;  // not found. Do nothing.
12472 
12473             if (prevFilters.length == 1) {
12474                 setFilters(NO_FILTERS);
12475                 return;
12476             }
12477 
12478             // Create new array which doesn't include mSingleLengthFilter.
12479             final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1];
12480             System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex);
12481             System.arraycopy(
12482                     prevFilters,
12483                     targetIndex + 1,
12484                     newFilters,
12485                     targetIndex,
12486                     prevFilters.length - targetIndex - 1);
12487             setFilters(newFilters);
12488             mSingleLineLengthFilter = null;
12489         }
12490     }
12491 
12492     /**
12493      * Causes words in the text that are longer than the view's width
12494      * to be ellipsized instead of broken in the middle.  You may also
12495      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
12496      * to constrain the text to a single line.  Use <code>null</code>
12497      * to turn off ellipsizing.
12498      *
12499      * If {@link #setMaxLines} has been used to set two or more lines,
12500      * only {@link android.text.TextUtils.TruncateAt#END} and
12501      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
12502      * (other ellipsizing types will not do anything).
12503      *
12504      * @attr ref android.R.styleable#TextView_ellipsize
12505      */
setEllipsize(TextUtils.TruncateAt where)12506     public void setEllipsize(TextUtils.TruncateAt where) {
12507         // TruncateAt is an enum. != comparison is ok between these singleton objects.
12508         if (mEllipsize != where) {
12509             mEllipsize = where;
12510 
12511             if (mLayout != null) {
12512                 nullLayouts();
12513                 requestLayout();
12514                 invalidate();
12515             }
12516         }
12517     }
12518 
12519     /**
12520      * Sets how many times to repeat the marquee animation. Only applied if the
12521      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
12522      *
12523      * @see #getMarqueeRepeatLimit()
12524      *
12525      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
12526      */
setMarqueeRepeatLimit(int marqueeLimit)12527     public void setMarqueeRepeatLimit(int marqueeLimit) {
12528         mMarqueeRepeatLimit = marqueeLimit;
12529     }
12530 
12531     /**
12532      * Gets the number of times the marquee animation is repeated. Only meaningful if the
12533      * TextView has marquee enabled.
12534      *
12535      * @return the number of times the marquee animation is repeated. -1 if the animation
12536      * repeats indefinitely
12537      *
12538      * @see #setMarqueeRepeatLimit(int)
12539      *
12540      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
12541      */
12542     @InspectableProperty
getMarqueeRepeatLimit()12543     public int getMarqueeRepeatLimit() {
12544         return mMarqueeRepeatLimit;
12545     }
12546 
12547     /**
12548      * Returns where, if anywhere, words that are longer than the view
12549      * is wide should be ellipsized.
12550      */
12551     @InspectableProperty
12552     @ViewDebug.ExportedProperty
getEllipsize()12553     public TextUtils.TruncateAt getEllipsize() {
12554         return mEllipsize;
12555     }
12556 
12557     /**
12558      * Set the TextView so that when it takes focus, all the text is
12559      * selected.
12560      *
12561      * @attr ref android.R.styleable#TextView_selectAllOnFocus
12562      */
12563     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)12564     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
12565         createEditorIfNeeded();
12566         mEditor.mSelectAllOnFocus = selectAllOnFocus;
12567 
12568         if (selectAllOnFocus && !(mText instanceof Spannable)) {
12569             setText(mText, BufferType.SPANNABLE);
12570         }
12571     }
12572 
12573     /**
12574      * Set whether the cursor is visible. The default is true. Note that this property only
12575      * makes sense for editable TextView. If IME is consuming the input, the cursor will always be
12576      * invisible, visibility will be updated as the last state when IME does not consume
12577      * the input anymore.
12578      *
12579      * @see #isCursorVisible()
12580      *
12581      * @attr ref android.R.styleable#TextView_cursorVisible
12582      */
12583     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)12584     public void setCursorVisible(boolean visible) {
12585         mCursorVisibleFromAttr = visible;
12586         updateCursorVisibleInternal();
12587     }
12588 
12589     /**
12590      * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput}
12591      * is {@code true}. Otherwise, make the cursor visible.
12592      *
12593      * @param imeConsumesInput {@code true} if IME is consuming the input
12594      *
12595      * @hide
12596      */
setImeConsumesInput(boolean imeConsumesInput)12597     public void setImeConsumesInput(boolean imeConsumesInput) {
12598         mImeIsConsumingInput = imeConsumesInput;
12599         updateCursorVisibleInternal();
12600     }
12601 
updateCursorVisibleInternal()12602     private void updateCursorVisibleInternal()  {
12603         boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput;
12604         if (visible && mEditor == null) return; // visible is the default value with no edit data
12605         createEditorIfNeeded();
12606         if (mEditor.mCursorVisible != visible) {
12607             mEditor.mCursorVisible = visible;
12608             invalidate();
12609 
12610             mEditor.makeBlink();
12611 
12612             // InsertionPointCursorController depends on mCursorVisible
12613             mEditor.prepareCursorControllers();
12614         }
12615     }
12616 
12617     /**
12618      * @return whether or not the cursor is visible (assuming this TextView is editable). This
12619      * method may return {@code false} when the IME is consuming the input even if the
12620      * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)}
12621      * is called.
12622      *
12623      * @see #setCursorVisible(boolean)
12624      *
12625      * @attr ref android.R.styleable#TextView_cursorVisible
12626      */
12627     @InspectableProperty
isCursorVisible()12628     public boolean isCursorVisible() {
12629         // true is the default value
12630         return mEditor == null ? true : mEditor.mCursorVisible;
12631     }
12632 
12633     /**
12634      * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}.
12635      * {@code true} is the default value.
12636      *
12637      * @see #setCursorVisible(boolean)
12638      * @hide
12639      */
isCursorVisibleFromAttr()12640     public boolean isCursorVisibleFromAttr() {
12641         return mCursorVisibleFromAttr;
12642     }
12643 
canMarquee()12644     private boolean canMarquee() {
12645         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
12646         return width > 0 && (mLayout.getLineWidth(0) > width
12647                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
12648                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
12649     }
12650 
12651     /**
12652      * @hide
12653      */
12654     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()12655     protected void startMarquee() {
12656         // Do not ellipsize EditText
12657         if (getKeyListener() != null) return;
12658 
12659         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
12660             return;
12661         }
12662 
12663         if ((mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible()
12664                 && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) {
12665 
12666             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
12667                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
12668                 final Layout tmp = mLayout;
12669                 mLayout = mSavedMarqueeModeLayout;
12670                 mSavedMarqueeModeLayout = tmp;
12671                 setHorizontalFadingEdgeEnabled(true);
12672                 requestLayout();
12673                 invalidate();
12674             }
12675 
12676             if (mMarquee == null) mMarquee = new Marquee(this);
12677             mMarquee.start(mMarqueeRepeatLimit);
12678         }
12679     }
12680 
12681     /**
12682      * @hide
12683      */
stopMarquee()12684     protected void stopMarquee() {
12685         if (mMarquee != null && !mMarquee.isStopped()) {
12686             mMarquee.stop();
12687         }
12688 
12689         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
12690             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
12691             final Layout tmp = mSavedMarqueeModeLayout;
12692             mSavedMarqueeModeLayout = mLayout;
12693             mLayout = tmp;
12694             setHorizontalFadingEdgeEnabled(false);
12695             requestLayout();
12696             invalidate();
12697         }
12698     }
12699 
12700     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)12701     private void startStopMarquee(boolean start) {
12702         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
12703             if (start) {
12704                 startMarquee();
12705             } else {
12706                 stopMarquee();
12707             }
12708         }
12709     }
12710 
12711     /**
12712      * This method is called when the text is changed, in case any subclasses
12713      * would like to know.
12714      *
12715      * Within <code>text</code>, the <code>lengthAfter</code> characters
12716      * beginning at <code>start</code> have just replaced old text that had
12717      * length <code>lengthBefore</code>. It is an error to attempt to make
12718      * changes to <code>text</code> from this callback.
12719      *
12720      * @param text The text the TextView is displaying
12721      * @param start The offset of the start of the range of the text that was
12722      * modified
12723      * @param lengthBefore The length of the former text that has been replaced
12724      * @param lengthAfter The length of the replacement modified text
12725      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)12726     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
12727         // intentionally empty, template pattern method can be overridden by subclasses
12728     }
12729 
12730     /**
12731      * This method is called when the selection has changed, in case any
12732      * subclasses would like to know.
12733      * </p>
12734      * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs
12735      * the accessibility subsystem about the selection change.
12736      * </p>
12737      *
12738      * @param selStart The new selection start location.
12739      * @param selEnd The new selection end location.
12740      */
12741     @CallSuper
onSelectionChanged(int selStart, int selEnd)12742     protected void onSelectionChanged(int selStart, int selEnd) {
12743         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
12744     }
12745 
12746     /**
12747      * Adds a TextWatcher to the list of those whose methods are called
12748      * whenever this TextView's text changes.
12749      * <p>
12750      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
12751      * not called after {@link #setText} calls.  Now, doing {@link #setText}
12752      * if there are any text changed listeners forces the buffer type to
12753      * Editable if it would not otherwise be and does call this method.
12754      */
addTextChangedListener(TextWatcher watcher)12755     public void addTextChangedListener(TextWatcher watcher) {
12756         if (mListeners == null) {
12757             mListeners = new ArrayList<TextWatcher>();
12758         }
12759 
12760         mListeners.add(watcher);
12761     }
12762 
12763     /**
12764      * Removes the specified TextWatcher from the list of those whose
12765      * methods are called
12766      * whenever this TextView's text changes.
12767      */
removeTextChangedListener(TextWatcher watcher)12768     public void removeTextChangedListener(TextWatcher watcher) {
12769         if (mListeners != null) {
12770             int i = mListeners.indexOf(watcher);
12771 
12772             if (i >= 0) {
12773                 mListeners.remove(i);
12774             }
12775         }
12776     }
12777 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)12778     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
12779         if (mListeners != null) {
12780             final ArrayList<TextWatcher> list = mListeners;
12781             final int count = list.size();
12782             for (int i = 0; i < count; i++) {
12783                 list.get(i).beforeTextChanged(text, start, before, after);
12784             }
12785         }
12786 
12787         // The spans that are inside or intersect the modified region no longer make sense
12788         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
12789         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
12790     }
12791 
12792     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)12793     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
12794         if (!(mText instanceof Editable)) return;
12795         Editable text = (Editable) mText;
12796 
12797         T[] spans = text.getSpans(start, end, type);
12798         ArrayList<T> spansToRemove = new ArrayList<>();
12799         for (T span : spans) {
12800             final int spanStart = text.getSpanStart(span);
12801             final int spanEnd = text.getSpanEnd(span);
12802             if (spanEnd == start || spanStart == end) continue;
12803             spansToRemove.add(span);
12804         }
12805         for (T span : spansToRemove) {
12806             text.removeSpan(span);
12807         }
12808     }
12809 
removeAdjacentSuggestionSpans(final int pos)12810     void removeAdjacentSuggestionSpans(final int pos) {
12811         if (!(mText instanceof Editable)) return;
12812         final Editable text = (Editable) mText;
12813 
12814         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
12815         final int length = spans.length;
12816         for (int i = 0; i < length; i++) {
12817             final int spanStart = text.getSpanStart(spans[i]);
12818             final int spanEnd = text.getSpanEnd(spans[i]);
12819             if (spanEnd == pos || spanStart == pos) {
12820                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
12821                     text.removeSpan(spans[i]);
12822                 }
12823             }
12824         }
12825     }
12826 
12827     /**
12828      * Not private so it can be called from an inner class without going
12829      * through a thunk.
12830      */
sendOnTextChanged(CharSequence text, int start, int before, int after)12831     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
12832         if (mListeners != null) {
12833             final ArrayList<TextWatcher> list = mListeners;
12834             final int count = list.size();
12835             for (int i = 0; i < count; i++) {
12836                 list.get(i).onTextChanged(text, start, before, after);
12837             }
12838         }
12839 
12840         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
12841     }
12842 
12843     /**
12844      * Not private so it can be called from an inner class without going
12845      * through a thunk.
12846      */
sendAfterTextChanged(Editable text)12847     void sendAfterTextChanged(Editable text) {
12848         if (mListeners != null) {
12849             final ArrayList<TextWatcher> list = mListeners;
12850             final int count = list.size();
12851             for (int i = 0; i < count; i++) {
12852                 list.get(i).afterTextChanged(text);
12853             }
12854         }
12855 
12856         notifyListeningManagersAfterTextChanged();
12857 
12858         hideErrorIfUnchanged();
12859     }
12860 
12861     /**
12862      * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
12863      * interested on text changes.
12864      */
notifyListeningManagersAfterTextChanged()12865     private void notifyListeningManagersAfterTextChanged() {
12866 
12867         // Autofill
12868         if (isAutofillable()) {
12869             // It is important to not check whether the view is important for autofill
12870             // since the user can trigger autofill manually on not important views.
12871             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
12872             if (afm != null) {
12873                 if (android.view.autofill.Helper.sVerbose) {
12874                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
12875                 }
12876                 afm.notifyValueChanged(TextView.this);
12877             }
12878         }
12879 
12880         notifyContentCaptureTextChanged();
12881     }
12882 
12883     /**
12884      * Notifies the ContentCapture service that the text of the view has changed (only if
12885      * ContentCapture has been notified of this view's existence already).
12886      *
12887      * @hide
12888      */
notifyContentCaptureTextChanged()12889     public void notifyContentCaptureTextChanged() {
12890         // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
12891         // of using isLaidout(), so it's not called in cases where it's laid out but a
12892         // notifyAppeared was not sent.
12893         if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) {
12894             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
12895             if (cm != null && cm.isContentCaptureEnabled()) {
12896                 final ContentCaptureSession session = getContentCaptureSession();
12897                 if (session != null) {
12898                     // TODO(b/111276913): pass flags when edited by user / add CTS test
12899                     session.notifyViewTextChanged(getAutofillId(), getText());
12900                 }
12901             }
12902         }
12903     }
12904 
isAutofillable()12905     private boolean isAutofillable() {
12906         // It is important to not check whether the view is important for autofill
12907         // since the user can trigger autofill manually on not important views.
12908         return getAutofillType() != AUTOFILL_TYPE_NONE;
12909     }
12910 
updateAfterEdit()12911     void updateAfterEdit() {
12912         invalidate();
12913         int curs = getSelectionStart();
12914 
12915         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
12916             registerForPreDraw();
12917         }
12918 
12919         checkForResize();
12920 
12921         if (curs >= 0) {
12922             mHighlightPathBogus = true;
12923             if (mEditor != null) mEditor.makeBlink();
12924             bringPointIntoView(curs);
12925         }
12926     }
12927 
12928     /**
12929      * Not private so it can be called from an inner class without going
12930      * through a thunk.
12931      */
handleTextChanged(CharSequence buffer, int start, int before, int after)12932     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
12933         sLastCutCopyOrTextChangedTime = 0;
12934 
12935         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
12936         if (ims == null || ims.mBatchEditNesting == 0) {
12937             updateAfterEdit();
12938         }
12939         if (ims != null) {
12940             ims.mContentChanged = true;
12941             if (ims.mChangedStart < 0) {
12942                 ims.mChangedStart = start;
12943                 ims.mChangedEnd = start + before;
12944             } else {
12945                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
12946                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
12947             }
12948             ims.mChangedDelta += after - before;
12949         }
12950         resetErrorChangedFlag();
12951         sendOnTextChanged(buffer, start, before, after);
12952         onTextChanged(buffer, start, before, after);
12953 
12954         mHideHint = false;
12955         clearGesturePreviewHighlight();
12956     }
12957 
12958     /**
12959      * Not private so it can be called from an inner class without going
12960      * through a thunk.
12961      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)12962     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
12963         // XXX Make the start and end move together if this ends up
12964         // spending too much time invalidating.
12965 
12966         boolean selChanged = false;
12967         int newSelStart = -1, newSelEnd = -1;
12968 
12969         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
12970 
12971         if (what == Selection.SELECTION_END) {
12972             selChanged = true;
12973             newSelEnd = newStart;
12974 
12975             if (oldStart >= 0 || newStart >= 0) {
12976                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
12977                 checkForResize();
12978                 registerForPreDraw();
12979                 if (mEditor != null) mEditor.makeBlink();
12980             }
12981         }
12982 
12983         if (what == Selection.SELECTION_START) {
12984             selChanged = true;
12985             newSelStart = newStart;
12986 
12987             if (oldStart >= 0 || newStart >= 0) {
12988                 int end = Selection.getSelectionEnd(buf);
12989                 invalidateCursor(end, oldStart, newStart);
12990             }
12991         }
12992 
12993         if (selChanged) {
12994             clearGesturePreviewHighlight();
12995             mHighlightPathBogus = true;
12996             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
12997 
12998             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
12999                 if (newSelStart < 0) {
13000                     newSelStart = Selection.getSelectionStart(buf);
13001                 }
13002                 if (newSelEnd < 0) {
13003                     newSelEnd = Selection.getSelectionEnd(buf);
13004                 }
13005 
13006                 if (mEditor != null) {
13007                     mEditor.refreshTextActionMode();
13008                     if (!hasSelection()
13009                             && mEditor.getTextActionMode() == null && hasTransientState()) {
13010                         // User generated selection has been removed.
13011                         setHasTransientState(false);
13012                     }
13013                 }
13014                 onSelectionChanged(newSelStart, newSelEnd);
13015             }
13016         }
13017 
13018         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
13019                 || what instanceof CharacterStyle) {
13020             if (ims == null || ims.mBatchEditNesting == 0) {
13021                 invalidate();
13022                 mHighlightPathBogus = true;
13023                 checkForResize();
13024             } else {
13025                 ims.mContentChanged = true;
13026             }
13027             if (mEditor != null) {
13028                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
13029                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
13030                 mEditor.invalidateHandlesAndActionMode();
13031             }
13032         }
13033 
13034         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
13035             mHighlightPathBogus = true;
13036             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
13037                 ims.mSelectionModeChanged = true;
13038             }
13039 
13040             if (Selection.getSelectionStart(buf) >= 0) {
13041                 if (ims == null || ims.mBatchEditNesting == 0) {
13042                     invalidateCursor();
13043                 } else {
13044                     ims.mCursorChanged = true;
13045                 }
13046             }
13047         }
13048 
13049         if (what instanceof ParcelableSpan) {
13050             // If this is a span that can be sent to a remote process,
13051             // the current extract editor would be interested in it.
13052             if (ims != null && ims.mExtractedTextRequest != null) {
13053                 if (ims.mBatchEditNesting != 0) {
13054                     if (oldStart >= 0) {
13055                         if (ims.mChangedStart > oldStart) {
13056                             ims.mChangedStart = oldStart;
13057                         }
13058                         if (ims.mChangedStart > oldEnd) {
13059                             ims.mChangedStart = oldEnd;
13060                         }
13061                     }
13062                     if (newStart >= 0) {
13063                         if (ims.mChangedStart > newStart) {
13064                             ims.mChangedStart = newStart;
13065                         }
13066                         if (ims.mChangedStart > newEnd) {
13067                             ims.mChangedStart = newEnd;
13068                         }
13069                     }
13070                 } else {
13071                     if (DEBUG_EXTRACT) {
13072                         Log.v(LOG_TAG, "Span change outside of batch: "
13073                                 + oldStart + "-" + oldEnd + ","
13074                                 + newStart + "-" + newEnd + " " + what);
13075                     }
13076                     ims.mContentChanged = true;
13077                 }
13078             }
13079         }
13080 
13081         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
13082                 && what instanceof SpellCheckSpan) {
13083             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
13084         }
13085     }
13086 
13087     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)13088     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
13089         if (isTemporarilyDetached()) {
13090             // If we are temporarily in the detach state, then do nothing.
13091             super.onFocusChanged(focused, direction, previouslyFocusedRect);
13092             return;
13093         }
13094 
13095         mHideHint = false;
13096 
13097         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
13098 
13099         if (focused) {
13100             if (mSpannable != null) {
13101                 MetaKeyKeyListener.resetMetaState(mSpannable);
13102             }
13103         }
13104 
13105         startStopMarquee(focused);
13106 
13107         if (mTransformation != null) {
13108             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
13109         }
13110 
13111         super.onFocusChanged(focused, direction, previouslyFocusedRect);
13112     }
13113 
13114     @Override
onWindowFocusChanged(boolean hasWindowFocus)13115     public void onWindowFocusChanged(boolean hasWindowFocus) {
13116         super.onWindowFocusChanged(hasWindowFocus);
13117 
13118         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
13119 
13120         startStopMarquee(hasWindowFocus);
13121     }
13122 
13123     @Override
onVisibilityChanged(View changedView, int visibility)13124     protected void onVisibilityChanged(View changedView, int visibility) {
13125         super.onVisibilityChanged(changedView, visibility);
13126         if (mEditor != null && visibility != VISIBLE) {
13127             mEditor.hideCursorAndSpanControllers();
13128             stopTextActionMode();
13129         }
13130     }
13131 
13132     @Override
onVisibilityAggregated(boolean isVisible)13133     public void onVisibilityAggregated(boolean isVisible) {
13134         super.onVisibilityAggregated(isVisible);
13135         startStopMarquee(isVisible);
13136     }
13137 
13138     /**
13139      * Use {@link BaseInputConnection#removeComposingSpans
13140      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
13141      * state from this text view.
13142      */
clearComposingText()13143     public void clearComposingText() {
13144         if (mText instanceof Spannable) {
13145             BaseInputConnection.removeComposingSpans(mSpannable);
13146         }
13147     }
13148 
13149     @Override
setSelected(boolean selected)13150     public void setSelected(boolean selected) {
13151         boolean wasSelected = isSelected();
13152 
13153         super.setSelected(selected);
13154 
13155         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
13156             if (selected) {
13157                 startMarquee();
13158             } else {
13159                 stopMarquee();
13160             }
13161         }
13162     }
13163 
13164     /**
13165      * Called from onTouchEvent() to prevent the touches by secondary fingers.
13166      * Dragging on handles can revise cursor/selection, so can dragging on the text view.
13167      * This method is a lock to avoid processing multiple fingers on both text view and handles.
13168      * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work.
13169      *
13170      * @param event The motion event that is being handled and carries the pointer info.
13171      * @param fromHandleView true if the event is delivered to selection handle or insertion
13172      * handle; false if this event is delivered to TextView.
13173      * @return Returns true to indicate that onTouchEvent() can continue processing the motion
13174      * event, otherwise false.
13175      *  - Always returns true for the first finger.
13176      *  - For secondary fingers, if the first or current finger is from TextView, returns false.
13177      *    This is to make touch mutually exclusive between the TextView and the handles, but
13178      *    not among the handles.
13179      */
isFromPrimePointer(MotionEvent event, boolean fromHandleView)13180     boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) {
13181         boolean res = true;
13182         if (mPrimePointerId == NO_POINTER_ID)  {
13183             mPrimePointerId = event.getPointerId(0);
13184             mIsPrimePointerFromHandleView = fromHandleView;
13185         } else if (mPrimePointerId != event.getPointerId(0)) {
13186             res = mIsPrimePointerFromHandleView && fromHandleView;
13187         }
13188         if (event.getActionMasked() == MotionEvent.ACTION_UP
13189             || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
13190             mPrimePointerId = -1;
13191         }
13192         return res;
13193     }
13194 
13195     @Override
onTouchEvent(MotionEvent event)13196     public boolean onTouchEvent(MotionEvent event) {
13197         if (DEBUG_CURSOR) {
13198             logCursor("onTouchEvent", "%d: %s (%f,%f)",
13199                     event.getSequenceNumber(),
13200                     MotionEvent.actionToString(event.getActionMasked()),
13201                     event.getX(), event.getY());
13202         }
13203         mLastInputSource = event.getSource();
13204         final int action = event.getActionMasked();
13205         if (mEditor != null) {
13206             if (!isFromPrimePointer(event, false)) {
13207                 return true;
13208             }
13209 
13210             mEditor.onTouchEvent(event);
13211 
13212             if (mEditor.mInsertionPointCursorController != null
13213                     && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
13214                 return true;
13215             }
13216             if (mEditor.mSelectionModifierCursorController != null
13217                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
13218                 return true;
13219             }
13220         }
13221 
13222         final boolean superResult = super.onTouchEvent(event);
13223         if (DEBUG_CURSOR) {
13224             logCursor("onTouchEvent", "superResult=%s", superResult);
13225         }
13226 
13227         /*
13228          * Don't handle the release after a long press, because it will move the selection away from
13229          * whatever the menu action was trying to affect. If the long press should have triggered an
13230          * insertion action mode, we can now actually show it.
13231          */
13232         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
13233             mEditor.mDiscardNextActionUp = false;
13234             if (DEBUG_CURSOR) {
13235                 logCursor("onTouchEvent", "release after long press detected");
13236             }
13237             if (mEditor.mIsInsertionActionModeStartPending) {
13238                 mEditor.startInsertionActionMode();
13239                 mEditor.mIsInsertionActionModeStartPending = false;
13240             }
13241             return superResult;
13242         }
13243 
13244         // At this point, the event is not a long press, otherwise it would be handled above.
13245         if (Flags.handwritingEndOfLineTap() && action == MotionEvent.ACTION_UP
13246                 && shouldStartHandwritingForEndOfLineTap(event)) {
13247             InputMethodManager imm = getInputMethodManager();
13248             if (imm != null) {
13249                 imm.startStylusHandwriting(this);
13250                 return true;
13251             }
13252         }
13253 
13254         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
13255                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
13256 
13257         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
13258                 && mText instanceof Spannable && mLayout != null) {
13259             boolean handled = false;
13260 
13261             if (mMovement != null) {
13262                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
13263             }
13264 
13265             final boolean textIsSelectable = isTextSelectable();
13266             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
13267                 // The LinkMovementMethod which should handle taps on links has not been installed
13268                 // on non editable text that support text selection.
13269                 // We reproduce its behavior here to open links for these.
13270                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
13271                     getSelectionEnd(), ClickableSpan.class);
13272 
13273                 if (links.length > 0) {
13274                     links[0].onClick(this);
13275                     handled = true;
13276                 }
13277             }
13278 
13279             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
13280                 // Show the IME, except when selecting in read-only text.
13281                 final InputMethodManager imm = getInputMethodManager();
13282                 viewClicked(imm);
13283                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null
13284                         && !showAutofillDialog()) {
13285                     imm.showSoftInput(this, 0);
13286                 }
13287 
13288                 // The above condition ensures that the mEditor is not null
13289                 mEditor.onTouchUpEvent(event);
13290 
13291                 handled = true;
13292             }
13293 
13294             if (handled) {
13295                 return true;
13296             }
13297         }
13298 
13299         return superResult;
13300     }
13301 
13302     /**
13303      * If handwriting is supported, the TextView is already focused and not empty, and the cursor is
13304      * at the end of a line, a stylus tap after the end of the line will trigger handwriting.
13305      */
shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent)13306     private boolean shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent) {
13307         if (!onCheckIsTextEditor()
13308                 || !isEnabled()
13309                 || !isAutoHandwritingEnabled()
13310                 || TextUtils.isEmpty(mText)
13311                 || didTouchFocusSelect()
13312                 || mLayout == null
13313                 || !actionUpEvent.isStylusPointer()) {
13314             return false;
13315         }
13316         int cursorOffset = getSelectionStart();
13317         if (cursorOffset < 0 || getSelectionEnd() != cursorOffset) {
13318             return false;
13319         }
13320         int cursorLine = mLayout.getLineForOffset(cursorOffset);
13321         int cursorLineEnd = mLayout.getLineEnd(cursorLine);
13322         if (cursorLine != mLayout.getLineCount() - 1) {
13323             cursorLineEnd--;
13324         }
13325         if (cursorLineEnd != cursorOffset) {
13326             return false;
13327         }
13328         // Check that the stylus down point is within the same line as the cursor.
13329         if (getLineAtCoordinate(actionUpEvent.getY()) != cursorLine) {
13330             return false;
13331         }
13332         // Check that the stylus down point is after the end of the line.
13333         float localX = convertToLocalHorizontalCoordinate(actionUpEvent.getX());
13334         if (mLayout.getParagraphDirection(cursorLine) == Layout.DIR_RIGHT_TO_LEFT
13335                 ? localX >= mLayout.getLineLeft(cursorLine)
13336                 : localX <= mLayout.getLineRight(cursorLine)) {
13337             return false;
13338         }
13339         return isStylusHandwritingAvailable();
13340     }
13341 
13342     /**
13343      * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
13344      *
13345      * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
13346      * @hide
13347      */
showUIForTouchScreen()13348     public final boolean showUIForTouchScreen() {
13349         return (mLastInputSource & InputDevice.SOURCE_TOUCHSCREEN)
13350                 == InputDevice.SOURCE_TOUCHSCREEN;
13351     }
13352 
13353     /**
13354      * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI.
13355      * If autofill suggestions are available when the user clicks on a field that supports filling
13356      * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area
13357      * to display the datasets, so it is easy for users to pay attention to the datasets and
13358      * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better
13359      * experience is not to show the IME if there is a fill dialog.
13360      */
showAutofillDialog()13361     private boolean showAutofillDialog() {
13362         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
13363         if (afm != null) {
13364             return afm.showAutofillDialog(this);
13365         }
13366         return false;
13367     }
13368 
13369     @Override
onGenericMotionEvent(MotionEvent event)13370     public boolean onGenericMotionEvent(MotionEvent event) {
13371         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
13372             try {
13373                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
13374                     return true;
13375                 }
13376             } catch (AbstractMethodError ex) {
13377                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
13378                 // Ignore its absence in case third party applications implemented the
13379                 // interface directly.
13380             }
13381         }
13382         return super.onGenericMotionEvent(event);
13383     }
13384 
13385     @Override
onCreateContextMenu(ContextMenu menu)13386     protected void onCreateContextMenu(ContextMenu menu) {
13387         if (mEditor != null) {
13388             mEditor.onCreateContextMenu(menu);
13389         }
13390     }
13391 
13392     @Override
showContextMenu()13393     public boolean showContextMenu() {
13394         if (mEditor != null) {
13395             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
13396         }
13397         return super.showContextMenu();
13398     }
13399 
13400     @Override
showContextMenu(float x, float y)13401     public boolean showContextMenu(float x, float y) {
13402         if (mEditor != null) {
13403             mEditor.setContextMenuAnchor(x, y);
13404         }
13405         return super.showContextMenu(x, y);
13406     }
13407 
13408     /**
13409      * @return True iff this TextView contains a text that can be edited, or if this is
13410      * a selectable TextView.
13411      */
13412     @UnsupportedAppUsage
isTextEditable()13413     boolean isTextEditable() {
13414         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
13415     }
13416 
13417     /**
13418      * @return true if this TextView could be filled by an Autofill service. Note that disabled
13419      * fields can still be filled.
13420      */
13421     @UnsupportedAppUsage
isTextAutofillable()13422     boolean isTextAutofillable() {
13423         return mText instanceof Editable && onCheckIsTextEditor();
13424     }
13425 
13426     /**
13427      * Returns true, only while processing a touch gesture, if the initial
13428      * touch down event caused focus to move to the text view and as a result
13429      * its selection changed.  Only valid while processing the touch gesture
13430      * of interest, in an editable text view.
13431      */
didTouchFocusSelect()13432     public boolean didTouchFocusSelect() {
13433         return mEditor != null && mEditor.mTouchFocusSelected;
13434     }
13435 
13436     @Override
cancelLongPress()13437     public void cancelLongPress() {
13438         super.cancelLongPress();
13439         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
13440     }
13441 
13442     @Override
onTrackballEvent(MotionEvent event)13443     public boolean onTrackballEvent(MotionEvent event) {
13444         if (mMovement != null && mSpannable != null && mLayout != null) {
13445             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
13446                 return true;
13447             }
13448         }
13449 
13450         return super.onTrackballEvent(event);
13451     }
13452 
13453     /**
13454      * Sets the Scroller used for producing a scrolling animation
13455      *
13456      * @param s A Scroller instance
13457      */
setScroller(Scroller s)13458     public void setScroller(Scroller s) {
13459         mScroller = s;
13460     }
13461 
13462     @Override
getLeftFadingEdgeStrength()13463     protected float getLeftFadingEdgeStrength() {
13464         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
13465             final Marquee marquee = mMarquee;
13466             if (marquee.shouldDrawLeftFade()) {
13467                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
13468             } else {
13469                 return 0.0f;
13470             }
13471         } else if (getLineCount() == 1) {
13472             final float lineLeft = getLayout().getLineLeft(0);
13473             if (lineLeft > mScrollX) return 0.0f;
13474             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
13475         }
13476         return super.getLeftFadingEdgeStrength();
13477     }
13478 
13479     @Override
getRightFadingEdgeStrength()13480     protected float getRightFadingEdgeStrength() {
13481         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
13482             final Marquee marquee = mMarquee;
13483             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
13484         } else if (getLineCount() == 1) {
13485             final float rightEdge = mScrollX +
13486                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
13487             final float lineRight = getLayout().getLineRight(0);
13488             if (lineRight < rightEdge) return 0.0f;
13489             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
13490         }
13491         return super.getRightFadingEdgeStrength();
13492     }
13493 
13494     /**
13495      * Calculates the fading edge strength as the ratio of the distance between two
13496      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
13497      * value for the distance calculation.
13498      *
13499      * @param position1 A horizontal position.
13500      * @param position2 A horizontal position.
13501      * @return Fading edge strength between [0.0f, 1.0f].
13502      */
13503     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)13504     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
13505         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
13506         if (horizontalFadingEdgeLength == 0) return 0.0f;
13507         final float diff = Math.abs(position1 - position2);
13508         if (diff > horizontalFadingEdgeLength) return 1.0f;
13509         return diff / horizontalFadingEdgeLength;
13510     }
13511 
isMarqueeFadeEnabled()13512     private boolean isMarqueeFadeEnabled() {
13513         return mEllipsize == TextUtils.TruncateAt.MARQUEE
13514                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
13515     }
13516 
13517     @Override
computeHorizontalScrollRange()13518     protected int computeHorizontalScrollRange() {
13519         if (mLayout != null) {
13520             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
13521                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
13522         }
13523 
13524         return super.computeHorizontalScrollRange();
13525     }
13526 
13527     @Override
computeVerticalScrollRange()13528     protected int computeVerticalScrollRange() {
13529         if (mLayout != null) {
13530             return mLayout.getHeight();
13531         }
13532         return super.computeVerticalScrollRange();
13533     }
13534 
13535     @Override
computeVerticalScrollExtent()13536     protected int computeVerticalScrollExtent() {
13537         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
13538     }
13539 
13540     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)13541     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
13542         super.findViewsWithText(outViews, searched, flags);
13543         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
13544                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
13545             String searchedLowerCase = searched.toString().toLowerCase();
13546             String textLowerCase = mText.toString().toLowerCase();
13547             if (textLowerCase.contains(searchedLowerCase)) {
13548                 outViews.add(this);
13549             }
13550         }
13551     }
13552 
13553     /**
13554      * Type of the text buffer that defines the characteristics of the text such as static,
13555      * styleable, or editable.
13556      */
13557     public enum BufferType {
13558         NORMAL, SPANNABLE, EDITABLE
13559     }
13560 
13561     /**
13562      * Returns the TextView_textColor attribute from the TypedArray, if set, or
13563      * the TextAppearance_textColor from the TextView_textAppearance attribute,
13564      * if TextView_textColor was not set directly.
13565      *
13566      * @removed
13567      */
getTextColors(Context context, TypedArray attrs)13568     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
13569         if (attrs == null) {
13570             // Preserve behavior prior to removal of this API.
13571             throw new NullPointerException();
13572         }
13573 
13574         // It's not safe to use this method from apps. The parameter 'attrs'
13575         // must have been obtained using the TextView filter array which is not
13576         // available to the SDK. As such, we grab a default TypedArray with the
13577         // right filter instead here.
13578         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
13579         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
13580         if (colors == null) {
13581             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
13582             if (ap != 0) {
13583                 final TypedArray appearance = context.obtainStyledAttributes(
13584                         ap, R.styleable.TextAppearance);
13585                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
13586                 appearance.recycle();
13587             }
13588         }
13589         a.recycle();
13590 
13591         return colors;
13592     }
13593 
13594     /**
13595      * Returns the default color from the TextView_textColor attribute from the
13596      * AttributeSet, if set, or the default color from the
13597      * TextAppearance_textColor from the TextView_textAppearance attribute, if
13598      * TextView_textColor was not set directly.
13599      *
13600      * @removed
13601      */
getTextColor(Context context, TypedArray attrs, int def)13602     public static int getTextColor(Context context, TypedArray attrs, int def) {
13603         final ColorStateList colors = getTextColors(context, attrs);
13604         if (colors == null) {
13605             return def;
13606         } else {
13607             return colors.getDefaultColor();
13608         }
13609     }
13610 
13611     @Override
onKeyShortcut(int keyCode, KeyEvent event)13612     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
13613         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
13614             // Handle Ctrl-only shortcuts.
13615             switch (keyCode) {
13616                 case KeyEvent.KEYCODE_A:
13617                     if (canSelectText()) {
13618                         return onTextContextMenuItem(ID_SELECT_ALL);
13619                     }
13620                     break;
13621                 case KeyEvent.KEYCODE_Z:
13622                     if (canUndo()) {
13623                         return onTextContextMenuItem(ID_UNDO);
13624                     }
13625                     break;
13626                 case KeyEvent.KEYCODE_X:
13627                     if (canCut()) {
13628                         return onTextContextMenuItem(ID_CUT);
13629                     }
13630                     break;
13631                 case KeyEvent.KEYCODE_C:
13632                     if (canCopy()) {
13633                         return onTextContextMenuItem(ID_COPY);
13634                     }
13635                     break;
13636                 case KeyEvent.KEYCODE_V:
13637                     if (canPaste()) {
13638                         return onTextContextMenuItem(ID_PASTE);
13639                     }
13640                     break;
13641                 case KeyEvent.KEYCODE_Y:
13642                     if (canRedo()) {
13643                         return onTextContextMenuItem(ID_REDO);
13644                     }
13645                     break;
13646             }
13647         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
13648             // Handle Ctrl-Shift shortcuts.
13649             switch (keyCode) {
13650                 case KeyEvent.KEYCODE_Z:
13651                     if (canRedo()) {
13652                         return onTextContextMenuItem(ID_REDO);
13653                     }
13654                     break;
13655                 case KeyEvent.KEYCODE_V:
13656                     if (canPaste()) {
13657                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
13658                     }
13659             }
13660         }
13661         return super.onKeyShortcut(keyCode, event);
13662     }
13663 
13664     /**
13665      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
13666      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
13667      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
13668      * sufficient.
13669      */
canSelectText()13670     boolean canSelectText() {
13671         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
13672     }
13673 
13674     /**
13675      * Test based on the <i>intrinsic</i> charateristics of the TextView.
13676      * The text must be spannable and the movement method must allow for arbitary selection.
13677      *
13678      * See also {@link #canSelectText()}.
13679      */
textCanBeSelected()13680     boolean textCanBeSelected() {
13681         // prepareCursorController() relies on this method.
13682         // If you change this condition, make sure prepareCursorController is called anywhere
13683         // the value of this condition might be changed.
13684         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
13685         return isTextEditable()
13686                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
13687     }
13688 
13689     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)13690     private Locale getTextServicesLocale(boolean allowNullLocale) {
13691         // Start fetching the text services locale asynchronously.
13692         updateTextServicesLocaleAsync();
13693         // If !allowNullLocale and there is no cached text services locale, just return the default
13694         // locale.
13695         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
13696                 : mCurrentSpellCheckerLocaleCache;
13697     }
13698 
13699     /**
13700      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
13701      * this {@link TextView}.
13702      *
13703      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
13704      * other apps may need to set this so that the system can user right user's resources and
13705      * services such as input methods and spell checkers.</p>
13706      *
13707      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
13708      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
13709      * @hide
13710      */
13711     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)13712     public final void setTextOperationUser(@Nullable UserHandle user) {
13713         if (Objects.equals(mTextOperationUser, user)) {
13714             return;
13715         }
13716         if (user != null && !Process.myUserHandle().equals(user)) {
13717             // Just for preventing people from accidentally using this hidden API without
13718             // the required permission.  The same permission is also checked in the system server.
13719             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
13720                     != PackageManager.PERMISSION_GRANTED) {
13721                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
13722                         + " userId=" + user.getIdentifier()
13723                         + " callingUserId" + UserHandle.myUserId());
13724             }
13725         }
13726         mTextOperationUser = user;
13727         // Invalidate some resources
13728         mCurrentSpellCheckerLocaleCache = null;
13729         if (mEditor != null) {
13730             mEditor.onTextOperationUserChanged();
13731         }
13732     }
13733 
13734     @Override
isAutoHandwritingEnabled()13735     public boolean isAutoHandwritingEnabled() {
13736         return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType();
13737     }
13738 
13739     /** @hide */
13740     @Override
shouldTrackHandwritingArea()13741     public boolean shouldTrackHandwritingArea() {
13742         // The handwriting initiator tracks all editable TextViews regardless of whether handwriting
13743         // is supported, so that it can show an error message for unsupported editable TextViews.
13744         return super.shouldTrackHandwritingArea()
13745                 || (Flags.handwritingUnsupportedMessage() && onCheckIsTextEditor());
13746     }
13747 
13748     /** @hide */
13749     @Override
isStylusHandwritingAvailable()13750     public boolean isStylusHandwritingAvailable() {
13751         if (mTextOperationUser == null) {
13752             return super.isStylusHandwritingAvailable();
13753         }
13754         final InputMethodManager imm = getInputMethodManager();
13755         return imm.isStylusHandwritingAvailableAsUser(mTextOperationUser);
13756     }
13757 
13758     @Nullable
getTextServicesManagerForUser()13759     final TextServicesManager getTextServicesManagerForUser() {
13760         return getServiceManagerForUser("android", TextServicesManager.class);
13761     }
13762 
13763     @Nullable
getClipboardManagerForUser()13764     final ClipboardManager getClipboardManagerForUser() {
13765         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
13766     }
13767 
13768     @Nullable
getTextClassificationManagerForUser()13769     final TextClassificationManager getTextClassificationManagerForUser() {
13770         return getServiceManagerForUser(
13771                 getContext().getPackageName(), TextClassificationManager.class);
13772     }
13773 
13774     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)13775     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
13776         if (mTextOperationUser == null) {
13777             return getContext().getSystemService(managerClazz);
13778         }
13779         try {
13780             Context context = getContext().createPackageContextAsUser(
13781                     packageName, 0 /* flags */, mTextOperationUser);
13782             return context.getSystemService(managerClazz);
13783         } catch (PackageManager.NameNotFoundException e) {
13784             return null;
13785         }
13786     }
13787 
13788     /**
13789      * Starts {@link Activity} as a text-operation user if it is specified with
13790      * {@link #setTextOperationUser(UserHandle)}.
13791      *
13792      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
13793      *
13794      * @param intent The description of the activity to start.
13795      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)13796     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
13797         if (mTextOperationUser != null) {
13798             getContext().startActivityAsUser(intent, mTextOperationUser);
13799         } else {
13800             getContext().startActivity(intent);
13801         }
13802     }
13803 
13804     /**
13805      * This is a temporary method. Future versions may support multi-locale text.
13806      * Caveat: This method may not return the latest text services locale, but this should be
13807      * acceptable and it's more important to make this method asynchronous.
13808      *
13809      * @return The locale that should be used for a word iterator
13810      * in this TextView, based on the current spell checker settings,
13811      * the current IME's locale, or the system default locale.
13812      * Please note that a word iterator in this TextView is different from another word iterator
13813      * used by SpellChecker.java of TextView. This method should be used for the former.
13814      * @hide
13815      */
13816     // TODO: Support multi-locale
13817     // TODO: Update the text services locale immediately after the keyboard locale is switched
13818     // by catching intent of keyboard switch event
getTextServicesLocale()13819     public Locale getTextServicesLocale() {
13820         return getTextServicesLocale(false /* allowNullLocale */);
13821     }
13822 
13823     /**
13824      * @return {@code true} if this TextView is specialized for showing and interacting with the
13825      * extracted text in a full-screen input method.
13826      * @hide
13827      */
isInExtractedMode()13828     public boolean isInExtractedMode() {
13829         return false;
13830     }
13831 
13832     /**
13833      * @return {@code true} if this widget supports auto-sizing text and has been configured to
13834      * auto-size.
13835      */
isAutoSizeEnabled()13836     private boolean isAutoSizeEnabled() {
13837         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
13838     }
13839 
13840     /**
13841      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
13842      * @hide
13843      */
supportsAutoSizeText()13844     protected boolean supportsAutoSizeText() {
13845         return true;
13846     }
13847 
13848     /**
13849      * This is a temporary method. Future versions may support multi-locale text.
13850      * Caveat: This method may not return the latest spell checker locale, but this should be
13851      * acceptable and it's more important to make this method asynchronous.
13852      *
13853      * @return The locale that should be used for a spell checker in this TextView,
13854      * based on the current spell checker settings, the current IME's locale, or the system default
13855      * locale.
13856      * @hide
13857      */
getSpellCheckerLocale()13858     public Locale getSpellCheckerLocale() {
13859         return getTextServicesLocale(true /* allowNullLocale */);
13860     }
13861 
updateTextServicesLocaleAsync()13862     private void updateTextServicesLocaleAsync() {
13863         // AsyncTask.execute() uses a serial executor which means we don't have
13864         // to lock around updateTextServicesLocaleLocked() to prevent it from
13865         // being executed n times in parallel.
13866         AsyncTask.execute(new Runnable() {
13867             @Override
13868             public void run() {
13869                 updateTextServicesLocaleLocked();
13870             }
13871         });
13872     }
13873 
13874     @UnsupportedAppUsage
updateTextServicesLocaleLocked()13875     private void updateTextServicesLocaleLocked() {
13876         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
13877         if (textServicesManager == null) {
13878             return;
13879         }
13880         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
13881         final Locale locale;
13882         if (subtype != null) {
13883             locale = subtype.getLocaleObject();
13884         } else {
13885             locale = null;
13886         }
13887         mCurrentSpellCheckerLocaleCache = locale;
13888     }
13889 
onLocaleChanged()13890     void onLocaleChanged() {
13891         mEditor.onLocaleChanged();
13892     }
13893 
13894     /**
13895      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
13896      * Made available to achieve a consistent behavior.
13897      * @hide
13898      */
getWordIterator()13899     public WordIterator getWordIterator() {
13900         if (mEditor != null) {
13901             return mEditor.getWordIterator();
13902         } else {
13903             return null;
13904         }
13905     }
13906 
13907     /** @hide */
13908     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)13909     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
13910         super.onPopulateAccessibilityEventInternal(event);
13911 
13912         if (this.isAccessibilityDataSensitive() && !event.isAccessibilityDataSensitive()) {
13913             // This view's accessibility data is sensitive, but another view that generated this
13914             // event is not, so don't append this view's text to the event in order to prevent
13915             // sharing this view's contents with non-accessibility-tool services.
13916             return;
13917         }
13918 
13919         final CharSequence text = getTextForAccessibility();
13920         if (!TextUtils.isEmpty(text)) {
13921             event.getText().add(text);
13922         }
13923     }
13924 
13925     @Override
getAccessibilityClassName()13926     public CharSequence getAccessibilityClassName() {
13927         return TextView.class.getName();
13928     }
13929 
13930     /** @hide */
13931     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)13932     protected void onProvideStructure(@NonNull ViewStructure structure,
13933             @ViewStructureType int viewFor, int flags) {
13934         super.onProvideStructure(structure, viewFor, flags);
13935 
13936         final boolean isPassword = hasPasswordTransformationMethod()
13937                 || isPasswordInputType(getInputType());
13938         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
13939                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13940             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13941                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
13942             }
13943             if (mTextId != Resources.ID_NULL) {
13944                 try {
13945                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
13946                 } catch (Resources.NotFoundException e) {
13947                     if (android.view.autofill.Helper.sVerbose) {
13948                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
13949                                 + mTextId + ": " + e.getMessage());
13950                     }
13951                 }
13952             }
13953             String[] mimeTypes = getReceiveContentMimeTypes();
13954             if (mimeTypes == null && mEditor != null) {
13955                 // If the app hasn't set a listener for receiving content on this view (ie,
13956                 // getReceiveContentMimeTypes() returns null), check if it implements the
13957                 // keyboard image API and, if possible, use those MIME types as fallback.
13958                 // This fallback is only in place for autofill, not other mechanisms for
13959                 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER
13960                 // in TextViewOnReceiveContentListener for more info.
13961                 mimeTypes = mEditor.getDefaultOnReceiveContentListener()
13962                         .getFallbackMimeTypesForAutofill(this);
13963             }
13964             structure.setReceiveContentMimeTypes(mimeTypes);
13965         }
13966 
13967         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
13968                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13969             if (mLayout == null) {
13970                 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13971                     Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
13972                 }
13973                 assumeLayout();
13974             }
13975             Layout layout = mLayout;
13976             final int lineCount = layout.getLineCount();
13977             if (lineCount <= 1) {
13978                 // Simple case: this is a single line.
13979                 final CharSequence text = getText();
13980                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13981                     structure.setText(text);
13982                 } else {
13983                     structure.setText(text, getSelectionStart(), getSelectionEnd());
13984                 }
13985             } else {
13986                 // Complex case: multi-line, could be scrolled or within a scroll container
13987                 // so some lines are not visible.
13988                 final int[] tmpCords = new int[2];
13989                 getLocationInWindow(tmpCords);
13990                 final int topWindowLocation = tmpCords[1];
13991                 View root = this;
13992                 ViewParent viewParent = getParent();
13993                 while (viewParent instanceof View) {
13994                     root = (View) viewParent;
13995                     viewParent = root.getParent();
13996                 }
13997                 final int windowHeight = root.getHeight();
13998                 final int topLine;
13999                 final int bottomLine;
14000                 if (topWindowLocation >= 0) {
14001                     // The top of the view is fully within its window; start text at line 0.
14002                     topLine = getLineAtCoordinateUnclamped(0);
14003                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
14004                 } else {
14005                     // The top of hte window has scrolled off the top of the window; figure out
14006                     // the starting line for this.
14007                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
14008                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
14009                 }
14010                 // We want to return some contextual lines above/below the lines that are
14011                 // actually visible.
14012                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
14013                 if (expandedTopLine < 0) {
14014                     expandedTopLine = 0;
14015                 }
14016                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
14017                 if (expandedBottomLine >= lineCount) {
14018                     expandedBottomLine = lineCount - 1;
14019                 }
14020 
14021                 // Convert lines into character offsets.
14022                 int expandedTopChar = transformedToOriginal(
14023                         layout.getLineStart(expandedTopLine),
14024                         OffsetMapping.MAP_STRATEGY_CHARACTER);
14025                 int expandedBottomChar = transformedToOriginal(
14026                         layout.getLineEnd(expandedBottomLine),
14027                         OffsetMapping.MAP_STRATEGY_CHARACTER);
14028 
14029                 // Take into account selection -- if there is a selection, we need to expand
14030                 // the text we are returning to include that selection.
14031                 final int selStart = getSelectionStart();
14032                 final int selEnd = getSelectionEnd();
14033                 if (selStart < selEnd) {
14034                     if (selStart < expandedTopChar) {
14035                         expandedTopChar = selStart;
14036                     }
14037                     if (selEnd > expandedBottomChar) {
14038                         expandedBottomChar = selEnd;
14039                     }
14040                 }
14041 
14042                 // Get the text and trim it to the range we are reporting.
14043                 CharSequence text = getText();
14044 
14045                 if (text != null) {
14046                     if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
14047                         // Cap the offsets to avoid an OOB exception. That can happen if the
14048                         // displayed/layout text, on which these offsets are calculated, is longer
14049                         // than the original text (such as when the view is translated by the
14050                         // platform intelligence).
14051                         // TODO(b/196433694): Figure out how to better handle the offset
14052                         // calculations for this case (so we don't unnecessarily cutoff the original
14053                         // text, for example).
14054                         expandedTopChar = Math.min(expandedTopChar, text.length());
14055                         expandedBottomChar = Math.min(expandedBottomChar, text.length());
14056                         text = text.subSequence(expandedTopChar, expandedBottomChar);
14057                     }
14058 
14059                     if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
14060                         structure.setText(text);
14061                     } else {
14062                         structure.setText(text,
14063                                 selStart - expandedTopChar,
14064                                 selEnd - expandedTopChar);
14065 
14066                         final int[] lineOffsets = new int[bottomLine - topLine + 1];
14067                         final int[] lineBaselines = new int[bottomLine - topLine + 1];
14068                         final int baselineOffset = getBaselineOffset();
14069                         for (int i = topLine; i <= bottomLine; i++) {
14070                             lineOffsets[i - topLine] = transformedToOriginal(layout.getLineStart(i),
14071                                     OffsetMapping.MAP_STRATEGY_CHARACTER);
14072                             lineBaselines[i - topLine] =
14073                                     layout.getLineBaseline(i) + baselineOffset;
14074                         }
14075                         structure.setTextLines(lineOffsets, lineBaselines);
14076                     }
14077                 }
14078             }
14079 
14080             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
14081                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
14082                 // Extract style information that applies to the TextView as a whole.
14083                 int style = 0;
14084                 int typefaceStyle = getTypefaceStyle();
14085                 if ((typefaceStyle & Typeface.BOLD) != 0) {
14086                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
14087                 }
14088                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
14089                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
14090                 }
14091 
14092                 // Global styles can also be set via TextView.setPaintFlags().
14093                 int paintFlags = mTextPaint.getFlags();
14094                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
14095                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
14096                 }
14097                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
14098                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
14099                 }
14100                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
14101                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
14102                 }
14103 
14104                 // TextView does not have its own text background color. A background is either part
14105                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
14106                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
14107                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
14108             }
14109             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
14110                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
14111                 structure.setMinTextEms(getMinEms());
14112                 structure.setMaxTextEms(getMaxEms());
14113                 int maxLength = -1;
14114                 for (InputFilter filter: getFilters()) {
14115                     if (filter instanceof InputFilter.LengthFilter) {
14116                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
14117                         break;
14118                     }
14119                 }
14120                 structure.setMaxTextLength(maxLength);
14121             }
14122         }
14123         if (mHintId != Resources.ID_NULL) {
14124             try {
14125                 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId));
14126             } catch (Resources.NotFoundException e) {
14127                 if (android.view.autofill.Helper.sVerbose) {
14128                     Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id "
14129                             + mHintId + ": " + e.getMessage());
14130                 }
14131             }
14132         }
14133         structure.setHint(getHint());
14134         structure.setInputType(getInputType());
14135     }
14136 
14137     /**
14138      * @hide
14139      */
14140     @VisibleForTesting
canRequestAutofill()14141     public boolean canRequestAutofill() {
14142         if (!isAutofillable()) {
14143             return false;
14144         }
14145         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
14146         if (afm != null) {
14147             return afm.isEnabled();
14148         }
14149         return false;
14150     }
14151 
requestAutofill()14152     private void requestAutofill() {
14153         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
14154         if (afm != null) {
14155             afm.requestAutofill(this);
14156         }
14157     }
14158 
14159     @Override
autofill(AutofillValue value)14160     public void autofill(AutofillValue value) {
14161         if (android.view.autofill.Helper.sVerbose) {
14162             Log.v(LOG_TAG, "autofill() called on textview for id:" + getAutofillId());
14163         }
14164         if (!isTextAutofillable()) {
14165             Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
14166             return;
14167         }
14168         if (!value.isText()) {
14169             Log.w(LOG_TAG, "value of type " + value.describeContents()
14170                     + " cannot be autofilled into " + this);
14171             return;
14172         }
14173         final ClipData clip = ClipData.newPlainText("", value.getTextValue());
14174         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build();
14175         performReceiveContent(payload);
14176     }
14177 
14178     @Override
getAutofillType()14179     public @AutofillType int getAutofillType() {
14180         return isTextAutofillable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
14181     }
14182 
14183     /**
14184      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
14185      * {@code char}s if longer.
14186      *
14187      * @return current text, {@code null} if the text is not editable
14188      *
14189      * @see View#getAutofillValue()
14190      */
14191     @Override
14192     @Nullable
getAutofillValue()14193     public AutofillValue getAutofillValue() {
14194         if (isTextAutofillable()) {
14195             final CharSequence text = TextUtils.trimToParcelableSize(getText());
14196             return AutofillValue.forText(text);
14197         }
14198         return null;
14199     }
14200 
14201     /** @hide */
14202     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)14203     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
14204         super.onInitializeAccessibilityEventInternal(event);
14205 
14206         final boolean isPassword = hasPasswordTransformationMethod();
14207         event.setPassword(isPassword);
14208 
14209         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
14210             event.setFromIndex(Selection.getSelectionStart(mText));
14211             event.setToIndex(Selection.getSelectionEnd(mText));
14212             event.setItemCount(mText.length());
14213         }
14214     }
14215 
14216     /** @hide */
14217     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)14218     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
14219         super.onInitializeAccessibilityNodeInfoInternal(info);
14220 
14221         final boolean isPassword = hasPasswordTransformationMethod();
14222         info.setPassword(isPassword);
14223         info.setText(getTextForAccessibility());
14224         info.setHintText(mHint);
14225         info.setShowingHintText(isShowingHint());
14226 
14227         if (mBufferType == BufferType.EDITABLE) {
14228             info.setEditable(true);
14229             if (isEnabled()) {
14230                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
14231             }
14232         }
14233 
14234         if (mEditor != null) {
14235             info.setInputType(mEditor.mInputType);
14236 
14237             if (mEditor.mError != null) {
14238                 info.setContentInvalid(true);
14239                 info.setError(mEditor.mError);
14240             }
14241             // TextView will expose this action if it is editable and has focus.
14242             if (isTextEditable() && isFocused()) {
14243                 CharSequence imeActionLabel = mContext.getResources().getString(
14244                         com.android.internal.R.string.keyboardview_keycode_enter);
14245                 if (getImeActionLabel() != null) {
14246                     imeActionLabel = getImeActionLabel();
14247                 }
14248                 AccessibilityNodeInfo.AccessibilityAction action =
14249                         new AccessibilityNodeInfo.AccessibilityAction(
14250                                 R.id.accessibilityActionImeEnter, imeActionLabel);
14251                 info.addAction(action);
14252             }
14253         }
14254 
14255         if (!TextUtils.isEmpty(mText)) {
14256             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
14257             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
14258             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
14259                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
14260                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
14261                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
14262                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
14263             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
14264             if (a11yCharacterInWindowApi()) {
14265                 info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED);
14266             } else {
14267                 info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS);
14268             }
14269             info.setTextSelectable(isTextSelectable() || isTextEditable());
14270         } else {
14271             info.setAvailableExtraData(Arrays.asList(
14272                     EXTRA_DATA_RENDERING_INFO_KEY
14273             ));
14274         }
14275 
14276         if (isFocused()) {
14277             if (canCopy()) {
14278                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
14279             }
14280             if (canPaste()) {
14281                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
14282             }
14283             if (canCut()) {
14284                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
14285             }
14286             if (canReplace()) {
14287                 info.addAction(
14288                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS);
14289             }
14290             if (canShare()) {
14291                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
14292                         ACCESSIBILITY_ACTION_SHARE,
14293                         getResources().getString(com.android.internal.R.string.share)));
14294             }
14295             if (canProcessText()) {  // also implies mEditor is not null.
14296                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
14297                 mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info);
14298             }
14299         }
14300 
14301         // Check for known input filter types.
14302         final int numFilters = mFilters.length;
14303         for (int i = 0; i < numFilters; i++) {
14304             final InputFilter filter = mFilters[i];
14305             if (filter instanceof InputFilter.LengthFilter) {
14306                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
14307             }
14308         }
14309 
14310         if (!isSingleLine()) {
14311             info.setMultiLine(true);
14312         }
14313 
14314         // A view should not be exposed as clickable/long-clickable to a service because of a
14315         // LinkMovementMethod or because it has selectable and non-editable text.
14316         if ((info.isClickable() || info.isLongClickable())
14317                 && (mMovement instanceof LinkMovementMethod
14318                 || (isTextSelectable() && !isTextEditable()))) {
14319             if (!hasOnClickListeners()) {
14320                 info.setClickable(false);
14321                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
14322             }
14323             if (!hasOnLongClickListeners()) {
14324                 info.setLongClickable(false);
14325                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
14326             }
14327         }
14328     }
14329 
14330     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)14331     public void addExtraDataToAccessibilityNodeInfo(
14332             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
14333         boolean isCharacterLocationKey = extraDataKey.equals(
14334                 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
14335         boolean isCharacterLocationInWindowKey = (a11yCharacterInWindowApi() && extraDataKey.equals(
14336                 EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY));
14337         if (arguments != null && (isCharacterLocationKey || isCharacterLocationInWindowKey)) {
14338             int positionInfoStartIndex = arguments.getInt(
14339                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
14340             int positionInfoLength = arguments.getInt(
14341                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
14342             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
14343                     || (positionInfoStartIndex >= mText.length())) {
14344                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
14345                 return;
14346             }
14347             RectF[] boundingRects = new RectF[positionInfoLength];
14348             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
14349             populateCharacterBounds(builder, positionInfoStartIndex,
14350                     Math.min(positionInfoStartIndex + positionInfoLength, length()),
14351                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
14352             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
14353             for (int i = 0; i < positionInfoLength; i++) {
14354                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
14355                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
14356                     RectF bounds = cursorAnchorInfo
14357                             .getCharacterBounds(positionInfoStartIndex + i);
14358                     if (bounds != null) {
14359                         if (isCharacterLocationKey) {
14360                             mapRectFromViewToScreenCoords(bounds, true);
14361                         } else if (isCharacterLocationInWindowKey) {
14362                             mapRectFromViewToWindowCoords(bounds, true);
14363                         }
14364                         boundingRects[i] = bounds;
14365                     }
14366                 }
14367             }
14368             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
14369             return;
14370         }
14371         if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) {
14372             final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo =
14373                     AccessibilityNodeInfo.ExtraRenderingInfo.obtain();
14374             extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height);
14375             extraRenderingInfo.setTextSizeInPx(getTextSize());
14376             extraRenderingInfo.setTextSizeUnit(getTextSizeUnit());
14377             info.setExtraRenderingInfo(extraRenderingInfo);
14378         }
14379     }
14380 
14381     /**
14382      * Don't use, it returns wrong result when the view is scaled. This method can be removed once
14383      * Flags.handwritingGestureWithTransformation is enabled.
14384      * Assume
14385      * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
14386      * This method obtains the view's visible rectangle whereas the method
14387      * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
14388      *
14389      * @return true if at least part of the text content is visible; false if the text content is
14390      * completely clipped or translated out of the visible area.
14391      */
getViewVisibleRect(Rect rect)14392     private boolean getViewVisibleRect(Rect rect) {
14393         if (!getLocalVisibleRect(rect)) {
14394             return false;
14395         }
14396         // getLocalVisibleRect returns a rect relative to the unscrolled left top corner of the
14397         // view. In other words, the returned rectangle's origin point is (-scrollX, -scrollY) in
14398         // view's coordinates. So we need to offset it with the negative scrolled amount to convert
14399         // it to view's coordinate.
14400         rect.offset(-getScrollX(), -getScrollY());
14401         return true;
14402     }
14403 
14404     /**
14405      * Don't use, it returns wrong result when view is scaled. This method can be removed once
14406      * Flags.handwritingGestureWithTransformation is enabled.
14407      * Helper method to set {@code rect} to the text content's non-clipped area in the view's
14408      * coordinates.
14409      *
14410      * @return true if at least part of the text content is visible; false if the text content is
14411      * completely clipped or translated out of the visible area.
14412      */
getContentVisibleRect(Rect rect)14413     private boolean getContentVisibleRect(Rect rect) {
14414         if (!getViewVisibleRect(rect)) {
14415             return false;
14416         }
14417         // Clip the view's visible rect with the text layout's visible rect.
14418         return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
14419                 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
14420     }
14421 
getEditorAndHandwritingBounds(@onNull RectF editorBounds, @Nullable RectF handwritingBounds)14422     private boolean getEditorAndHandwritingBounds(@NonNull RectF editorBounds,
14423             @Nullable RectF handwritingBounds) {
14424         if (mTempRect == null) {
14425             mTempRect = new Rect();
14426         }
14427         Rect rect = mTempRect;
14428         if (!getGlobalVisibleRect(rect)) {
14429             return false;
14430         }
14431         if (mTempMatrix == null) {
14432             mTempMatrix = new Matrix();
14433         }
14434 
14435         Matrix matrix = mTempMatrix;
14436         matrix.reset();
14437         transformMatrixRootToLocal(matrix);
14438         editorBounds.set(rect);
14439         // When the view has transformations like scaleX/scaleY computing the global visible
14440         // rectangle will already apply the transformations. The getLocalVisibleRect only offsets
14441         // the global rectangle to local. And the result is wrong the View is scaled.
14442         //
14443         // This approach use the local transformation matrix to map the global rectangle to
14444         // local instead.
14445         //
14446         // Note: it doesn't work well with rotation. Because Rect must be
14447         // axis-aligned, when a rotated Rect becomes quadrilateral, the quadrilateral's
14448         // bounding box is stored at Rect instead. It makes the returned Rect larger than
14449         // the correct size.
14450         matrix.mapRect(editorBounds);
14451 
14452         if (handwritingBounds != null) {
14453             // Similar to editorBounds, handwritingBounds must be computed in global coordinates
14454             // and then converted back to local coordinates. Otherwise, if the view is scaled,
14455             // the handwritingBoundsOffsets are also scaled, which is not the expected behavior.
14456             handwritingBounds.top = rect.top -  getHandwritingBoundsOffsetTop();
14457             handwritingBounds.left = rect.left - getHandwritingBoundsOffsetLeft();
14458             handwritingBounds.bottom = rect.bottom + getHandwritingBoundsOffsetBottom();
14459             handwritingBounds.right = rect.right + getHandwritingBoundsOffsetRight();
14460             matrix.mapRect(handwritingBounds);
14461         }
14462         return true;
14463     }
14464 
getContentVisibleRect(RectF rect)14465     private boolean getContentVisibleRect(RectF rect) {
14466         if (!getEditorAndHandwritingBounds(rect, /* handwritingBounds= */null)) {
14467             return false;
14468         }
14469         // Clip the view's visible rect with the text layout's visible rect.
14470         return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
14471                 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
14472     }
14473 
14474     /**
14475      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
14476      *
14477      * @param builder The builder to populate
14478      * @param startIndex The starting character index to populate
14479      * @param endIndex The ending character index to populate
14480      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
14481      * content
14482      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
14483      * @hide
14484      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)14485     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
14486             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
14487             float viewportToContentVerticalOffset) {
14488         if (isOffsetMappingAvailable()) {
14489             // The text is transformed, and has different length, we don't support
14490             // character bounds in this case yet.
14491             return;
14492         }
14493         final RectF visibleRect = new RectF();
14494 
14495         if (Flags.handwritingGestureWithTransformation()) {
14496             getContentVisibleRect(visibleRect);
14497         } else {
14498             final Rect rect = new Rect();
14499             getContentVisibleRect(rect);
14500             visibleRect.set(rect);
14501         }
14502 
14503         final float[] characterBounds = getCharacterBounds(startIndex, endIndex,
14504                 viewportToContentHorizontalOffset, viewportToContentVerticalOffset);
14505         final int limit = endIndex - startIndex;
14506         for (int offset = 0; offset < limit; ++offset) {
14507             final float left = characterBounds[offset * 4];
14508             final float top = characterBounds[offset * 4 + 1];
14509             final float right = characterBounds[offset * 4 + 2];
14510             final float bottom = characterBounds[offset * 4 + 3];
14511 
14512             final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom);
14513             final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom);
14514             int characterBoundsFlags = 0;
14515             if (hasVisibleRegion) {
14516                 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
14517             }
14518             if (hasInVisibleRegion) {
14519                 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
14520             }
14521 
14522             if (mLayout.isRtlCharAt(offset)) {
14523                 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
14524             }
14525             builder.addCharacterBounds(offset + startIndex, left, top, right, bottom,
14526                     characterBoundsFlags);
14527         }
14528     }
14529 
14530     /**
14531      * Return the bounds of the characters in the given range, in TextView's coordinates.
14532      *
14533      * @param start the start index of the interested text range, inclusive.
14534      * @param end the end index of the interested text range, exclusive.
14535      * @param layoutLeft the left of the given {@code layout} in the editor view's coordinates.
14536      * @param layoutTop  the top of the given {@code layout} in the editor view's coordinates.
14537      * @return the character bounds stored in a flattened array, in the editor view's coordinates.
14538      */
getCharacterBounds(int start, int end, float layoutLeft, float layoutTop)14539     private float[] getCharacterBounds(int start, int end, float layoutLeft, float layoutTop) {
14540         final float[] characterBounds = new float[4 * (end - start)];
14541         mLayout.fillCharacterBounds(start, end, characterBounds, 0);
14542         for (int offset = 0; offset < end - start; ++offset) {
14543             characterBounds[4 * offset] += layoutLeft;
14544             characterBounds[4 * offset + 1] += layoutTop;
14545             characterBounds[4 * offset + 2] += layoutLeft;
14546             characterBounds[4 * offset + 3] += layoutTop;
14547         }
14548         return characterBounds;
14549     }
14550 
14551     /**
14552      * Compute {@link CursorAnchorInfo} from this {@link TextView}.
14553      *
14554      * @param filter the {@link CursorAnchorInfo} update filter which specified the needed
14555      *               information from IME.
14556      * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build
14557      *                                the result {@link CursorAnchorInfo}.
14558      * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen
14559      *                           matrix.
14560      * @return the result {@link CursorAnchorInfo} to be passed to IME.
14561      * @hide
14562      */
14563     @VisibleForTesting
14564     @Nullable
getCursorAnchorInfo(@nputConnection.CursorUpdateFilter int filter, @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, @NonNull Matrix viewToScreenMatrix)14565     public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter,
14566             @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder,
14567             @NonNull Matrix viewToScreenMatrix) {
14568         Layout layout = getLayout();
14569         if (layout == null) {
14570             return null;
14571         }
14572         boolean includeEditorBounds =
14573                 (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0;
14574         boolean includeCharacterBounds =
14575                 (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0;
14576         boolean includeInsertionMarker =
14577                 (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
14578         boolean includeVisibleLineBounds =
14579                 (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0;
14580         boolean includeTextAppearance =
14581                 (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0;
14582         boolean includeAll =
14583                 (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker
14584                         && !includeVisibleLineBounds && !includeTextAppearance);
14585 
14586         includeEditorBounds |= includeAll;
14587         includeCharacterBounds |= includeAll;
14588         includeInsertionMarker |= includeAll;
14589         includeVisibleLineBounds |= includeAll;
14590         includeTextAppearance |= includeAll;
14591 
14592         final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder;
14593         builder.reset();
14594 
14595         final int selectionStart = getSelectionStart();
14596         builder.setSelectionRange(selectionStart, getSelectionEnd());
14597 
14598         // Construct transformation matrix from view local coordinates to screen coordinates.
14599         viewToScreenMatrix.reset();
14600         transformMatrixToGlobal(viewToScreenMatrix);
14601         builder.setMatrix(viewToScreenMatrix);
14602 
14603         if (includeEditorBounds) {
14604             final RectF editorBounds = new RectF();
14605             final RectF handwritingBounds = new RectF();
14606             if (Flags.handwritingGestureWithTransformation()) {
14607                 getEditorAndHandwritingBounds(editorBounds, handwritingBounds);
14608             } else {
14609                 if (mTempRect == null) {
14610                     mTempRect = new Rect();
14611                 }
14612                 final Rect bounds = mTempRect;
14613 
14614                 // If the editor is not visible at all, return empty rectangles. We still need to
14615                 // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
14616                 if (getViewVisibleRect(bounds)) {
14617                     editorBounds.set(bounds);
14618                     handwritingBounds.set(editorBounds);
14619                     handwritingBounds.top -= getHandwritingBoundsOffsetTop();
14620                     handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
14621                     handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
14622                     handwritingBounds.right += getHandwritingBoundsOffsetRight();
14623                 }
14624             }
14625             EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
14626             EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
14627                     .setHandwritingBounds(handwritingBounds).build();
14628             builder.setEditorBoundsInfo(editorBoundsInfo);
14629         }
14630 
14631         if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) {
14632             final float viewportToContentHorizontalOffset =
14633                     viewportToContentHorizontalOffset();
14634             final float viewportToContentVerticalOffset =
14635                     viewportToContentVerticalOffset();
14636             final boolean isTextTransformed = (getTransformationMethod() != null
14637                     && getTransformed() instanceof OffsetMapping);
14638             if (includeCharacterBounds && !isTextTransformed) {
14639                 final CharSequence text = getText();
14640                 if (text instanceof Spannable) {
14641                     final Spannable sp = (Spannable) text;
14642                     int composingTextStart = EditableInputConnection.getComposingSpanStart(sp);
14643                     int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp);
14644                     if (composingTextEnd < composingTextStart) {
14645                         final int temp = composingTextEnd;
14646                         composingTextEnd = composingTextStart;
14647                         composingTextStart = temp;
14648                     }
14649                     final boolean hasComposingText =
14650                             (0 <= composingTextStart) && (composingTextStart
14651                                     < composingTextEnd);
14652                     if (hasComposingText) {
14653                         final CharSequence composingText = text.subSequence(composingTextStart,
14654                                 composingTextEnd);
14655                         builder.setComposingText(composingTextStart, composingText);
14656                         populateCharacterBounds(builder, composingTextStart,
14657                                 composingTextEnd, viewportToContentHorizontalOffset,
14658                                 viewportToContentVerticalOffset);
14659                     }
14660                 }
14661             }
14662 
14663             if (includeInsertionMarker) {
14664                 // Treat selectionStart as the insertion point.
14665                 if (0 <= selectionStart) {
14666                     final int offsetTransformed = originalToTransformed(
14667                             selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR);
14668                     final int line = layout.getLineForOffset(offsetTransformed);
14669                     final float insertionMarkerX =
14670                             layout.getPrimaryHorizontal(
14671                                             offsetTransformed, layout.shouldClampCursor(line))
14672                                     + viewportToContentHorizontalOffset;
14673                     final float insertionMarkerTop = layout.getLineTop(line)
14674                             + viewportToContentVerticalOffset;
14675                     final float insertionMarkerBaseline = layout.getLineBaseline(line)
14676                             + viewportToContentVerticalOffset;
14677                     final float insertionMarkerBottom =
14678                             layout.getLineBottom(line, /* includeLineSpacing= */ false)
14679                                     + viewportToContentVerticalOffset;
14680                     final boolean isTopVisible =
14681                             isPositionVisible(insertionMarkerX, insertionMarkerTop);
14682                     final boolean isBottomVisible =
14683                             isPositionVisible(insertionMarkerX, insertionMarkerBottom);
14684                     int insertionMarkerFlags = 0;
14685                     if (isTopVisible || isBottomVisible) {
14686                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
14687                     }
14688                     if (!isTopVisible || !isBottomVisible) {
14689                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
14690                     }
14691                     if (layout.isRtlCharAt(offsetTransformed)) {
14692                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
14693                     }
14694                     builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
14695                             insertionMarkerBaseline, insertionMarkerBottom,
14696                             insertionMarkerFlags);
14697                 }
14698             }
14699 
14700             if (includeVisibleLineBounds) {
14701                 if (Flags.handwritingGestureWithTransformation()) {
14702                     RectF visibleRect = new RectF();
14703                     if (getContentVisibleRect(visibleRect)) {
14704                         // Subtract the viewportToContentVerticalOffset to convert the view
14705                         // coordinates to layout coordinates.
14706                         final float visibleTop =
14707                                 visibleRect.top - viewportToContentVerticalOffset;
14708                         final float visibleBottom =
14709                                 visibleRect.bottom - viewportToContentVerticalOffset;
14710                         final int firstLine =
14711                                 layout.getLineForVertical((int) Math.floor(visibleTop));
14712                         final int lastLine =
14713                                 layout.getLineForVertical((int) Math.ceil(visibleBottom));
14714 
14715                         for (int line = firstLine; line <= lastLine; ++line) {
14716                             final float left = layout.getLineLeft(line)
14717                                     + viewportToContentHorizontalOffset;
14718                             final float top = layout.getLineTop(line)
14719                                     + viewportToContentVerticalOffset;
14720                             final float right = layout.getLineRight(line)
14721                                     + viewportToContentHorizontalOffset;
14722                             final float bottom = layout.getLineBottom(line, false)
14723                                     + viewportToContentVerticalOffset;
14724                             builder.addVisibleLineBounds(left, top, right, bottom);
14725                         }
14726                     }
14727                 } else {
14728                     final Rect visibleRect = new Rect();
14729                     if (getContentVisibleRect(visibleRect)) {
14730                         // Subtract the viewportToContentVerticalOffset to convert the view
14731                         // coordinates to layout coordinates.
14732                         final float visibleTop =
14733                                 visibleRect.top - viewportToContentVerticalOffset;
14734                         final float visibleBottom =
14735                                 visibleRect.bottom - viewportToContentVerticalOffset;
14736                         final int firstLine =
14737                                 layout.getLineForVertical((int) Math.floor(visibleTop));
14738                         final int lastLine =
14739                                 layout.getLineForVertical((int) Math.ceil(visibleBottom));
14740 
14741                         for (int line = firstLine; line <= lastLine; ++line) {
14742                             final float left = layout.getLineLeft(line)
14743                                     + viewportToContentHorizontalOffset;
14744                             final float top = layout.getLineTop(line)
14745                                     + viewportToContentVerticalOffset;
14746                             final float right = layout.getLineRight(line)
14747                                     + viewportToContentHorizontalOffset;
14748                             final float bottom = layout.getLineBottom(line, false)
14749                                     + viewportToContentVerticalOffset;
14750                             builder.addVisibleLineBounds(left, top, right, bottom);
14751                         }
14752                     }
14753                 }
14754             }
14755         }
14756 
14757         if (includeTextAppearance) {
14758             builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this));
14759         }
14760         return builder.build();
14761     }
14762 
14763     /**
14764      * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}.
14765      * @hide
14766      */
getTextBoundsInfo(@onNull RectF bounds)14767     public TextBoundsInfo getTextBoundsInfo(@NonNull RectF bounds) {
14768         final Layout layout = getLayout();
14769         if (layout == null) {
14770             // No valid text layout, return null.
14771             return null;
14772         }
14773         final CharSequence text = layout.getText();
14774         if (text == null || isOffsetMappingAvailable()) {
14775             // The text is Null or the text has been transformed. Can't provide TextBoundsInfo.
14776             return null;
14777         }
14778 
14779         final Matrix localToGlobalMatrix = new Matrix();
14780         transformMatrixToGlobal(localToGlobalMatrix);
14781         final Matrix globalToLocalMatrix = new Matrix();
14782         if (!localToGlobalMatrix.invert(globalToLocalMatrix)) {
14783             // Can't map global rectF to local coordinates, this is almost impossible in practice.
14784             return null;
14785         }
14786 
14787         final float layoutLeft = viewportToContentHorizontalOffset();
14788         final float layoutTop = viewportToContentVerticalOffset();
14789 
14790         final RectF localBounds = new RectF(bounds);
14791         globalToLocalMatrix.mapRect(localBounds);
14792         localBounds.offset(-layoutLeft, -layoutTop);
14793 
14794         // Text length is 0. There is no character bounds, return empty TextBoundsInfo.
14795         // rectF doesn't intersect with the layout, return empty TextBoundsInfo.
14796         if (!localBounds.intersects(0f, 0f, layout.getWidth(), layout.getHeight())
14797                 || text.length() == 0) {
14798             final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 0);
14799             final SegmentFinder emptySegmentFinder =
14800                     new SegmentFinder.PrescribedSegmentFinder(new int[0]);
14801             builder.setMatrix(localToGlobalMatrix)
14802                     .setCharacterBounds(new float[0])
14803                     .setCharacterBidiLevel(new int[0])
14804                     .setCharacterFlags(new int[0])
14805                     .setGraphemeSegmentFinder(emptySegmentFinder)
14806                     .setLineSegmentFinder(emptySegmentFinder)
14807                     .setWordSegmentFinder(emptySegmentFinder);
14808             return  builder.build();
14809         }
14810 
14811         final int startLine = layout.getLineForVertical((int) Math.floor(localBounds.top));
14812         final int endLine = layout.getLineForVertical((int) Math.floor(localBounds.bottom));
14813         final int start = layout.getLineStart(startLine);
14814         final int end = layout.getLineEnd(endLine);
14815 
14816         // Compute character bounds.
14817         final float[] characterBounds = getCharacterBounds(start, end, layoutLeft, layoutTop);
14818 
14819         // Compute character flags and BiDi levels.
14820         final int[] characterFlags = new int[end - start];
14821         final int[] characterBidiLevels = new int[end - start];
14822         for (int line = startLine; line <= endLine; ++line) {
14823             final int lineStart = layout.getLineStart(line);
14824             final int lineEnd = layout.getLineEnd(line);
14825             final Layout.Directions directions = layout.getLineDirections(line);
14826             for (int i = 0; i < directions.getRunCount(); ++i) {
14827                 final int runStart = directions.getRunStart(i) + lineStart;
14828                 final int runEnd = Math.min(runStart + directions.getRunLength(i), lineEnd);
14829                 final int runLevel = directions.getRunLevel(i);
14830                 Arrays.fill(characterBidiLevels, runStart - start, runEnd - start, runLevel);
14831             }
14832 
14833             final boolean lineIsRtl =
14834                     layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT;
14835             for (int index = lineStart; index < lineEnd; ++index) {
14836                 int flags = 0;
14837                 if (TextUtils.isWhitespace(text.charAt(index))) {
14838                     flags |= TextBoundsInfo.FLAG_CHARACTER_WHITESPACE;
14839                 }
14840                 if (TextUtils.isPunctuation(Character.codePointAt(text, index))) {
14841                     flags |= TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION;
14842                 }
14843                 if (TextUtils.isNewline(Character.codePointAt(text, index))) {
14844                     flags |= TextBoundsInfo.FLAG_CHARACTER_LINEFEED;
14845                 }
14846                 if (lineIsRtl) {
14847                     flags |= TextBoundsInfo.FLAG_LINE_IS_RTL;
14848                 }
14849                 characterFlags[index - start] = flags;
14850             }
14851         }
14852 
14853         // Create grapheme SegmentFinder.
14854         final SegmentFinder graphemeSegmentFinder =
14855                 new GraphemeClusterSegmentFinder(text, layout.getPaint());
14856 
14857         // Create word SegmentFinder.
14858         final WordIterator wordIterator = getWordIterator();
14859         wordIterator.setCharSequence(text, 0, text.length());
14860         final SegmentFinder wordSegmentFinder = new WordSegmentFinder(text, wordIterator);
14861 
14862         // Create line SegmentFinder.
14863         final int lineCount = endLine - startLine + 1;
14864         final int[] lineRanges = new int[2 * lineCount];
14865         for (int line = startLine; line <= endLine; ++line) {
14866             final int offset = line - startLine;
14867             lineRanges[2 * offset] = layout.getLineStart(line);
14868             lineRanges[2 * offset + 1] = layout.getLineEnd(line);
14869         }
14870         final SegmentFinder lineSegmentFinder =
14871                 new SegmentFinder.PrescribedSegmentFinder(lineRanges);
14872 
14873         return new TextBoundsInfo.Builder(start, end)
14874                 .setMatrix(localToGlobalMatrix)
14875                 .setCharacterBounds(characterBounds)
14876                 .setCharacterBidiLevel(characterBidiLevels)
14877                 .setCharacterFlags(characterFlags)
14878                 .setGraphemeSegmentFinder(graphemeSegmentFinder)
14879                 .setLineSegmentFinder(lineSegmentFinder)
14880                 .setWordSegmentFinder(wordSegmentFinder)
14881                 .build();
14882     }
14883 
14884     /**
14885      * @hide
14886      */
isPositionVisible(final float positionX, final float positionY)14887     public boolean isPositionVisible(final float positionX, final float positionY) {
14888         synchronized (TEMP_POSITION) {
14889             final float[] position = TEMP_POSITION;
14890             position[0] = positionX;
14891             position[1] = positionY;
14892             View view = this;
14893 
14894             while (view != null) {
14895                 if (view != this) {
14896                     // Local scroll is already taken into account in positionX/Y
14897                     position[0] -= view.getScrollX();
14898                     position[1] -= view.getScrollY();
14899                 }
14900 
14901                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
14902                         || position[1] > view.getHeight()) {
14903                     return false;
14904                 }
14905 
14906                 if (!view.getMatrix().isIdentity()) {
14907                     view.getMatrix().mapPoints(position);
14908                 }
14909 
14910                 position[0] += view.getLeft();
14911                 position[1] += view.getTop();
14912 
14913                 final ViewParent parent = view.getParent();
14914                 if (parent instanceof View) {
14915                     view = (View) parent;
14916                 } else {
14917                     // We've reached the ViewRoot, stop iterating
14918                     view = null;
14919                 }
14920             }
14921         }
14922 
14923         // We've been able to walk up the view hierarchy and the position was never clipped
14924         return true;
14925     }
14926 
14927     /**
14928      * Performs an accessibility action after it has been offered to the
14929      * delegate.
14930      *
14931      * @hide
14932      */
14933     @Override
performAccessibilityActionInternal(int action, Bundle arguments)14934     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
14935         if (mEditor != null) {
14936             if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)
14937                     || mEditor.performSmartActionsAccessibilityAction(action)) {
14938                 return true;
14939             }
14940         }
14941         switch (action) {
14942             case AccessibilityNodeInfo.ACTION_CLICK: {
14943                 return performAccessibilityActionClick(arguments);
14944             }
14945             case AccessibilityNodeInfo.ACTION_COPY: {
14946                 if (isFocused() && canCopy()) {
14947                     if (onTextContextMenuItem(ID_COPY)) {
14948                         return true;
14949                     }
14950                 }
14951             } return false;
14952             case AccessibilityNodeInfo.ACTION_PASTE: {
14953                 if (isFocused() && canPaste()) {
14954                     if (onTextContextMenuItem(ID_PASTE)) {
14955                         return true;
14956                     }
14957                 }
14958             } return false;
14959             case AccessibilityNodeInfo.ACTION_CUT: {
14960                 if (isFocused() && canCut()) {
14961                     if (onTextContextMenuItem(ID_CUT)) {
14962                         return true;
14963                     }
14964                 }
14965             } return false;
14966             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
14967                 ensureIterableTextForAccessibilitySelectable();
14968                 CharSequence text = getIterableTextForAccessibility();
14969                 if (text == null) {
14970                     return false;
14971                 }
14972                 final int start = (arguments != null) ? arguments.getInt(
14973                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
14974                 final int end = (arguments != null) ? arguments.getInt(
14975                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
14976                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
14977                     // No arguments clears the selection.
14978                     if (start == end && end == -1) {
14979                         Selection.removeSelection((Spannable) text);
14980                         return true;
14981                     }
14982                     if (start >= 0 && start <= end && end <= text.length()) {
14983                         requestFocusOnNonEditableSelectableText();
14984                         Selection.setSelection((Spannable) text, start, end);
14985                         // Make sure selection mode is engaged.
14986                         if (mEditor != null) {
14987                             mEditor.startSelectionActionModeAsync(false);
14988                         }
14989                         return true;
14990                     }
14991                 }
14992             } return false;
14993             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
14994             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
14995                 ensureIterableTextForAccessibilitySelectable();
14996                 return super.performAccessibilityActionInternal(action, arguments);
14997             }
14998             case ACCESSIBILITY_ACTION_SHARE: {
14999                 if (isFocused() && canShare()) {
15000                     if (onTextContextMenuItem(ID_SHARE)) {
15001                         return true;
15002                     }
15003                 }
15004             } return false;
15005             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
15006                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
15007                     return false;
15008                 }
15009                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
15010                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
15011                 setText(text);
15012                 if (mText != null) {
15013                     int updatedTextLength = mText.length();
15014                     if (updatedTextLength > 0) {
15015                         Selection.setSelection(mSpannable, updatedTextLength);
15016                     }
15017                 }
15018             } return true;
15019             case R.id.accessibilityActionImeEnter: {
15020                 if (isFocused() && isTextEditable()) {
15021                     onEditorAction(getImeActionId());
15022                 }
15023             } return true;
15024             case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
15025                 if (isLongClickable()) {
15026                     boolean handled;
15027                     if (isEnabled() && (mBufferType == BufferType.EDITABLE)) {
15028                         mEditor.mIsBeingLongClickedByAccessibility = true;
15029                         try {
15030                             handled = performLongClick();
15031                         } finally {
15032                             mEditor.mIsBeingLongClickedByAccessibility = false;
15033                         }
15034                     } else {
15035                         handled = performLongClick();
15036                     }
15037                     return handled;
15038                 }
15039             }
15040             return false;
15041             default: {
15042                 // New ids have static blocks to assign values, so they can't be used in a case
15043                 // block.
15044                 if (action == R.id.accessibilityActionShowTextSuggestions) {
15045                     return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE);
15046                 }
15047                 return super.performAccessibilityActionInternal(action, arguments);
15048             }
15049         }
15050     }
15051 
performAccessibilityActionClick(Bundle arguments)15052     private boolean performAccessibilityActionClick(Bundle arguments) {
15053         boolean handled = false;
15054 
15055         if (!isEnabled()) {
15056             return false;
15057         }
15058 
15059         if (isClickable() || isLongClickable()) {
15060             // Simulate View.onTouchEvent for an ACTION_UP event
15061             if (isFocusable() && !isFocused()) {
15062                 requestFocus();
15063             }
15064 
15065             performClick();
15066             handled = true;
15067         }
15068 
15069         // Show the IME, except when selecting in read-only text.
15070         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
15071                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
15072             final InputMethodManager imm = getInputMethodManager();
15073             viewClicked(imm);
15074             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
15075                 handled |= imm.showSoftInput(this, 0);
15076             }
15077         }
15078 
15079         return handled;
15080     }
15081 
requestFocusOnNonEditableSelectableText()15082     private void requestFocusOnNonEditableSelectableText() {
15083         if (!isTextEditable() && isTextSelectable()) {
15084             if (!isEnabled()) {
15085                 return;
15086             }
15087 
15088             if (isFocusable() && !isFocused()) {
15089                 requestFocus();
15090             }
15091         }
15092     }
15093 
hasSpannableText()15094     private boolean hasSpannableText() {
15095         return mText != null && mText instanceof Spannable;
15096     }
15097 
15098     /** @hide */
15099     @Override
sendAccessibilityEventInternal(int eventType)15100     public void sendAccessibilityEventInternal(int eventType) {
15101         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
15102             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
15103         }
15104 
15105         super.sendAccessibilityEventInternal(eventType);
15106     }
15107 
15108     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)15109     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
15110         // Do not send scroll events since first they are not interesting for
15111         // accessibility and second such events a generated too frequently.
15112         // For details see the implementation of bringTextIntoView().
15113         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
15114             return;
15115         }
15116         super.sendAccessibilityEventUnchecked(event);
15117     }
15118 
15119     /**
15120      * Returns the text that should be exposed to accessibility services.
15121      * <p>
15122      * This approximates what is displayed visually.
15123      *
15124      * @return the text that should be exposed to accessibility services, may
15125      *         be {@code null} if no text is set
15126      */
15127     @Nullable
15128     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTextForAccessibility()15129     private CharSequence getTextForAccessibility() {
15130         // If the text is empty, we must be showing the hint text.
15131         if (TextUtils.isEmpty(mText)) {
15132             return mHint;
15133         }
15134 
15135         // Otherwise, return whatever text is being displayed.
15136         return TextUtils.trimToParcelableSize(mTransformed);
15137     }
15138 
isVisibleToAccessibility()15139     boolean isVisibleToAccessibility() {
15140         return AccessibilityManager.getInstance(mContext).isEnabled()
15141                 && (isFocused() || (isSelected() && isShown()));
15142     }
15143 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)15144     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
15145             int fromIndex, int removedCount, int addedCount) {
15146         AccessibilityEvent event =
15147                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
15148         event.setFromIndex(fromIndex);
15149         event.setRemovedCount(removedCount);
15150         event.setAddedCount(addedCount);
15151         event.setBeforeText(beforeText);
15152         sendAccessibilityEventUnchecked(event);
15153     }
15154 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int toIndex)15155     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
15156             int fromIndex, int toIndex) {
15157         AccessibilityEvent event =
15158                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
15159         event.setFromIndex(fromIndex);
15160         event.setToIndex(toIndex);
15161         event.setBeforeText(beforeText);
15162         sendAccessibilityEventUnchecked(event);
15163     }
15164 
getInputMethodManager()15165     private InputMethodManager getInputMethodManager() {
15166         return getContext().getSystemService(InputMethodManager.class);
15167     }
15168 
15169     /**
15170      * Returns whether this text view is a current input method target.  The
15171      * default implementation just checks with {@link InputMethodManager}.
15172      * @return True if the TextView is a current input method target; false otherwise.
15173      */
isInputMethodTarget()15174     public boolean isInputMethodTarget() {
15175         InputMethodManager imm = getInputMethodManager();
15176         return imm != null && imm.isActive(this);
15177     }
15178 
15179     static final int ID_SELECT_ALL = android.R.id.selectAll;
15180     static final int ID_UNDO = android.R.id.undo;
15181     static final int ID_REDO = android.R.id.redo;
15182     static final int ID_CUT = android.R.id.cut;
15183     static final int ID_COPY = android.R.id.copy;
15184     static final int ID_PASTE = android.R.id.paste;
15185     static final int ID_SHARE = android.R.id.shareText;
15186     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
15187     static final int ID_REPLACE = android.R.id.replaceText;
15188     static final int ID_ASSIST = android.R.id.textAssist;
15189     static final int ID_AUTOFILL = android.R.id.autofill;
15190 
15191     /**
15192      * Called when a context menu option for the text view is selected.  Currently
15193      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
15194      * {@link android.R.id#copy}, {@link android.R.id#paste},
15195      * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or
15196      * {@link android.R.id#shareText}.
15197      *
15198      * @return true if the context menu item action was performed.
15199      */
onTextContextMenuItem(int id)15200     public boolean onTextContextMenuItem(int id) {
15201         int min = 0;
15202         int max = mText.length();
15203 
15204         if (isFocused()) {
15205             final int selStart = getSelectionStart();
15206             final int selEnd = getSelectionEnd();
15207 
15208             min = Math.max(0, Math.min(selStart, selEnd));
15209             max = Math.max(0, Math.max(selStart, selEnd));
15210         }
15211 
15212         switch (id) {
15213             case ID_SELECT_ALL:
15214                 final boolean hadSelection = hasSelection();
15215                 selectAllText();
15216                 if (mEditor != null && hadSelection) {
15217                     mEditor.invalidateActionModeAsync();
15218                 }
15219                 return true;
15220 
15221             case ID_UNDO:
15222                 if (mEditor != null) {
15223                     mEditor.undo();
15224                 }
15225                 return true;  // Returns true even if nothing was undone.
15226 
15227             case ID_REDO:
15228                 if (mEditor != null) {
15229                     mEditor.redo();
15230                 }
15231                 return true;  // Returns true even if nothing was undone.
15232 
15233             case ID_PASTE:
15234                 paste(true /* withFormatting */);
15235                 return true;
15236 
15237             case ID_PASTE_AS_PLAIN_TEXT:
15238                 paste(false /* withFormatting */);
15239                 return true;
15240 
15241             case ID_CUT:
15242                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
15243                 if (setPrimaryClip(cutData)) {
15244                     deleteText_internal(min, max);
15245                 } else {
15246                     Toast.makeText(getContext(),
15247                             com.android.internal.R.string.failed_to_copy_to_clipboard,
15248                             Toast.LENGTH_SHORT).show();
15249                 }
15250                 return true;
15251 
15252             case ID_COPY:
15253                 // For link action mode in a non-selectable/non-focusable TextView,
15254                 // make sure that we set the appropriate min/max.
15255                 final int selStart = getSelectionStart();
15256                 final int selEnd = getSelectionEnd();
15257                 min = Math.max(0, Math.min(selStart, selEnd));
15258                 max = Math.max(0, Math.max(selStart, selEnd));
15259                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
15260                 if (setPrimaryClip(copyData)) {
15261                     stopTextActionMode();
15262                 } else {
15263                     Toast.makeText(getContext(),
15264                             com.android.internal.R.string.failed_to_copy_to_clipboard,
15265                             Toast.LENGTH_SHORT).show();
15266                 }
15267                 return true;
15268 
15269             case ID_REPLACE:
15270                 if (mEditor != null) {
15271                     mEditor.replace();
15272                 }
15273                 return true;
15274 
15275             case ID_SHARE:
15276                 shareSelectedText();
15277                 return true;
15278 
15279             case ID_AUTOFILL:
15280                 requestAutofill();
15281                 stopTextActionMode();
15282                 return true;
15283         }
15284         return false;
15285     }
15286 
15287     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTransformedText(int start, int end)15288     CharSequence getTransformedText(int start, int end) {
15289         return removeSuggestionSpans(mTransformed.subSequence(start, end));
15290     }
15291 
15292     @Override
performLongClick()15293     public boolean performLongClick() {
15294         if (DEBUG_CURSOR) {
15295             logCursor("performLongClick", null);
15296         }
15297 
15298         boolean handled = false;
15299         boolean performedHapticFeedback = false;
15300 
15301         if (mEditor != null) {
15302             mEditor.mIsBeingLongClicked = true;
15303         }
15304 
15305         if (super.performLongClick()) {
15306             handled = true;
15307             performedHapticFeedback = true;
15308         }
15309 
15310         if (mEditor != null) {
15311             handled |= mEditor.performLongClick(handled);
15312             mEditor.mIsBeingLongClicked = false;
15313         }
15314 
15315         if (handled) {
15316             if (!performedHapticFeedback) {
15317               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
15318             }
15319             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
15320         } else {
15321             MetricsLogger.action(
15322                     mContext,
15323                     MetricsEvent.TEXT_LONGPRESS,
15324                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
15325         }
15326 
15327         return handled;
15328     }
15329 
15330     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)15331     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
15332         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
15333         if (mEditor != null) {
15334             mEditor.onScrollChanged();
15335         }
15336     }
15337 
15338     /**
15339      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
15340      * by the IME or by the spell checker as the user types. This is done by adding
15341      * {@link SuggestionSpan}s to the text.
15342      *
15343      * When suggestions are enabled (default), this list of suggestions will be displayed when the
15344      * user asks for them on these parts of the text. This value depends on the inputType of this
15345      * TextView.
15346      *
15347      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
15348      *
15349      * In addition, the type variation must be one of
15350      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
15351      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
15352      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
15353      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
15354      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
15355      *
15356      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
15357      *
15358      * @return true if the suggestions popup window is enabled, based on the inputType.
15359      */
isSuggestionsEnabled()15360     public boolean isSuggestionsEnabled() {
15361         if (mEditor == null) return false;
15362         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
15363             return false;
15364         }
15365         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
15366 
15367         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
15368         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
15369                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
15370                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
15371                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
15372                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
15373     }
15374 
15375     /**
15376      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
15377      * selection is initiated in this View.
15378      *
15379      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
15380      * Paste, Replace and Share actions, depending on what this View supports.
15381      *
15382      * <p>A custom implementation can add new entries in the default menu in its
15383      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
15384      * method. The default actions can also be removed from the menu using
15385      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
15386      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
15387      * {@link android.R.id#pasteAsPlainText} (starting at API level 23),
15388      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
15389      *
15390      * <p>Returning false from
15391      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
15392      * will prevent the action mode from being started.
15393      *
15394      * <p>Action click events should be handled by the custom implementation of
15395      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
15396      * android.view.MenuItem)}.
15397      *
15398      * <p>Note that text selection mode is not started when a TextView receives focus and the
15399      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
15400      * that case, to allow for quick replacement.
15401      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)15402     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
15403         createEditorIfNeeded();
15404         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
15405     }
15406 
15407     /**
15408      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
15409      *
15410      * @return The current custom selection callback.
15411      */
getCustomSelectionActionModeCallback()15412     public ActionMode.Callback getCustomSelectionActionModeCallback() {
15413         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
15414     }
15415 
15416     /**
15417      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
15418      * insertion is initiated in this View.
15419      * The standard implementation populates the menu with a subset of Select All,
15420      * Paste and Replace actions, depending on what this View supports.
15421      *
15422      * <p>A custom implementation can add new entries in the default menu in its
15423      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
15424      * android.view.Menu)} method. The default actions can also be removed from the menu using
15425      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
15426      * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API
15427      * level 23) or {@link android.R.id#replaceText} ids as parameters.</p>
15428      *
15429      * <p>Returning false from
15430      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
15431      * android.view.Menu)} will prevent the action mode from being started.</p>
15432      *
15433      * <p>Action click events should be handled by the custom implementation of
15434      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
15435      * android.view.MenuItem)}.</p>
15436      *
15437      * <p>Note that text insertion mode is not started when a TextView receives focus and the
15438      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
15439      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)15440     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
15441         createEditorIfNeeded();
15442         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
15443     }
15444 
15445     /**
15446      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
15447      *
15448      * @return The current custom insertion callback.
15449      */
getCustomInsertionActionModeCallback()15450     public ActionMode.Callback getCustomInsertionActionModeCallback() {
15451         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
15452     }
15453 
15454     /**
15455      * Sets the {@link TextClassifier} for this TextView.
15456      */
setTextClassifier(@ullable TextClassifier textClassifier)15457     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
15458         mTextClassifier = textClassifier;
15459     }
15460 
15461     /**
15462      * Returns the {@link TextClassifier} used by this TextView.
15463      * If no TextClassifier has been set, this TextView uses the default set by the
15464      * {@link TextClassificationManager}.
15465      */
15466     @NonNull
getTextClassifier()15467     public TextClassifier getTextClassifier() {
15468         if (mTextClassifier == null) {
15469             final TextClassificationManager tcm = getTextClassificationManagerForUser();
15470             if (tcm != null) {
15471                 return tcm.getTextClassifier();
15472             }
15473             return TextClassifier.NO_OP;
15474         }
15475         return mTextClassifier;
15476     }
15477 
15478     /**
15479      * Returns a session-aware text classifier.
15480      * This method creates one if none already exists or the current one is destroyed.
15481      */
15482     @NonNull
getTextClassificationSession()15483     TextClassifier getTextClassificationSession() {
15484         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
15485             final TextClassificationManager tcm = getTextClassificationManagerForUser();
15486             if (tcm != null) {
15487                 final String widgetType;
15488                 if (isTextEditable()) {
15489                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
15490                 } else if (isTextSelectable()) {
15491                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
15492                 } else {
15493                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
15494                 }
15495                 mTextClassificationContext = new TextClassificationContext.Builder(
15496                         mContext.getPackageName(), widgetType)
15497                         .build();
15498                 if (mTextClassifier != null) {
15499                     mTextClassificationSession = tcm.createTextClassificationSession(
15500                             mTextClassificationContext, mTextClassifier);
15501                 } else {
15502                     mTextClassificationSession = tcm.createTextClassificationSession(
15503                             mTextClassificationContext);
15504                 }
15505             } else {
15506                 mTextClassificationSession = TextClassifier.NO_OP;
15507             }
15508         }
15509         return mTextClassificationSession;
15510     }
15511 
15512     /**
15513      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
15514      * @see #getTextClassificationSession()
15515      */
15516     @Nullable
getTextClassificationContext()15517     TextClassificationContext getTextClassificationContext() {
15518         return mTextClassificationContext;
15519     }
15520 
15521     /**
15522      * Returns true if this TextView uses a no-op TextClassifier.
15523      */
usesNoOpTextClassifier()15524     boolean usesNoOpTextClassifier() {
15525         return getTextClassifier() == TextClassifier.NO_OP;
15526     }
15527 
15528     /**
15529      * Starts an ActionMode for the specified TextLinkSpan.
15530      *
15531      * @return Whether or not we're attempting to start the action mode.
15532      * @hide
15533      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)15534     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
15535         Preconditions.checkNotNull(clickedSpan);
15536 
15537         if (!(mText instanceof Spanned)) {
15538             return false;
15539         }
15540 
15541         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
15542         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
15543 
15544         if (start < 0 || end > mText.length() || start >= end) {
15545             return false;
15546         }
15547 
15548         createEditorIfNeeded();
15549         mEditor.startLinkActionModeAsync(start, end);
15550         return true;
15551     }
15552 
15553     /**
15554      * Handles a click on the specified TextLinkSpan.
15555      *
15556      * @return Whether or not the click is being handled.
15557      * @hide
15558      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)15559     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
15560         Preconditions.checkNotNull(clickedSpan);
15561         if (mText instanceof Spanned) {
15562             final Spanned spanned = (Spanned) mText;
15563             final int start = spanned.getSpanStart(clickedSpan);
15564             final int end = spanned.getSpanEnd(clickedSpan);
15565             if (start >= 0 && end <= mText.length() && start < end) {
15566                 final TextClassification.Request request = new TextClassification.Request.Builder(
15567                         mText, start, end)
15568                         .setDefaultLocales(getTextLocales())
15569                         .build();
15570                 final Supplier<TextClassification> supplier = () ->
15571                         getTextClassificationSession().classifyText(request);
15572                 final Consumer<TextClassification> consumer = classification -> {
15573                     if (classification != null) {
15574                         if (!classification.getActions().isEmpty()) {
15575                             try {
15576                                 classification.getActions().get(0).getActionIntent().send();
15577                             } catch (PendingIntent.CanceledException e) {
15578                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
15579                             }
15580                         } else {
15581                             Log.d(LOG_TAG, "No link action to perform");
15582                         }
15583                     } else {
15584                         // classification == null
15585                         Log.d(LOG_TAG, "Timeout while classifying text");
15586                     }
15587                 };
15588                 CompletableFuture.supplyAsync(supplier)
15589                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
15590                         .thenAccept(consumer);
15591                 return true;
15592             }
15593         }
15594         return false;
15595     }
15596 
15597     /**
15598      * @hide
15599      */
15600     @UnsupportedAppUsage
stopTextActionMode()15601     protected void stopTextActionMode() {
15602         if (mEditor != null) {
15603             mEditor.stopTextActionMode();
15604         }
15605     }
15606 
15607     /** @hide */
hideFloatingToolbar(int durationMs)15608     public void hideFloatingToolbar(int durationMs) {
15609         if (mEditor != null) {
15610             mEditor.hideFloatingToolbar(durationMs);
15611         }
15612     }
15613 
15614     /** @hide */
15615     @VisibleForTesting
canUndo()15616     public boolean canUndo() {
15617         return mEditor != null && mEditor.canUndo();
15618     }
15619 
15620     /** @hide */
15621     @VisibleForTesting
canRedo()15622     public boolean canRedo() {
15623         return mEditor != null && mEditor.canRedo();
15624     }
15625 
15626     /** @hide */
15627     @VisibleForTesting
canCut()15628     public boolean canCut() {
15629         if (hasPasswordTransformationMethod()) {
15630             return false;
15631         }
15632 
15633         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
15634                 && mEditor.mKeyListener != null) {
15635             return true;
15636         }
15637 
15638         return false;
15639     }
15640 
15641     /** @hide */
15642     @VisibleForTesting
canCopy()15643     public boolean canCopy() {
15644         if (hasPasswordTransformationMethod()) {
15645             return false;
15646         }
15647 
15648         if (mText.length() > 0 && hasSelection() && mEditor != null) {
15649             return true;
15650         }
15651 
15652         return false;
15653     }
15654 
canReplace()15655     boolean canReplace() {
15656         if (hasPasswordTransformationMethod()) {
15657             return false;
15658         }
15659 
15660         return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null)
15661                 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions();
15662     }
15663 
15664     /** @hide */
15665     @VisibleForTesting
canShare()15666     public boolean canShare() {
15667         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()
15668                 || !getContext().getResources().getBoolean(
15669                 com.android.internal.R.bool.config_textShareSupported)) {
15670             return false;
15671         }
15672         return canCopy();
15673     }
15674 
isDeviceProvisioned()15675     boolean isDeviceProvisioned() {
15676         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
15677             mDeviceProvisionedState = Settings.Global.getInt(
15678                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
15679                     ? DEVICE_PROVISIONED_YES
15680                     : DEVICE_PROVISIONED_NO;
15681         }
15682         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
15683     }
15684 
15685     /** @hide */
15686     @VisibleForTesting
15687     @UnsupportedAppUsage
canPaste()15688     public boolean canPaste() {
15689         return (mText instanceof Editable
15690                 && mEditor != null && mEditor.mKeyListener != null
15691                 && getSelectionStart() >= 0
15692                 && getSelectionEnd() >= 0
15693                 && getClipboardManagerForUser().hasPrimaryClip());
15694     }
15695 
15696     /** @hide */
15697     @VisibleForTesting
canPasteAsPlainText()15698     public boolean canPasteAsPlainText() {
15699         if (!canPaste()) {
15700             return false;
15701         }
15702 
15703         final ClipDescription description =
15704                 getClipboardManagerForUser().getPrimaryClipDescription();
15705         if (description == null) {
15706             return false;
15707         }
15708         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
15709         return (isPlainType && description.isStyledText())
15710                 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
15711     }
15712 
canProcessText()15713     boolean canProcessText() {
15714         if (getId() == View.NO_ID) {
15715             return false;
15716         }
15717         return canShare();
15718     }
15719 
15720     /** @hide */
15721     @VisibleForTesting
canSelectAllText()15722     public boolean canSelectAllText() {
15723         return canSelectText() && !hasPasswordTransformationMethod()
15724                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
15725     }
15726 
selectAllText()15727     boolean selectAllText() {
15728         if (mEditor != null) {
15729             // Hide the toolbar before changing the selection to avoid flickering.
15730             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
15731         }
15732         final int length = mText.length();
15733         Selection.setSelection(mSpannable, 0, length);
15734         return length > 0;
15735     }
15736 
paste(boolean withFormatting)15737     private void paste(boolean withFormatting) {
15738         ClipboardManager clipboard = getClipboardManagerForUser();
15739         ClipData clip = clipboard.getPrimaryClip();
15740         if (clip == null) {
15741             return;
15742         }
15743         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD)
15744                 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT)
15745                 .build();
15746         performReceiveContent(payload);
15747         sLastCutCopyOrTextChangedTime = 0;
15748     }
15749 
shareSelectedText()15750     private void shareSelectedText() {
15751         String selectedText = getSelectedText();
15752         if (selectedText != null && !selectedText.isEmpty()) {
15753             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
15754             sharingIntent.setType("text/plain");
15755             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
15756             selectedText = TextUtils.trimToParcelableSize(selectedText);
15757             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
15758             getContext().startActivity(Intent.createChooser(sharingIntent, null));
15759             Selection.setSelection(mSpannable, getSelectionEnd());
15760         }
15761     }
15762 
15763     @CheckResult
setPrimaryClip(ClipData clip)15764     private boolean setPrimaryClip(ClipData clip) {
15765         ClipboardManager clipboard = getClipboardManagerForUser();
15766         try {
15767             clipboard.setPrimaryClip(clip);
15768         } catch (Throwable t) {
15769             return false;
15770         }
15771         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
15772         return true;
15773     }
15774 
15775     /**
15776      * Get the character offset closest to the specified absolute position. A typical use case is to
15777      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
15778      *
15779      * @param x The horizontal absolute position of a point on screen
15780      * @param y The vertical absolute position of a point on screen
15781      * @return the character offset for the character whose position is closest to the specified
15782      *  position. Returns -1 if there is no layout.
15783      */
getOffsetForPosition(float x, float y)15784     public int getOffsetForPosition(float x, float y) {
15785         if (getLayout() == null) return -1;
15786         final int line = getLineAtCoordinate(y);
15787         final int offset = getOffsetAtCoordinate(line, x);
15788         return offset;
15789     }
15790 
convertToLocalHorizontalCoordinate(float x)15791     float convertToLocalHorizontalCoordinate(float x) {
15792         x -= getTotalPaddingLeft();
15793         // Clamp the position to inside of the view.
15794         x = Math.max(0.0f, x);
15795         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
15796         x += getScrollX();
15797         return x;
15798     }
15799 
15800     /** @hide */
15801     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getLineAtCoordinate(float y)15802     public int getLineAtCoordinate(float y) {
15803         y -= getTotalPaddingTop();
15804         // Clamp the position to inside of the view.
15805         y = Math.max(0.0f, y);
15806         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
15807         y += getScrollY();
15808         return getLayout().getLineForVertical((int) y);
15809     }
15810 
getLineAtCoordinateUnclamped(float y)15811     int getLineAtCoordinateUnclamped(float y) {
15812         y -= getTotalPaddingTop();
15813         y += getScrollY();
15814         return getLayout().getLineForVertical((int) y);
15815     }
15816 
getOffsetAtCoordinate(int line, float x)15817     int getOffsetAtCoordinate(int line, float x) {
15818         x = convertToLocalHorizontalCoordinate(x);
15819         final int offset = getLayout().getOffsetForHorizontal(line, x);
15820         return transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
15821     }
15822 
15823     /**
15824      * Convenient method to convert an offset on the transformed text to the original text.
15825      * @hide
15826      */
transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy)15827     public int transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy) {
15828         if (getTransformationMethod() == null) {
15829             return offset;
15830         }
15831         if (mTransformed instanceof OffsetMapping) {
15832             final OffsetMapping transformedText = (OffsetMapping) mTransformed;
15833             return transformedText.transformedToOriginal(offset, strategy);
15834         }
15835         return offset;
15836     }
15837 
15838     /**
15839      * Convenient method to convert an offset on the original text to the transformed text.
15840      * @hide
15841      */
originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy)15842     public int originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy) {
15843         if (getTransformationMethod() == null) {
15844             return offset;
15845         }
15846         if (mTransformed instanceof OffsetMapping) {
15847             final OffsetMapping transformedText = (OffsetMapping) mTransformed;
15848             return transformedText.originalToTransformed(offset, strategy);
15849         }
15850         return offset;
15851     }
15852     /**
15853      * Handles drag events sent by the system following a call to
15854      * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
15855      * startDragAndDrop()}.
15856      *
15857      * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent}
15858      * implementation.
15859      *
15860      * <p>If this text view is editable, accepts all drag actions (returns true for an
15861      * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all
15862      * subsequent drag events). While the drag is in progress, updates the cursor position
15863      * to follow the touch location. Once a drop event is received, handles content insertion
15864      * via {@link #performReceiveContent}.
15865      *
15866      * @param event The {@link android.view.DragEvent} sent by the system.
15867      * The {@link android.view.DragEvent#getAction()} method returns an action type constant
15868      * defined in DragEvent, indicating the type of drag event represented by this object.
15869      * @return Returns true if this text view is editable and delegates to super otherwise.
15870      * See {@link View#onDragEvent}.
15871      */
15872     @Override
onDragEvent(DragEvent event)15873     public boolean onDragEvent(DragEvent event) {
15874         if (mEditor == null || !mEditor.hasInsertionController()) {
15875             // If this TextView is not editable, defer to the default View implementation. This
15876             // will check for the presence of an OnReceiveContentListener and accept/reject
15877             // drag events depending on whether the listener is/isn't set.
15878             return super.onDragEvent(event);
15879         }
15880         switch (event.getAction()) {
15881             case DragEvent.ACTION_DRAG_STARTED:
15882                 return true;
15883 
15884             case DragEvent.ACTION_DRAG_ENTERED:
15885                 TextView.this.requestFocus();
15886                 return true;
15887 
15888             case DragEvent.ACTION_DRAG_LOCATION:
15889                 if (mText instanceof Spannable) {
15890                     final int offset = getOffsetForPosition(event.getX(), event.getY());
15891                     Selection.setSelection(mSpannable, offset);
15892                 }
15893                 return true;
15894 
15895             case DragEvent.ACTION_DROP:
15896                 if (mEditor != null) mEditor.onDrop(event);
15897                 return true;
15898 
15899             case DragEvent.ACTION_DRAG_ENDED:
15900             case DragEvent.ACTION_DRAG_EXITED:
15901             default:
15902                 return true;
15903         }
15904     }
15905 
isInBatchEditMode()15906     boolean isInBatchEditMode() {
15907         if (mEditor == null) return false;
15908         final Editor.InputMethodState ims = mEditor.mInputMethodState;
15909         if (ims != null) {
15910             return ims.mBatchEditNesting > 0;
15911         }
15912         return mEditor.mInBatchEditControllers;
15913     }
15914 
15915     @Override
onRtlPropertiesChanged(int layoutDirection)15916     public void onRtlPropertiesChanged(int layoutDirection) {
15917         super.onRtlPropertiesChanged(layoutDirection);
15918 
15919         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
15920         if (mTextDir != newTextDir) {
15921             mTextDir = newTextDir;
15922             if (mLayout != null) {
15923                 checkForRelayout();
15924             }
15925         }
15926     }
15927 
15928     /**
15929      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
15930      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
15931      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
15932      * return value may not be the same as the one TextView uses if the View's layout direction is
15933      * not resolved or detached from parent root view.
15934      */
getTextDirectionHeuristic()15935     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
15936         if (hasPasswordTransformationMethod()) {
15937             // passwords fields should be LTR
15938             return TextDirectionHeuristics.LTR;
15939         }
15940 
15941         if (mEditor != null
15942                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
15943                     == EditorInfo.TYPE_CLASS_PHONE) {
15944             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
15945             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
15946             // RTL digits.
15947             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
15948             final String zero = symbols.getDigitStrings()[0];
15949             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
15950             // direction.
15951             final int firstCodepoint = zero.codePointAt(0);
15952             final byte digitDirection = Character.getDirectionality(firstCodepoint);
15953             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
15954                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
15955                 return TextDirectionHeuristics.RTL;
15956             } else {
15957                 return TextDirectionHeuristics.LTR;
15958             }
15959         }
15960 
15961         // Always need to resolve layout direction first
15962         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
15963 
15964         // Now, we can select the heuristic
15965         switch (getTextDirection()) {
15966             default:
15967             case TEXT_DIRECTION_FIRST_STRONG:
15968                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
15969                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
15970             case TEXT_DIRECTION_ANY_RTL:
15971                 return TextDirectionHeuristics.ANYRTL_LTR;
15972             case TEXT_DIRECTION_LTR:
15973                 return TextDirectionHeuristics.LTR;
15974             case TEXT_DIRECTION_RTL:
15975                 return TextDirectionHeuristics.RTL;
15976             case TEXT_DIRECTION_LOCALE:
15977                 return TextDirectionHeuristics.LOCALE;
15978             case TEXT_DIRECTION_FIRST_STRONG_LTR:
15979                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
15980             case TEXT_DIRECTION_FIRST_STRONG_RTL:
15981                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
15982         }
15983     }
15984 
15985     /**
15986      * @hide
15987      */
15988     @Override
onResolveDrawables(int layoutDirection)15989     public void onResolveDrawables(int layoutDirection) {
15990         // No need to resolve twice
15991         if (mLastLayoutDirection == layoutDirection) {
15992             return;
15993         }
15994         mLastLayoutDirection = layoutDirection;
15995 
15996         // Resolve drawables
15997         if (mDrawables != null) {
15998             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
15999                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
16000                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
16001                 applyCompoundDrawableTint();
16002             }
16003         }
16004     }
16005 
16006     /**
16007      * Prepares a drawable for display by propagating layout direction and
16008      * drawable state.
16009      *
16010      * @param dr the drawable to prepare
16011      */
prepareDrawableForDisplay(@ullable Drawable dr)16012     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
16013         if (dr == null) {
16014             return;
16015         }
16016 
16017         dr.setLayoutDirection(getLayoutDirection());
16018 
16019         if (dr.isStateful()) {
16020             dr.setState(getDrawableState());
16021             dr.jumpToCurrentState();
16022         }
16023     }
16024 
16025     /**
16026      * @hide
16027      */
resetResolvedDrawables()16028     protected void resetResolvedDrawables() {
16029         super.resetResolvedDrawables();
16030         mLastLayoutDirection = -1;
16031     }
16032 
16033     /**
16034      * @hide
16035      */
viewClicked(InputMethodManager imm)16036     protected void viewClicked(InputMethodManager imm) {
16037         if (imm != null) {
16038             imm.viewClicked(this);
16039         }
16040     }
16041 
16042     /**
16043      * Deletes the range of text [start, end[.
16044      * @hide
16045      */
16046     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
deleteText_internal(int start, int end)16047     protected void deleteText_internal(int start, int end) {
16048         ((Editable) mText).delete(start, end);
16049     }
16050 
16051     /**
16052      * Replaces the range of text [start, end[ by replacement text
16053      * @hide
16054      */
replaceText_internal(int start, int end, CharSequence text)16055     protected void replaceText_internal(int start, int end, CharSequence text) {
16056         ((Editable) mText).replace(start, end, text);
16057     }
16058 
16059     /**
16060      * Sets a span on the specified range of text
16061      * @hide
16062      */
setSpan_internal(Object span, int start, int end, int flags)16063     protected void setSpan_internal(Object span, int start, int end, int flags) {
16064         ((Editable) mText).setSpan(span, start, end, flags);
16065     }
16066 
16067     /**
16068      * Moves the cursor to the specified offset position in text
16069      * @hide
16070      */
setCursorPosition_internal(int start, int end)16071     protected void setCursorPosition_internal(int start, int end) {
16072         Selection.setSelection(((Editable) mText), start, end);
16073     }
16074 
16075     /**
16076      * An Editor should be created as soon as any of the editable-specific fields (grouped
16077      * inside the Editor object) is assigned to a non-default value.
16078      * This method will create the Editor if needed.
16079      *
16080      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
16081      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
16082      * Editor for backward compatibility, as soon as one of these fields is assigned.
16083      *
16084      * Also note that for performance reasons, the mEditor is created when needed, but not
16085      * reset when no more edit-specific fields are needed.
16086      */
16087     @UnsupportedAppUsage
createEditorIfNeeded()16088     private void createEditorIfNeeded() {
16089         if (mEditor == null) {
16090             mEditor = new Editor(this);
16091         }
16092     }
16093 
16094     /**
16095      * @hide
16096      */
16097     @Override
16098     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getIterableTextForAccessibility()16099     public CharSequence getIterableTextForAccessibility() {
16100         return mText;
16101     }
16102 
ensureIterableTextForAccessibilitySelectable()16103     private void ensureIterableTextForAccessibilitySelectable() {
16104         if (!(mText instanceof Spannable)) {
16105             setText(mText, BufferType.SPANNABLE);
16106             if (getLayout() == null) {
16107                 assumeLayout();
16108             }
16109         }
16110     }
16111 
16112     /**
16113      * @hide
16114      */
16115     @Override
getIteratorForGranularity(int granularity)16116     public TextSegmentIterator getIteratorForGranularity(int granularity) {
16117         switch (granularity) {
16118             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
16119                 Spannable text = (Spannable) getIterableTextForAccessibility();
16120                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
16121                     AccessibilityIterators.LineTextSegmentIterator iterator =
16122                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
16123                     iterator.initialize(text, getLayout());
16124                     return iterator;
16125                 }
16126             } break;
16127             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
16128                 Spannable text = (Spannable) getIterableTextForAccessibility();
16129                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
16130                     AccessibilityIterators.PageTextSegmentIterator iterator =
16131                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
16132                     iterator.initialize(this);
16133                     return iterator;
16134                 }
16135             } break;
16136         }
16137         return super.getIteratorForGranularity(granularity);
16138     }
16139 
16140     /**
16141      * @hide
16142      */
16143     @Override
getAccessibilitySelectionStart()16144     public int getAccessibilitySelectionStart() {
16145         return getSelectionStart();
16146     }
16147 
16148     /**
16149      * @hide
16150      */
isAccessibilitySelectionExtendable()16151     public boolean isAccessibilitySelectionExtendable() {
16152         return true;
16153     }
16154 
16155     /**
16156      * @hide
16157      */
prepareForExtendedAccessibilitySelection()16158     public void prepareForExtendedAccessibilitySelection() {
16159         requestFocusOnNonEditableSelectableText();
16160     }
16161 
16162     /**
16163      * @hide
16164      */
16165     @Override
getAccessibilitySelectionEnd()16166     public int getAccessibilitySelectionEnd() {
16167         return getSelectionEnd();
16168     }
16169 
16170     /**
16171      * @hide
16172      */
16173     @Override
setAccessibilitySelection(int start, int end)16174     public void setAccessibilitySelection(int start, int end) {
16175         if (getAccessibilitySelectionStart() == start
16176                 && getAccessibilitySelectionEnd() == end) {
16177             return;
16178         }
16179         CharSequence text = getIterableTextForAccessibility();
16180         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
16181             Selection.setSelection((Spannable) text, start, end);
16182         } else {
16183             Selection.removeSelection((Spannable) text);
16184         }
16185         // Hide all selection controllers used for adjusting selection
16186         // since we are doing so explicitlty by other means and these
16187         // controllers interact with how selection behaves.
16188         if (mEditor != null) {
16189             mEditor.hideCursorAndSpanControllers();
16190             mEditor.stopTextActionMode();
16191         }
16192     }
16193 
16194     /** @hide */
16195     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)16196     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
16197         super.encodeProperties(stream);
16198 
16199         TruncateAt ellipsize = getEllipsize();
16200         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
16201         stream.addProperty("text:textSize", getTextSize());
16202         stream.addProperty("text:scaledTextSize", getScaledTextSize());
16203         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
16204         stream.addProperty("text:selectionStart", getSelectionStart());
16205         stream.addProperty("text:selectionEnd", getSelectionEnd());
16206         stream.addProperty("text:curTextColor", mCurTextColor);
16207         stream.addUserProperty("text:text", mText == null ? null : mText.toString());
16208         stream.addProperty("text:gravity", mGravity);
16209     }
16210 
16211     /**
16212      * User interface state that is stored by TextView for implementing
16213      * {@link View#onSaveInstanceState}.
16214      */
16215     public static class SavedState extends BaseSavedState {
16216         int selStart = -1;
16217         int selEnd = -1;
16218         @UnsupportedAppUsage
16219         CharSequence text;
16220         boolean frozenWithFocus;
16221         CharSequence error;
16222         ParcelableParcel editorState;  // Optional state from Editor.
16223 
SavedState(Parcelable superState)16224         SavedState(Parcelable superState) {
16225             super(superState);
16226         }
16227 
16228         @Override
writeToParcel(Parcel out, int flags)16229         public void writeToParcel(Parcel out, int flags) {
16230             super.writeToParcel(out, flags);
16231             out.writeInt(selStart);
16232             out.writeInt(selEnd);
16233             out.writeInt(frozenWithFocus ? 1 : 0);
16234             TextUtils.writeToParcel(text, out, flags);
16235 
16236             if (error == null) {
16237                 out.writeInt(0);
16238             } else {
16239                 out.writeInt(1);
16240                 TextUtils.writeToParcel(error, out, flags);
16241             }
16242 
16243             if (editorState == null) {
16244                 out.writeInt(0);
16245             } else {
16246                 out.writeInt(1);
16247                 editorState.writeToParcel(out, flags);
16248             }
16249         }
16250 
16251         @Override
toString()16252         public String toString() {
16253             String str = "TextView.SavedState{"
16254                     + Integer.toHexString(System.identityHashCode(this))
16255                     + " start=" + selStart + " end=" + selEnd;
16256             if (text != null) {
16257                 str += " text=" + text;
16258             }
16259             return str + "}";
16260         }
16261 
16262         @SuppressWarnings("hiding")
16263         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
16264                 new Parcelable.Creator<SavedState>() {
16265                     public SavedState createFromParcel(Parcel in) {
16266                         return new SavedState(in);
16267                     }
16268 
16269                     public SavedState[] newArray(int size) {
16270                         return new SavedState[size];
16271                     }
16272                 };
16273 
SavedState(Parcel in)16274         private SavedState(Parcel in) {
16275             super(in);
16276             selStart = in.readInt();
16277             selEnd = in.readInt();
16278             frozenWithFocus = (in.readInt() != 0);
16279             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
16280 
16281             if (in.readInt() != 0) {
16282                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
16283             }
16284 
16285             if (in.readInt() != 0) {
16286                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
16287             }
16288         }
16289     }
16290 
16291     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
16292         @NonNull
16293         private char[] mChars;
16294         private int mStart, mLength;
16295 
CharWrapper(@onNull char[] chars, int start, int len)16296         CharWrapper(@NonNull char[] chars, int start, int len) {
16297             mChars = chars;
16298             mStart = start;
16299             mLength = len;
16300         }
16301 
set(@onNull char[] chars, int start, int len)16302         /* package */ void set(@NonNull char[] chars, int start, int len) {
16303             mChars = chars;
16304             mStart = start;
16305             mLength = len;
16306         }
16307 
length()16308         public int length() {
16309             return mLength;
16310         }
16311 
charAt(int off)16312         public char charAt(int off) {
16313             return mChars[off + mStart];
16314         }
16315 
16316         @Override
toString()16317         public String toString() {
16318             return new String(mChars, mStart, mLength);
16319         }
16320 
subSequence(int start, int end)16321         public CharSequence subSequence(int start, int end) {
16322             if (start < 0 || end < 0 || start > mLength || end > mLength) {
16323                 throw new IndexOutOfBoundsException(start + ", " + end);
16324             }
16325 
16326             return new String(mChars, start + mStart, end - start);
16327         }
16328 
getChars(int start, int end, char[] buf, int off)16329         public void getChars(int start, int end, char[] buf, int off) {
16330             if (start < 0 || end < 0 || start > mLength || end > mLength) {
16331                 throw new IndexOutOfBoundsException(start + ", " + end);
16332             }
16333 
16334             System.arraycopy(mChars, start + mStart, buf, off, end - start);
16335         }
16336 
16337         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)16338         public void drawText(BaseCanvas c, int start, int end,
16339                              float x, float y, Paint p) {
16340             c.drawText(mChars, start + mStart, end - start, x, y, p);
16341         }
16342 
16343         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)16344         public void drawTextRun(BaseCanvas c, int start, int end,
16345                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
16346             int count = end - start;
16347             int contextCount = contextEnd - contextStart;
16348             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
16349                     contextCount, x, y, isRtl, p);
16350         }
16351 
measureText(int start, int end, Paint p)16352         public float measureText(int start, int end, Paint p) {
16353             return p.measureText(mChars, start + mStart, end - start);
16354         }
16355 
getTextWidths(int start, int end, float[] widths, Paint p)16356         public int getTextWidths(int start, int end, float[] widths, Paint p) {
16357             return p.getTextWidths(mChars, start + mStart, end - start, widths);
16358         }
16359 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)16360         public float getTextRunAdvances(int start, int end, int contextStart,
16361                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
16362                 Paint p) {
16363             int count = end - start;
16364             int contextCount = contextEnd - contextStart;
16365             return p.getTextRunAdvances(mChars, start + mStart, count,
16366                     contextStart + mStart, contextCount, isRtl, advances,
16367                     advancesIndex);
16368         }
16369 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)16370         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
16371                 int offset, int cursorOpt, Paint p) {
16372             int contextCount = contextEnd - contextStart;
16373             return p.getTextRunCursor(mChars, contextStart + mStart,
16374                     contextCount, isRtl, offset + mStart, cursorOpt);
16375         }
16376     }
16377 
16378     private static final class Marquee {
16379         // TODO: Add an option to configure this
16380         private static final float MARQUEE_DELTA_MAX = 0.07f;
16381         private static final int MARQUEE_DELAY = 1200;
16382         private static final int MARQUEE_DP_PER_SECOND = 30;
16383 
16384         private static final byte MARQUEE_STOPPED = 0x0;
16385         private static final byte MARQUEE_STARTING = 0x1;
16386         private static final byte MARQUEE_RUNNING = 0x2;
16387 
16388         private final WeakReference<TextView> mView;
16389         private final Choreographer mChoreographer;
16390 
16391         private byte mStatus = MARQUEE_STOPPED;
16392         private final float mPixelsPerMs;
16393         private float mMaxScroll;
16394         private float mMaxFadeScroll;
16395         private float mGhostStart;
16396         private float mGhostOffset;
16397         private float mFadeStop;
16398         private int mRepeatLimit;
16399 
16400         private float mScroll;
16401         private long mLastAnimationMs;
16402 
Marquee(TextView v)16403         Marquee(TextView v) {
16404             final float density = v.getContext().getResources().getDisplayMetrics().density;
16405             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
16406             mView = new WeakReference<TextView>(v);
16407             mChoreographer = Choreographer.getInstance();
16408         }
16409 
16410         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
16411             @Override
16412             public void doFrame(long frameTimeNanos) {
16413                 tick();
16414             }
16415         };
16416 
16417         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
16418             @Override
16419             public void doFrame(long frameTimeNanos) {
16420                 mStatus = MARQUEE_RUNNING;
16421                 mLastAnimationMs = mChoreographer.getFrameTime();
16422                 tick();
16423             }
16424         };
16425 
16426         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
16427             @Override
16428             public void doFrame(long frameTimeNanos) {
16429                 if (mStatus == MARQUEE_RUNNING) {
16430                     if (mRepeatLimit >= 0) {
16431                         mRepeatLimit--;
16432                     }
16433                     start(mRepeatLimit);
16434                 }
16435             }
16436         };
16437 
tick()16438         void tick() {
16439             if (mStatus != MARQUEE_RUNNING) {
16440                 return;
16441             }
16442 
16443             mChoreographer.removeFrameCallback(mTickCallback);
16444 
16445             final TextView textView = mView.get();
16446             if (textView != null && textView.isAggregatedVisible()
16447                     && (textView.isFocused() || textView.isSelected())) {
16448                 long currentMs = mChoreographer.getFrameTime();
16449                 long deltaMs = currentMs - mLastAnimationMs;
16450                 mLastAnimationMs = currentMs;
16451                 float deltaPx = deltaMs * mPixelsPerMs;
16452                 mScroll += deltaPx;
16453                 if (mScroll > mMaxScroll) {
16454                     mScroll = mMaxScroll;
16455                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
16456                 } else {
16457                     mChoreographer.postFrameCallback(mTickCallback);
16458                 }
16459                 textView.invalidate();
16460             }
16461         }
16462 
stop()16463         void stop() {
16464             mStatus = MARQUEE_STOPPED;
16465             mChoreographer.removeFrameCallback(mStartCallback);
16466             mChoreographer.removeFrameCallback(mRestartCallback);
16467             mChoreographer.removeFrameCallback(mTickCallback);
16468             resetScroll();
16469         }
16470 
resetScroll()16471         private void resetScroll() {
16472             mScroll = 0.0f;
16473             final TextView textView = mView.get();
16474             if (textView != null) textView.invalidate();
16475         }
16476 
start(int repeatLimit)16477         void start(int repeatLimit) {
16478             if (repeatLimit == 0) {
16479                 stop();
16480                 return;
16481             }
16482             mRepeatLimit = repeatLimit;
16483             final TextView textView = mView.get();
16484             if (textView != null && textView.mLayout != null) {
16485                 mStatus = MARQUEE_STARTING;
16486                 mScroll = 0.0f;
16487                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
16488                         - textView.getCompoundPaddingRight();
16489                 final float lineWidth = textView.mLayout.getLineWidth(0);
16490                 final float gap = textWidth / 3.0f;
16491                 mGhostStart = lineWidth - textWidth + gap;
16492                 mMaxScroll = mGhostStart + textWidth;
16493                 mGhostOffset = lineWidth + gap;
16494                 mFadeStop = lineWidth + textWidth / 6.0f;
16495                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
16496 
16497                 textView.invalidate();
16498                 mChoreographer.postFrameCallback(mStartCallback);
16499             }
16500         }
16501 
getGhostOffset()16502         float getGhostOffset() {
16503             return mGhostOffset;
16504         }
16505 
getScroll()16506         float getScroll() {
16507             return mScroll;
16508         }
16509 
getMaxFadeScroll()16510         float getMaxFadeScroll() {
16511             return mMaxFadeScroll;
16512         }
16513 
shouldDrawLeftFade()16514         boolean shouldDrawLeftFade() {
16515             return mScroll <= mFadeStop;
16516         }
16517 
shouldDrawGhost()16518         boolean shouldDrawGhost() {
16519             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
16520         }
16521 
isRunning()16522         boolean isRunning() {
16523             return mStatus == MARQUEE_RUNNING;
16524         }
16525 
isStopped()16526         boolean isStopped() {
16527             return mStatus == MARQUEE_STOPPED;
16528         }
16529     }
16530 
16531     private class ChangeWatcher implements TextWatcher, SpanWatcher {
16532 
16533         private CharSequence mBeforeText;
16534 
beforeTextChanged(CharSequence buffer, int start, int before, int after)16535         public void beforeTextChanged(CharSequence buffer, int start,
16536                                       int before, int after) {
16537             if (DEBUG_EXTRACT) {
16538                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
16539                         + " before=" + before + " after=" + after + ": " + buffer);
16540             }
16541 
16542             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
16543                 mBeforeText = mTransformed.toString();
16544             }
16545 
16546             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
16547         }
16548 
onTextChanged(CharSequence buffer, int start, int before, int after)16549         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
16550             if (DEBUG_EXTRACT) {
16551                 Log.v(LOG_TAG, "onTextChanged start=" + start
16552                         + " before=" + before + " after=" + after + ": " + buffer);
16553             }
16554             TextView.this.handleTextChanged(buffer, start, before, after);
16555 
16556             if (isVisibleToAccessibility()) {
16557                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
16558                 mBeforeText = null;
16559             }
16560         }
16561 
afterTextChanged(Editable buffer)16562         public void afterTextChanged(Editable buffer) {
16563             if (DEBUG_EXTRACT) {
16564                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
16565             }
16566             TextView.this.sendAfterTextChanged(buffer);
16567 
16568             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
16569                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
16570             }
16571         }
16572 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)16573         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
16574             if (DEBUG_EXTRACT) {
16575                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
16576                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
16577             }
16578             TextView.this.spanChange(buf, what, s, st, e, en);
16579         }
16580 
onSpanAdded(Spannable buf, Object what, int s, int e)16581         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
16582             if (DEBUG_EXTRACT) {
16583                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
16584             }
16585             TextView.this.spanChange(buf, what, -1, s, -1, e);
16586         }
16587 
onSpanRemoved(Spannable buf, Object what, int s, int e)16588         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
16589             if (DEBUG_EXTRACT) {
16590                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
16591             }
16592             TextView.this.spanChange(buf, what, s, -1, e, -1);
16593         }
16594     }
16595 
16596     /** @hide */
16597     @Override
onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)16598     public void onInputConnectionOpenedInternal(@NonNull InputConnection ic,
16599             @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
16600         if (mEditor != null) {
16601             mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic,
16602                     editorInfo);
16603         }
16604     }
16605 
16606     /** @hide */
16607     @Override
onInputConnectionClosedInternal()16608     public void onInputConnectionClosedInternal() {
16609         if (mEditor != null) {
16610             mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo();
16611         }
16612     }
16613 
16614     /**
16615      * Default {@link TextView} implementation for receiving content. Apps wishing to provide
16616      * custom behavior should configure a listener via {@link #setOnReceiveContentListener}.
16617      *
16618      * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in
16619      * content without acting on it).
16620      *
16621      * <p>For editable TextViews the default behavior is to insert text into the view, coercing
16622      * non-text content to text as needed. The MIME types "text/plain" and "text/html" have
16623      * well-defined behavior for this, while other MIME types have reasonable fallback behavior
16624      * (see {@link ClipData.Item#coerceToStyledText}).
16625      *
16626      * @param payload The content to insert and related metadata.
16627      *
16628      * @return The portion of the passed-in content that was not handled (may be all, some, or none
16629      * of the passed-in content).
16630      */
16631     @Nullable
16632     @Override
onReceiveContent(@onNull ContentInfo payload)16633     public ContentInfo onReceiveContent(@NonNull ContentInfo payload) {
16634         if (mEditor != null) {
16635             return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload);
16636         }
16637         return payload;
16638     }
16639 
logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)16640     private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
16641         if (msgFormat == null) {
16642             Log.d(LOG_TAG, location);
16643         } else {
16644             Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs));
16645         }
16646     }
16647 
16648     /**
16649      * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
16650      * the view.
16651      *
16652      * <p>NOTE: When overriding the method, it should not collect a request to translate this
16653      * TextView if it is displaying a password.
16654      *
16655      * @param supportedFormats the supported translation format. The value could be {@link
16656      *                         android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
16657      * @param requestsCollector {@link Consumer} to receiver the {@link ViewTranslationRequest}
16658      *                                         which contains the information to be translated.
16659      */
16660     @Override
onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)16661     public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats,
16662             @NonNull Consumer<ViewTranslationRequest> requestsCollector) {
16663         if (supportedFormats == null || supportedFormats.length == 0) {
16664             if (UiTranslationController.DEBUG) {
16665                 Log.w(LOG_TAG, "Do not provide the support translation formats.");
16666             }
16667             return;
16668         }
16669         ViewTranslationRequest.Builder requestBuilder =
16670                 new ViewTranslationRequest.Builder(getAutofillId());
16671         // Support Text translation
16672         if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) {
16673             if (mText == null || mText.length() == 0) {
16674                 if (UiTranslationController.DEBUG) {
16675                     Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
16676                 }
16677                 return;
16678             }
16679             boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
16680             if (isTextEditable() || isPassword) {
16681                 Log.w(LOG_TAG, "Cannot create translation request. editable = "
16682                         + isTextEditable() + ", isPassword = " + isPassword);
16683                 return;
16684             }
16685             // TODO(b/176488462): apply the view's important for translation
16686             requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
16687                     TranslationRequestValue.forText(mText));
16688             if (!TextUtils.isEmpty(getContentDescription())) {
16689                 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION,
16690                         TranslationRequestValue.forText(getContentDescription()));
16691             }
16692         }
16693         requestsCollector.accept(requestBuilder.build());
16694     }
16695 }
16696