• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
20 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
21 import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT;
22 import static android.view.ContentInfo.SOURCE_AUTOFILL;
23 import static android.view.ContentInfo.SOURCE_CLIPBOARD;
24 import static android.view.ContentInfo.SOURCE_PROCESS_TEXT;
25 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY;
26 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
27 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
28 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
29 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
30 import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY;
31 
32 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
33 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
34 
35 import android.R;
36 import android.annotation.CallSuper;
37 import android.annotation.CheckResult;
38 import android.annotation.ColorInt;
39 import android.annotation.DrawableRes;
40 import android.annotation.FlaggedApi;
41 import android.annotation.FloatRange;
42 import android.annotation.IntDef;
43 import android.annotation.IntRange;
44 import android.annotation.NonNull;
45 import android.annotation.Nullable;
46 import android.annotation.Px;
47 import android.annotation.RequiresPermission;
48 import android.annotation.Size;
49 import android.annotation.StringRes;
50 import android.annotation.StyleRes;
51 import android.annotation.TestApi;
52 import android.annotation.XmlRes;
53 import android.app.Activity;
54 import android.app.PendingIntent;
55 import android.app.assist.AssistStructure;
56 import android.app.compat.CompatChanges;
57 import android.compat.annotation.ChangeId;
58 import android.compat.annotation.EnabledSince;
59 import android.compat.annotation.UnsupportedAppUsage;
60 import android.content.ClipData;
61 import android.content.ClipDescription;
62 import android.content.ClipboardManager;
63 import android.content.Context;
64 import android.content.Intent;
65 import android.content.UndoManager;
66 import android.content.pm.PackageManager;
67 import android.content.res.ColorStateList;
68 import android.content.res.CompatibilityInfo;
69 import android.content.res.Configuration;
70 import android.content.res.FontScaleConverterFactory;
71 import android.content.res.Resources;
72 import android.content.res.TypedArray;
73 import android.content.res.XmlResourceParser;
74 import android.graphics.BaseCanvas;
75 import android.graphics.BlendMode;
76 import android.graphics.Canvas;
77 import android.graphics.Color;
78 import android.graphics.Insets;
79 import android.graphics.Matrix;
80 import android.graphics.Paint;
81 import android.graphics.Paint.FontMetricsInt;
82 import android.graphics.Path;
83 import android.graphics.PointF;
84 import android.graphics.PorterDuff;
85 import android.graphics.Rect;
86 import android.graphics.RectF;
87 import android.graphics.Typeface;
88 import android.graphics.drawable.Drawable;
89 import android.graphics.fonts.FontStyle;
90 import android.graphics.fonts.FontVariationAxis;
91 import android.graphics.text.LineBreakConfig;
92 import android.icu.text.DecimalFormatSymbols;
93 import android.os.AsyncTask;
94 import android.os.Build;
95 import android.os.Build.VERSION_CODES;
96 import android.os.Bundle;
97 import android.os.CancellationSignal;
98 import android.os.Handler;
99 import android.os.LocaleList;
100 import android.os.Parcel;
101 import android.os.Parcelable;
102 import android.os.ParcelableParcel;
103 import android.os.Process;
104 import android.os.SystemClock;
105 import android.os.UserHandle;
106 import android.provider.Settings;
107 import android.text.BoringLayout;
108 import android.text.ClientFlags;
109 import android.text.DynamicLayout;
110 import android.text.Editable;
111 import android.text.GetChars;
112 import android.text.GraphemeClusterSegmentFinder;
113 import android.text.GraphicsOperations;
114 import android.text.Highlights;
115 import android.text.InputFilter;
116 import android.text.InputType;
117 import android.text.Layout;
118 import android.text.NoCopySpan;
119 import android.text.ParcelableSpan;
120 import android.text.PrecomputedText;
121 import android.text.SegmentFinder;
122 import android.text.Selection;
123 import android.text.SpanWatcher;
124 import android.text.Spannable;
125 import android.text.SpannableStringBuilder;
126 import android.text.Spanned;
127 import android.text.SpannedString;
128 import android.text.StaticLayout;
129 import android.text.TextDirectionHeuristic;
130 import android.text.TextDirectionHeuristics;
131 import android.text.TextPaint;
132 import android.text.TextUtils;
133 import android.text.TextUtils.TruncateAt;
134 import android.text.TextWatcher;
135 import android.text.WordSegmentFinder;
136 import android.text.method.AllCapsTransformationMethod;
137 import android.text.method.ArrowKeyMovementMethod;
138 import android.text.method.DateKeyListener;
139 import android.text.method.DateTimeKeyListener;
140 import android.text.method.DialerKeyListener;
141 import android.text.method.DigitsKeyListener;
142 import android.text.method.KeyListener;
143 import android.text.method.LinkMovementMethod;
144 import android.text.method.MetaKeyKeyListener;
145 import android.text.method.MovementMethod;
146 import android.text.method.OffsetMapping;
147 import android.text.method.PasswordTransformationMethod;
148 import android.text.method.SingleLineTransformationMethod;
149 import android.text.method.TextKeyListener;
150 import android.text.method.TimeKeyListener;
151 import android.text.method.TransformationMethod;
152 import android.text.method.TransformationMethod2;
153 import android.text.method.WordIterator;
154 import android.text.style.CharacterStyle;
155 import android.text.style.ClickableSpan;
156 import android.text.style.ParagraphStyle;
157 import android.text.style.SpellCheckSpan;
158 import android.text.style.SuggestionSpan;
159 import android.text.style.URLSpan;
160 import android.text.style.UpdateAppearance;
161 import android.text.util.Linkify;
162 import android.util.ArraySet;
163 import android.util.AttributeSet;
164 import android.util.DisplayMetrics;
165 import android.util.IntArray;
166 import android.util.Log;
167 import android.util.SparseIntArray;
168 import android.util.TypedValue;
169 import android.view.AccessibilityIterators.TextSegmentIterator;
170 import android.view.ActionMode;
171 import android.view.Choreographer;
172 import android.view.ContentInfo;
173 import android.view.ContextMenu;
174 import android.view.DragEvent;
175 import android.view.Gravity;
176 import android.view.HapticFeedbackConstants;
177 import android.view.InputDevice;
178 import android.view.KeyCharacterMap;
179 import android.view.KeyEvent;
180 import android.view.MotionEvent;
181 import android.view.PointerIcon;
182 import android.view.View;
183 import android.view.ViewConfiguration;
184 import android.view.ViewDebug;
185 import android.view.ViewGroup;
186 import android.view.ViewGroup.LayoutParams;
187 import android.view.ViewHierarchyEncoder;
188 import android.view.ViewParent;
189 import android.view.ViewRootImpl;
190 import android.view.ViewStructure;
191 import android.view.ViewTreeObserver;
192 import android.view.accessibility.AccessibilityEvent;
193 import android.view.accessibility.AccessibilityManager;
194 import android.view.accessibility.AccessibilityNodeInfo;
195 import android.view.animation.AnimationUtils;
196 import android.view.autofill.AutofillManager;
197 import android.view.autofill.AutofillValue;
198 import android.view.contentcapture.ContentCaptureManager;
199 import android.view.contentcapture.ContentCaptureSession;
200 import android.view.inputmethod.BaseInputConnection;
201 import android.view.inputmethod.CompletionInfo;
202 import android.view.inputmethod.CorrectionInfo;
203 import android.view.inputmethod.CursorAnchorInfo;
204 import android.view.inputmethod.DeleteGesture;
205 import android.view.inputmethod.DeleteRangeGesture;
206 import android.view.inputmethod.EditorBoundsInfo;
207 import android.view.inputmethod.EditorInfo;
208 import android.view.inputmethod.ExtractedText;
209 import android.view.inputmethod.ExtractedTextRequest;
210 import android.view.inputmethod.HandwritingGesture;
211 import android.view.inputmethod.InputConnection;
212 import android.view.inputmethod.InputMethodManager;
213 import android.view.inputmethod.InsertGesture;
214 import android.view.inputmethod.InsertModeGesture;
215 import android.view.inputmethod.JoinOrSplitGesture;
216 import android.view.inputmethod.PreviewableHandwritingGesture;
217 import android.view.inputmethod.RemoveSpaceGesture;
218 import android.view.inputmethod.SelectGesture;
219 import android.view.inputmethod.SelectRangeGesture;
220 import android.view.inputmethod.TextAppearanceInfo;
221 import android.view.inputmethod.TextBoundsInfo;
222 import android.view.inspector.InspectableProperty;
223 import android.view.inspector.InspectableProperty.EnumEntry;
224 import android.view.inspector.InspectableProperty.FlagEntry;
225 import android.view.textclassifier.TextClassification;
226 import android.view.textclassifier.TextClassificationContext;
227 import android.view.textclassifier.TextClassificationManager;
228 import android.view.textclassifier.TextClassifier;
229 import android.view.textclassifier.TextLinks;
230 import android.view.textservice.SpellCheckerSubtype;
231 import android.view.textservice.TextServicesManager;
232 import android.view.translation.TranslationRequestValue;
233 import android.view.translation.TranslationSpec;
234 import android.view.translation.UiTranslationController;
235 import android.view.translation.ViewTranslationCallback;
236 import android.view.translation.ViewTranslationRequest;
237 import android.widget.RemoteViews.RemoteView;
238 
239 import com.android.internal.accessibility.util.AccessibilityUtils;
240 import com.android.internal.annotations.VisibleForTesting;
241 import com.android.internal.graphics.ColorUtils;
242 import com.android.internal.inputmethod.EditableInputConnection;
243 import com.android.internal.logging.MetricsLogger;
244 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
245 import com.android.internal.util.ArrayUtils;
246 import com.android.internal.util.FastMath;
247 import com.android.internal.util.Preconditions;
248 import com.android.text.flags.Flags;
249 
250 import libcore.util.EmptyArray;
251 
252 import org.xmlpull.v1.XmlPullParserException;
253 
254 import java.io.IOException;
255 import java.lang.annotation.Retention;
256 import java.lang.annotation.RetentionPolicy;
257 import java.lang.ref.WeakReference;
258 import java.util.ArrayList;
259 import java.util.Arrays;
260 import java.util.List;
261 import java.util.Locale;
262 import java.util.Objects;
263 import java.util.Set;
264 import java.util.concurrent.CompletableFuture;
265 import java.util.concurrent.TimeUnit;
266 import java.util.function.Consumer;
267 import java.util.function.Supplier;
268 import java.util.regex.Matcher;
269 import java.util.regex.Pattern;
270 
271 /**
272  * A user interface element that displays text to the user.
273  * To provide user-editable text, see {@link EditText}.
274  * <p>
275  * The following code sample shows a typical use, with an XML layout
276  * and code to modify the contents of the text view:
277  * </p>
278 
279  * <pre>
280  * &lt;LinearLayout
281        xmlns:android="http://schemas.android.com/apk/res/android"
282        android:layout_width="match_parent"
283        android:layout_height="match_parent"&gt;
284  *    &lt;TextView
285  *        android:id="@+id/text_view_id"
286  *        android:layout_height="wrap_content"
287  *        android:layout_width="wrap_content"
288  *        android:text="@string/hello" /&gt;
289  * &lt;/LinearLayout&gt;
290  * </pre>
291  * <p>
292  * This code sample demonstrates how to modify the contents of the text view
293  * defined in the previous XML layout:
294  * </p>
295  * <pre>
296  * public class MainActivity extends Activity {
297  *
298  *    protected void onCreate(Bundle savedInstanceState) {
299  *         super.onCreate(savedInstanceState);
300  *         setContentView(R.layout.activity_main);
301  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
302  *         helloTextView.setText(R.string.user_greeting);
303  *     }
304  * }
305  * </pre>
306  * <p>
307  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
308  * </p>
309  * <p>
310  * <b>XML attributes</b>
311  * <p>
312  * See {@link android.R.styleable#TextView TextView Attributes},
313  * {@link android.R.styleable#View View Attributes}
314  *
315  * @attr ref android.R.styleable#TextView_text
316  * @attr ref android.R.styleable#TextView_bufferType
317  * @attr ref android.R.styleable#TextView_hint
318  * @attr ref android.R.styleable#TextView_textColor
319  * @attr ref android.R.styleable#TextView_textColorHighlight
320  * @attr ref android.R.styleable#TextView_textColorHint
321  * @attr ref android.R.styleable#TextView_textAppearance
322  * @attr ref android.R.styleable#TextView_textColorLink
323  * @attr ref android.R.styleable#TextView_textFontWeight
324  * @attr ref android.R.styleable#TextView_textSize
325  * @attr ref android.R.styleable#TextView_textScaleX
326  * @attr ref android.R.styleable#TextView_fontFamily
327  * @attr ref android.R.styleable#TextView_typeface
328  * @attr ref android.R.styleable#TextView_textStyle
329  * @attr ref android.R.styleable#TextView_cursorVisible
330  * @attr ref android.R.styleable#TextView_maxLines
331  * @attr ref android.R.styleable#TextView_maxHeight
332  * @attr ref android.R.styleable#TextView_lines
333  * @attr ref android.R.styleable#TextView_height
334  * @attr ref android.R.styleable#TextView_minLines
335  * @attr ref android.R.styleable#TextView_minHeight
336  * @attr ref android.R.styleable#TextView_maxEms
337  * @attr ref android.R.styleable#TextView_maxWidth
338  * @attr ref android.R.styleable#TextView_ems
339  * @attr ref android.R.styleable#TextView_width
340  * @attr ref android.R.styleable#TextView_minEms
341  * @attr ref android.R.styleable#TextView_minWidth
342  * @attr ref android.R.styleable#TextView_gravity
343  * @attr ref android.R.styleable#TextView_scrollHorizontally
344  * @attr ref android.R.styleable#TextView_password
345  * @attr ref android.R.styleable#TextView_singleLine
346  * @attr ref android.R.styleable#TextView_selectAllOnFocus
347  * @attr ref android.R.styleable#TextView_includeFontPadding
348  * @attr ref android.R.styleable#TextView_maxLength
349  * @attr ref android.R.styleable#TextView_shadowColor
350  * @attr ref android.R.styleable#TextView_shadowDx
351  * @attr ref android.R.styleable#TextView_shadowDy
352  * @attr ref android.R.styleable#TextView_shadowRadius
353  * @attr ref android.R.styleable#TextView_autoLink
354  * @attr ref android.R.styleable#TextView_linksClickable
355  * @attr ref android.R.styleable#TextView_numeric
356  * @attr ref android.R.styleable#TextView_digits
357  * @attr ref android.R.styleable#TextView_phoneNumber
358  * @attr ref android.R.styleable#TextView_inputMethod
359  * @attr ref android.R.styleable#TextView_capitalize
360  * @attr ref android.R.styleable#TextView_autoText
361  * @attr ref android.R.styleable#TextView_editable
362  * @attr ref android.R.styleable#TextView_freezesText
363  * @attr ref android.R.styleable#TextView_ellipsize
364  * @attr ref android.R.styleable#TextView_drawableTop
365  * @attr ref android.R.styleable#TextView_drawableBottom
366  * @attr ref android.R.styleable#TextView_drawableRight
367  * @attr ref android.R.styleable#TextView_drawableLeft
368  * @attr ref android.R.styleable#TextView_drawableStart
369  * @attr ref android.R.styleable#TextView_drawableEnd
370  * @attr ref android.R.styleable#TextView_drawablePadding
371  * @attr ref android.R.styleable#TextView_drawableTint
372  * @attr ref android.R.styleable#TextView_drawableTintMode
373  * @attr ref android.R.styleable#TextView_lineSpacingExtra
374  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
375  * @attr ref android.R.styleable#TextView_justificationMode
376  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
377  * @attr ref android.R.styleable#TextView_inputType
378  * @attr ref android.R.styleable#TextView_imeOptions
379  * @attr ref android.R.styleable#TextView_privateImeOptions
380  * @attr ref android.R.styleable#TextView_imeActionLabel
381  * @attr ref android.R.styleable#TextView_imeActionId
382  * @attr ref android.R.styleable#TextView_editorExtras
383  * @attr ref android.R.styleable#TextView_elegantTextHeight
384  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
385  * @attr ref android.R.styleable#TextView_letterSpacing
386  * @attr ref android.R.styleable#TextView_fontFeatureSettings
387  * @attr ref android.R.styleable#TextView_fontVariationSettings
388  * @attr ref android.R.styleable#TextView_breakStrategy
389  * @attr ref android.R.styleable#TextView_hyphenationFrequency
390  * @attr ref android.R.styleable#TextView_lineBreakStyle
391  * @attr ref android.R.styleable#TextView_lineBreakWordStyle
392  * @attr ref android.R.styleable#TextView_autoSizeTextType
393  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
394  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
395  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
396  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
397  * @attr ref android.R.styleable#TextView_textCursorDrawable
398  * @attr ref android.R.styleable#TextView_textSelectHandle
399  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
400  * @attr ref android.R.styleable#TextView_textSelectHandleRight
401  * @attr ref android.R.styleable#TextView_allowUndo
402  * @attr ref android.R.styleable#TextView_enabled
403  */
404 @RemoteView
405 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
406     static final String LOG_TAG = "TextView";
407     static final boolean DEBUG_EXTRACT = false;
408     static final boolean DEBUG_CURSOR = false;
409 
410     private static final float[] TEMP_POSITION = new float[2];
411 
412     // Enum for the "typeface" XML parameter.
413     // TODO: How can we get this from the XML instead of hardcoding it here?
414     /** @hide */
415     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
416     @Retention(RetentionPolicy.SOURCE)
417     public @interface XMLTypefaceAttr{}
418     private static final int DEFAULT_TYPEFACE = -1;
419     private static final int SANS = 1;
420     private static final int SERIF = 2;
421     private static final int MONOSPACE = 3;
422 
423     // Enum for the "ellipsize" XML parameter.
424     private static final int ELLIPSIZE_NOT_SET = -1;
425     private static final int ELLIPSIZE_NONE = 0;
426     private static final int ELLIPSIZE_START = 1;
427     private static final int ELLIPSIZE_MIDDLE = 2;
428     private static final int ELLIPSIZE_END = 3;
429     private static final int ELLIPSIZE_MARQUEE = 4;
430 
431     // Bitfield for the "numeric" XML parameter.
432     // TODO: How can we get this from the XML instead of hardcoding it here?
433     private static final int SIGNED = 2;
434     private static final int DECIMAL = 4;
435 
436     /**
437      * Draw marquee text with fading edges as usual
438      */
439     private static final int MARQUEE_FADE_NORMAL = 0;
440 
441     /**
442      * Draw marquee text as ellipsize end while inactive instead of with the fade.
443      * (Useful for devices where the fade can be expensive if overdone)
444      */
445     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
446 
447     /**
448      * Draw marquee text with fading edges because it is currently active/animating.
449      */
450     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
451 
452     @UnsupportedAppUsage
453     private static final int LINES = 1;
454     private static final int EMS = LINES;
455     private static final int PIXELS = 2;
456 
457     // Maximum text length for single line input.
458     private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
459     private InputFilter.LengthFilter mSingleLineLengthFilter = null;
460 
461     private static final RectF TEMP_RECTF = new RectF();
462 
463     /** @hide */
464     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
465     private static final int ANIMATED_SCROLL_GAP = 250;
466 
467     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
468     private static final Spanned EMPTY_SPANNED = new SpannedString("");
469 
470     private static final int CHANGE_WATCHER_PRIORITY = 100;
471 
472     /**
473      * The span priority of the {@link OffsetMapping} that is set on the text. It must be
474      * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is
475      * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered
476      * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}.
477      */
478     private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200;
479 
480     // New state used to change background based on whether this TextView is multiline.
481     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
482 
483     // Accessibility action to share selected text.
484     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
485 
486     /**
487      * @hide
488      */
489     // Accessibility action start id for "process text" actions.
490     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
491 
492     /** Accessibility action start id for "smart" actions. @hide */
493     static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000;
494 
495     /**
496      * @hide
497      */
498     @TestApi
499     public static final int PROCESS_TEXT_REQUEST_CODE = 100;
500 
501     /**
502      *  Return code of {@link #doKeyDown}.
503      */
504     private static final int KEY_EVENT_NOT_HANDLED = 0;
505     private static final int KEY_EVENT_HANDLED = -1;
506     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
507     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
508 
509     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
510 
511     // The default value of the line break style.
512     private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE;
513 
514     // The default value of the line break word style.
515     private static final int DEFAULT_LINE_BREAK_WORD_STYLE =
516             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
517 
518     /**
519      * This change ID enables the fallback text line spacing (line height) for BoringLayout.
520      * @hide
521      */
522     @ChangeId
523     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
524     public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id
525 
526     /**
527      * This change ID enables the fallback text line spacing (line height) for StaticLayout.
528      * @hide
529      */
530     @ChangeId
531     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P)
532     public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id
533 
534 
535     /**
536      * This change ID enables the bounding box based layout.
537      * @hide
538      */
539     @ChangeId
540     @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
541     public static final long USE_BOUNDS_FOR_WIDTH = 63938206;  // buganizer id
542 
543     // System wide time for last cut, copy or text changed action.
544     static long sLastCutCopyOrTextChangedTime;
545     private ColorStateList mTextColor;
546     private ColorStateList mHintTextColor;
547     private ColorStateList mLinkTextColor;
548     @ViewDebug.ExportedProperty(category = "text")
549 
550     /**
551      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
552      */
553     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
554     private int mCurTextColor;
555 
556     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
557     private int mCurHintTextColor;
558     private boolean mFreezesText;
559 
560     @UnsupportedAppUsage
561     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
562     @UnsupportedAppUsage
563     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
564 
565     @UnsupportedAppUsage
566     private float mShadowRadius;
567     @UnsupportedAppUsage
568     private float mShadowDx;
569     @UnsupportedAppUsage
570     private float mShadowDy;
571     private int mShadowColor;
572 
573     private int mLastOrientation;
574 
575     private boolean mPreDrawRegistered;
576     private boolean mPreDrawListenerDetached;
577 
578     private TextClassifier mTextClassifier;
579     private TextClassifier mTextClassificationSession;
580     private TextClassificationContext mTextClassificationContext;
581 
582     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
583     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
584     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
585     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
586     // into by the user holding the movement key down) then we shouldn't prevent the focus from
587     // changing.
588     private boolean mPreventDefaultMovement;
589 
590     private TextUtils.TruncateAt mEllipsize;
591 
592     // A flag to indicate the cursor was hidden by IME.
593     private boolean mImeIsConsumingInput;
594 
595     // Whether cursor is visible without regard to {@link mImeConsumesInput}.
596     // {@code true} is the default value.
597     private boolean mCursorVisibleFromAttr = true;
598 
599     static class Drawables {
600         static final int LEFT = 0;
601         static final int TOP = 1;
602         static final int RIGHT = 2;
603         static final int BOTTOM = 3;
604 
605         static final int DRAWABLE_NONE = -1;
606         static final int DRAWABLE_RIGHT = 0;
607         static final int DRAWABLE_LEFT = 1;
608 
609         final Rect mCompoundRect = new Rect();
610 
611         final Drawable[] mShowing = new Drawable[4];
612 
613         ColorStateList mTintList;
614         BlendMode mBlendMode;
615         boolean mHasTint;
616         boolean mHasTintMode;
617 
618         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
619         Drawable mDrawableLeftInitial, mDrawableRightInitial;
620 
621         boolean mIsRtlCompatibilityMode;
622         boolean mOverride;
623 
624         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
625                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
626 
627         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
628                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
629 
630         int mDrawablePadding;
631 
632         int mDrawableSaved = DRAWABLE_NONE;
633 
Drawables(Context context)634         public Drawables(Context context) {
635             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
636             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
637                     || !context.getApplicationInfo().hasRtlSupport();
638             mOverride = false;
639         }
640 
641         /**
642          * @return {@code true} if this object contains metadata that needs to
643          *         be retained, {@code false} otherwise
644          */
645         public boolean hasMetadata() {
646             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
647         }
648 
649         /**
650          * Updates the list of displayed drawables to account for the current
651          * layout direction.
652          *
653          * @param layoutDirection the current layout direction
654          * @return {@code true} if the displayed drawables changed
655          */
656         public boolean resolveWithLayoutDirection(int layoutDirection) {
657             final Drawable previousLeft = mShowing[Drawables.LEFT];
658             final Drawable previousRight = mShowing[Drawables.RIGHT];
659 
660             // First reset "left" and "right" drawables to their initial values
661             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
662             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
663 
664             if (mIsRtlCompatibilityMode) {
665                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
666                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
667                     mShowing[Drawables.LEFT] = mDrawableStart;
668                     mDrawableSizeLeft = mDrawableSizeStart;
669                     mDrawableHeightLeft = mDrawableHeightStart;
670                 }
671                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
672                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
673                     mShowing[Drawables.RIGHT] = mDrawableEnd;
674                     mDrawableSizeRight = mDrawableSizeEnd;
675                     mDrawableHeightRight = mDrawableHeightEnd;
676                 }
677             } else {
678                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
679                 // drawable if and only if they have been defined
680                 switch(layoutDirection) {
681                     case LAYOUT_DIRECTION_RTL:
682                         if (mOverride) {
683                             mShowing[Drawables.RIGHT] = mDrawableStart;
684                             mDrawableSizeRight = mDrawableSizeStart;
685                             mDrawableHeightRight = mDrawableHeightStart;
686 
687                             mShowing[Drawables.LEFT] = mDrawableEnd;
688                             mDrawableSizeLeft = mDrawableSizeEnd;
689                             mDrawableHeightLeft = mDrawableHeightEnd;
690                         }
691                         break;
692 
693                     case LAYOUT_DIRECTION_LTR:
694                     default:
695                         if (mOverride) {
696                             mShowing[Drawables.LEFT] = mDrawableStart;
697                             mDrawableSizeLeft = mDrawableSizeStart;
698                             mDrawableHeightLeft = mDrawableHeightStart;
699 
700                             mShowing[Drawables.RIGHT] = mDrawableEnd;
701                             mDrawableSizeRight = mDrawableSizeEnd;
702                             mDrawableHeightRight = mDrawableHeightEnd;
703                         }
704                         break;
705                 }
706             }
707 
708             applyErrorDrawableIfNeeded(layoutDirection);
709 
710             return mShowing[Drawables.LEFT] != previousLeft
711                     || mShowing[Drawables.RIGHT] != previousRight;
712         }
713 
714         public void setErrorDrawable(Drawable dr, TextView tv) {
715             if (mDrawableError != dr && mDrawableError != null) {
716                 mDrawableError.setCallback(null);
717             }
718             mDrawableError = dr;
719 
720             if (mDrawableError != null) {
721                 final Rect compoundRect = mCompoundRect;
722                 final int[] state = tv.getDrawableState();
723 
724                 mDrawableError.setState(state);
725                 mDrawableError.copyBounds(compoundRect);
726                 mDrawableError.setCallback(tv);
727                 mDrawableSizeError = compoundRect.width();
728                 mDrawableHeightError = compoundRect.height();
729             } else {
730                 mDrawableSizeError = mDrawableHeightError = 0;
731             }
732         }
733 
734         private void applyErrorDrawableIfNeeded(int layoutDirection) {
735             // first restore the initial state if needed
736             switch (mDrawableSaved) {
737                 case DRAWABLE_LEFT:
738                     mShowing[Drawables.LEFT] = mDrawableTemp;
739                     mDrawableSizeLeft = mDrawableSizeTemp;
740                     mDrawableHeightLeft = mDrawableHeightTemp;
741                     break;
742                 case DRAWABLE_RIGHT:
743                     mShowing[Drawables.RIGHT] = mDrawableTemp;
744                     mDrawableSizeRight = mDrawableSizeTemp;
745                     mDrawableHeightRight = mDrawableHeightTemp;
746                     break;
747                 case DRAWABLE_NONE:
748                 default:
749             }
750             // then, if needed, assign the Error drawable to the correct location
751             if (mDrawableError != null) {
752                 switch(layoutDirection) {
753                     case LAYOUT_DIRECTION_RTL:
754                         mDrawableSaved = DRAWABLE_LEFT;
755 
756                         mDrawableTemp = mShowing[Drawables.LEFT];
757                         mDrawableSizeTemp = mDrawableSizeLeft;
758                         mDrawableHeightTemp = mDrawableHeightLeft;
759 
760                         mShowing[Drawables.LEFT] = mDrawableError;
761                         mDrawableSizeLeft = mDrawableSizeError;
762                         mDrawableHeightLeft = mDrawableHeightError;
763                         break;
764                     case LAYOUT_DIRECTION_LTR:
765                     default:
766                         mDrawableSaved = DRAWABLE_RIGHT;
767 
768                         mDrawableTemp = mShowing[Drawables.RIGHT];
769                         mDrawableSizeTemp = mDrawableSizeRight;
770                         mDrawableHeightTemp = mDrawableHeightRight;
771 
772                         mShowing[Drawables.RIGHT] = mDrawableError;
773                         mDrawableSizeRight = mDrawableSizeError;
774                         mDrawableHeightRight = mDrawableHeightError;
775                         break;
776                 }
777             }
778         }
779     }
780 
781     @UnsupportedAppUsage
782     Drawables mDrawables;
783 
784     @UnsupportedAppUsage
785     private CharWrapper mCharWrapper;
786 
787     @UnsupportedAppUsage(trackingBug = 124050217)
788     private Marquee mMarquee;
789     @UnsupportedAppUsage
790     private boolean mRestartMarquee;
791 
792     private int mMarqueeRepeatLimit = 3;
793 
794     private int mLastLayoutDirection = -1;
795 
796     /**
797      * On some devices the fading edges add a performance penalty if used
798      * extensively in the same layout. This mode indicates how the marquee
799      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
800      */
801     @UnsupportedAppUsage
802     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
803 
804     /**
805      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
806      * the layout that should be used when the mode switches.
807      */
808     @UnsupportedAppUsage
809     private Layout mSavedMarqueeModeLayout;
810 
811     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
812     @ViewDebug.ExportedProperty(category = "text")
813     @UnsupportedAppUsage
814     private @Nullable CharSequence mText;
815     private @Nullable Spannable mSpannable;
816     private @Nullable PrecomputedText mPrecomputed;
817 
818     @UnsupportedAppUsage
819     private CharSequence mTransformed;
820     @UnsupportedAppUsage
821     private BufferType mBufferType = BufferType.NORMAL;
822 
823     private CharSequence mHint;
824     @UnsupportedAppUsage
825     private Layout mHintLayout;
826     private boolean mHideHint;
827 
828     private MovementMethod mMovement;
829 
830     private TransformationMethod mTransformation;
831     @UnsupportedAppUsage
832     private boolean mAllowTransformationLengthChange;
833     @UnsupportedAppUsage
834     private ChangeWatcher mChangeWatcher;
835 
836     @UnsupportedAppUsage(trackingBug = 123769451)
837     private ArrayList<TextWatcher> mListeners;
838 
839     // display attributes
840     @UnsupportedAppUsage
841     private final TextPaint mTextPaint;
842     @UnsupportedAppUsage
843     private boolean mUserSetTextScaleX;
844     @UnsupportedAppUsage
845     private Layout mLayout;
846     private boolean mLocalesChanged = false;
847     private int mTextSizeUnit = -1;
848     private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
849     private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
850 
851     // This is used to reflect the current user preference for changing font weight and making text
852     // more bold.
853     private int mFontWeightAdjustment;
854     private Typeface mOriginalTypeface;
855 
856     // True if setKeyListener() has been explicitly called
857     private boolean mListenerChanged = false;
858     // True if internationalized input should be used for numbers and date and time.
859     private final boolean mUseInternationalizedInput;
860 
861     // Fallback fonts that end up getting used should be allowed to affect line spacing.
862     private static final int FALLBACK_LINE_SPACING_NONE = 0;
863     private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1;
864     private static final int FALLBACK_LINE_SPACING_ALL = 2;
865 
866     private int mUseFallbackLineSpacing;
867     // True if the view text can be padded for compat reasons, when the view is translated.
868     private final boolean mUseTextPaddingForUiTranslation;
869 
870     private boolean mUseBoundsForWidth;
871     private boolean mShiftDrawingOffsetForStartOverhang;
872     @Nullable private Paint.FontMetrics mMinimumFontMetrics;
873     @Nullable private Paint.FontMetrics mLocalePreferredFontMetrics;
874     private boolean mUseLocalePreferredLineHeightForMinimum;
875 
876     @ViewDebug.ExportedProperty(category = "text")
877     @UnsupportedAppUsage
878     private int mGravity = Gravity.TOP | Gravity.START;
879     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
880     private boolean mHorizontallyScrolling;
881 
882     private int mAutoLinkMask;
883     private boolean mLinksClickable = true;
884 
885     @UnsupportedAppUsage
886     private float mSpacingMult = 1.0f;
887     @UnsupportedAppUsage
888     private float mSpacingAdd = 0.0f;
889 
890     /**
891      * Remembers what line height was set to originally, before we broke it down into raw pixels.
892      *
893      * <p>This is stored as a complex dimension with both value and unit packed into one field!
894      * {@see TypedValue}
895      */
896     private int mLineHeightComplexDimen;
897 
898     private int mBreakStrategy;
899     private int mHyphenationFrequency;
900     private int mJustificationMode;
901 
902     @UnsupportedAppUsage
903     private int mMaximum = Integer.MAX_VALUE;
904     @UnsupportedAppUsage
905     private int mMaxMode = LINES;
906     @UnsupportedAppUsage
907     private int mMinimum = 0;
908     @UnsupportedAppUsage
909     private int mMinMode = LINES;
910 
911     @UnsupportedAppUsage
912     private int mOldMaximum = mMaximum;
913     @UnsupportedAppUsage
914     private int mOldMaxMode = mMaxMode;
915 
916     @UnsupportedAppUsage
917     private int mMaxWidth = Integer.MAX_VALUE;
918     @UnsupportedAppUsage
919     private int mMaxWidthMode = PIXELS;
920     @UnsupportedAppUsage
921     private int mMinWidth = 0;
922     @UnsupportedAppUsage
923     private int mMinWidthMode = PIXELS;
924 
925     @UnsupportedAppUsage
926     private boolean mSingleLine;
927     @UnsupportedAppUsage
928     private int mDesiredHeightAtMeasure = -1;
929     @UnsupportedAppUsage
930     private boolean mIncludePad = true;
931     private int mDeferScroll = -1;
932 
933     // tmp primitives, so we don't alloc them on each draw
934     private Rect mTempRect;
935     private long mLastScroll;
936     private Scroller mScroller;
937     private TextPaint mTempTextPaint;
938 
939     private Object mTempCursor;
940 
941     @UnsupportedAppUsage
942     private BoringLayout.Metrics mBoring;
943     @UnsupportedAppUsage
944     private BoringLayout.Metrics mHintBoring;
945     @UnsupportedAppUsage
946     private BoringLayout mSavedLayout;
947     @UnsupportedAppUsage
948     private BoringLayout mSavedHintLayout;
949 
950     @UnsupportedAppUsage
951     private TextDirectionHeuristic mTextDir;
952 
953     private InputFilter[] mFilters = NO_FILTERS;
954 
955     /**
956      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
957      * the same as {@link Process#myUserHandle()}.
958      *
959      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
960      * other apps may need to set this so that the system can use right user's resources and
961      * services such as input methods and spell checkers.</p>
962      *
963      * @see #setTextOperationUser(UserHandle)
964      */
965     @Nullable
966     private UserHandle mTextOperationUser;
967 
968     private volatile Locale mCurrentSpellCheckerLocaleCache;
969 
970     // It is possible to have a selection even when mEditor is null (programmatically set, like when
971     // a link is pressed). These highlight-related fields do not go in mEditor.
972     @UnsupportedAppUsage
973     int mHighlightColor = 0x6633B5E5;
974     private Path mHighlightPath;
975     @UnsupportedAppUsage
976     private final Paint mHighlightPaint;
977     @UnsupportedAppUsage
978     private boolean mHighlightPathBogus = true;
979 
980     private List<Path> mHighlightPaths;
981     private List<Paint> mHighlightPaints;
982     private Highlights mHighlights;
983     private int[] mSearchResultHighlights = null;
984     private Paint mSearchResultHighlightPaint = null;
985     private Paint mFocusedSearchResultHighlightPaint = null;
986     private int mFocusedSearchResultHighlightColor = 0xFFFF9632;
987     private int mSearchResultHighlightColor = 0xFFFFFF00;
988 
989     private int mFocusedSearchResultIndex = -1;
990     private int mGesturePreviewHighlightStart = -1;
991     private int mGesturePreviewHighlightEnd = -1;
992     private Paint mGesturePreviewHighlightPaint;
993     private final List<Path> mPathRecyclePool = new ArrayList<>();
994     private boolean mHighlightPathsBogus = true;
995 
996     // Although these fields are specific to editable text, they are not added to Editor because
997     // they are defined by the TextView's style and are theme-dependent.
998     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
999     int mCursorDrawableRes;
1000     private Drawable mCursorDrawable;
1001     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
1002     // by removing it, but we would break apps targeting <= P that use it by reflection.
1003     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1004     int mTextSelectHandleLeftRes;
1005     private Drawable mTextSelectHandleLeft;
1006     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
1007     // by removing it, but we would break apps targeting <= P that use it by reflection.
1008     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1009     int mTextSelectHandleRightRes;
1010     private Drawable mTextSelectHandleRight;
1011     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
1012     // by removing it, but we would break apps targeting <= P that use it by reflection.
1013     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
1014     int mTextSelectHandleRes;
1015     private Drawable mTextSelectHandle;
1016     int mTextEditSuggestionItemLayout;
1017     int mTextEditSuggestionContainerLayout;
1018     int mTextEditSuggestionHighlightStyle;
1019 
1020     private static final int NO_POINTER_ID = -1;
1021     /**
1022      * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among
1023      * TextView and the handle views which are rendered on popup windows.
1024      */
1025     private int mPrimePointerId = NO_POINTER_ID;
1026 
1027     /**
1028      * Whether the prime pointer is from the event delivered to selection handle or insertion
1029      * handle.
1030      */
1031     private boolean mIsPrimePointerFromHandleView;
1032 
1033     /**
1034      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
1035      * See {@link #createEditorIfNeeded()}.
1036      */
1037     @UnsupportedAppUsage
1038     private Editor mEditor;
1039 
1040     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
1041     private static final int DEVICE_PROVISIONED_NO = 1;
1042     private static final int DEVICE_PROVISIONED_YES = 2;
1043 
1044     /**
1045      * Some special options such as sharing selected text should only be shown if the device
1046      * is provisioned. Only check the provisioned state once for a given view instance.
1047      */
1048     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
1049 
1050     /**
1051      * The last input source on this TextView.
1052      *
1053      * Use the SOURCE_TOUCHSCREEN as the default value for backward compatibility. There could be a
1054      * non UI event originated ActionMode initiation, e.g. API call, a11y events, etc.
1055      */
1056     private int mLastInputSource = InputDevice.SOURCE_TOUCHSCREEN;
1057 
1058     /**
1059      * The TextView does not auto-size text (default).
1060      */
1061     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
1062 
1063     /**
1064      * The TextView scales text size both horizontally and vertically to fit within the
1065      * container.
1066      */
1067     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
1068 
1069     /** @hide */
1070     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
1071             AUTO_SIZE_TEXT_TYPE_NONE,
1072             AUTO_SIZE_TEXT_TYPE_UNIFORM
1073     })
1074     @Retention(RetentionPolicy.SOURCE)
1075     public @interface AutoSizeTextType {}
1076     // Default minimum size for auto-sizing text in scaled pixels.
1077     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
1078     // Default maximum size for auto-sizing text in scaled pixels.
1079     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
1080     // Default value for the step size in pixels.
1081     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
1082     // Use this to specify that any of the auto-size configuration int values have not been set.
1083     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
1084     // Auto-size text type.
1085     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1086     // Specify if auto-size text is needed.
1087     private boolean mNeedsAutoSizeText = false;
1088     // Step size for auto-sizing in pixels.
1089     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1090     // Minimum text size for auto-sizing in pixels.
1091     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1092     // Maximum text size for auto-sizing in pixels.
1093     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1094     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
1095     // when auto-sizing text.
1096     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
1097     // Specifies whether auto-size should use the provided auto size steps set or if it should
1098     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
1099     // mAutoSizeStepGranularityInPx.
1100     private boolean mHasPresetAutoSizeValues = false;
1101 
1102     // Autofill-related attributes
1103     //
1104     // Indicates whether the text was set statically or dynamically, so it can be used to
1105     // sanitize autofill requests.
1106     private boolean mTextSetFromXmlOrResourceId = false;
1107     // Resource id used to set the text.
1108     private @StringRes int mTextId = Resources.ID_NULL;
1109     // Resource id used to set the hint.
1110     private @StringRes int mHintId = Resources.ID_NULL;
1111     //
1112     // End of autofill-related attributes
1113 
1114     private Pattern mWhitespacePattern;
1115 
1116     /**
1117      * Kick-start the font cache for the zygote process (to pay the cost of
1118      * initializing freetype for our default font only once).
1119      * @hide
1120      */
1121     public static void preloadFontCache() {
1122         if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
1123             return;
1124         }
1125         Paint p = new Paint();
1126         p.setAntiAlias(true);
1127         // Ensure that the Typeface is loaded here.
1128         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
1129         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
1130         // since Paint.measureText can not be called without Typeface static initializer.
1131         p.setTypeface(Typeface.DEFAULT);
1132         // We don't care about the result, just the side-effect of measuring.
1133         p.measureText("H");
1134     }
1135 
1136     /**
1137      * Interface definition for a callback to be invoked when an action is
1138      * performed on the editor.
1139      */
1140     public interface OnEditorActionListener {
1141         /**
1142          * Called when an action is being performed.
1143          *
1144          * @param v The view that was clicked.
1145          * @param actionId Identifier of the action.  This will be either the
1146          * identifier you supplied, or {@link EditorInfo#IME_NULL
1147          * EditorInfo.IME_NULL} if being called due to the enter key
1148          * being pressed. Starting from Android 14, the action identifier will
1149          * also be included when triggered by an enter key if the input is
1150          * constrained to a single line.
1151          * @param event If triggered by an enter key, this is the event;
1152          * otherwise, this is null.
1153          * @return Return true if you have consumed the action, else false.
1154          */
1155         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
1156     }
1157 
1158     public TextView(Context context) {
1159         this(context, null);
1160     }
1161 
1162     public TextView(Context context, @Nullable AttributeSet attrs) {
1163         this(context, attrs, com.android.internal.R.attr.textViewStyle);
1164     }
1165 
1166     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
1167         this(context, attrs, defStyleAttr, 0);
1168     }
1169 
1170     @SuppressWarnings("deprecation")
1171     public TextView(
1172             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1173         super(context, attrs, defStyleAttr, defStyleRes);
1174 
1175         // TextView is important by default, unless app developer overrode attribute.
1176         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
1177             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
1178         }
1179         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
1180             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
1181         }
1182 
1183         setTextInternal("");
1184 
1185         final Resources res = getResources();
1186         final CompatibilityInfo compat = res.getCompatibilityInfo();
1187 
1188         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
1189         mTextPaint.density = res.getDisplayMetrics().density;
1190         mTextPaint.setCompatibilityScaling(compat.applicationScale);
1191 
1192         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1193         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
1194 
1195         mMovement = getDefaultMovementMethod();
1196 
1197         mTransformation = null;
1198 
1199         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
1200         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
1201         attributes.mTextSize = 15;
1202         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1203         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1204         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1205         mLastOrientation = getResources().getConfiguration().orientation;
1206 
1207         final Resources.Theme theme = context.getTheme();
1208 
1209         /*
1210          * Look the appearance up without checking first if it exists because
1211          * almost every TextView has one and it greatly simplifies the logic
1212          * to be able to parse the appearance first and then let specific tags
1213          * for this View override it.
1214          */
1215         TypedArray a = theme.obtainStyledAttributes(attrs,
1216                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1217         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1218                 attrs, a, defStyleAttr, defStyleRes);
1219         TypedArray appearance = null;
1220         int ap = a.getResourceId(
1221                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1222         a.recycle();
1223         if (ap != -1) {
1224             appearance = theme.obtainStyledAttributes(
1225                     ap, com.android.internal.R.styleable.TextAppearance);
1226             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1227                     null, appearance, 0, ap);
1228         }
1229         if (appearance != null) {
1230             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1231             attributes.mFontFamilyExplicit = false;
1232             appearance.recycle();
1233         }
1234 
1235         boolean editable = getDefaultEditable();
1236         CharSequence inputMethod = null;
1237         int numeric = 0;
1238         CharSequence digits = null;
1239         boolean phone = false;
1240         boolean autotext = false;
1241         int autocap = -1;
1242         int buffertype = 0;
1243         boolean selectallonfocus = false;
1244         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1245                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1246         ColorStateList drawableTint = null;
1247         BlendMode drawableTintMode = null;
1248         int drawablePadding = 0;
1249         int ellipsize = ELLIPSIZE_NOT_SET;
1250         boolean singleLine = false;
1251         int maxlength = -1;
1252         CharSequence text = "";
1253         CharSequence hint = null;
1254         boolean password = false;
1255         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1256         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1257         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1258         int inputType = EditorInfo.TYPE_NULL;
1259         a = theme.obtainStyledAttributes(
1260                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1261         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1262                 defStyleAttr, defStyleRes);
1263         int firstBaselineToTopHeight = -1;
1264         int lastBaselineToBottomHeight = -1;
1265         float lineHeight = -1f;
1266         int lineHeightUnit = -1;
1267         boolean hasUseBoundForWidthValue = false;
1268 
1269         readTextAppearance(context, a, attributes, true /* styleArray */);
1270 
1271         int n = a.getIndexCount();
1272 
1273         // Must set id in a temporary variable because it will be reset by setText()
1274         boolean textIsSetFromXml = false;
1275         for (int i = 0; i < n; i++) {
1276             int attr = a.getIndex(i);
1277 
1278             switch (attr) {
1279                 case com.android.internal.R.styleable.TextView_editable:
1280                     editable = a.getBoolean(attr, editable);
1281                     break;
1282 
1283                 case com.android.internal.R.styleable.TextView_inputMethod:
1284                     inputMethod = a.getText(attr);
1285                     break;
1286 
1287                 case com.android.internal.R.styleable.TextView_numeric:
1288                     numeric = a.getInt(attr, numeric);
1289                     break;
1290 
1291                 case com.android.internal.R.styleable.TextView_digits:
1292                     digits = a.getText(attr);
1293                     break;
1294 
1295                 case com.android.internal.R.styleable.TextView_phoneNumber:
1296                     phone = a.getBoolean(attr, phone);
1297                     break;
1298 
1299                 case com.android.internal.R.styleable.TextView_autoText:
1300                     autotext = a.getBoolean(attr, autotext);
1301                     break;
1302 
1303                 case com.android.internal.R.styleable.TextView_capitalize:
1304                     autocap = a.getInt(attr, autocap);
1305                     break;
1306 
1307                 case com.android.internal.R.styleable.TextView_bufferType:
1308                     buffertype = a.getInt(attr, buffertype);
1309                     break;
1310 
1311                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1312                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1313                     break;
1314 
1315                 case com.android.internal.R.styleable.TextView_autoLink:
1316                     mAutoLinkMask = a.getInt(attr, 0);
1317                     break;
1318 
1319                 case com.android.internal.R.styleable.TextView_linksClickable:
1320                     mLinksClickable = a.getBoolean(attr, true);
1321                     break;
1322 
1323                 case com.android.internal.R.styleable.TextView_drawableLeft:
1324                     drawableLeft = a.getDrawable(attr);
1325                     break;
1326 
1327                 case com.android.internal.R.styleable.TextView_drawableTop:
1328                     drawableTop = a.getDrawable(attr);
1329                     break;
1330 
1331                 case com.android.internal.R.styleable.TextView_drawableRight:
1332                     drawableRight = a.getDrawable(attr);
1333                     break;
1334 
1335                 case com.android.internal.R.styleable.TextView_drawableBottom:
1336                     drawableBottom = a.getDrawable(attr);
1337                     break;
1338 
1339                 case com.android.internal.R.styleable.TextView_drawableStart:
1340                     drawableStart = a.getDrawable(attr);
1341                     break;
1342 
1343                 case com.android.internal.R.styleable.TextView_drawableEnd:
1344                     drawableEnd = a.getDrawable(attr);
1345                     break;
1346 
1347                 case com.android.internal.R.styleable.TextView_drawableTint:
1348                     drawableTint = a.getColorStateList(attr);
1349                     break;
1350 
1351                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1352                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1353                             drawableTintMode);
1354                     break;
1355 
1356                 case com.android.internal.R.styleable.TextView_drawablePadding:
1357                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1358                     break;
1359 
1360                 case com.android.internal.R.styleable.TextView_maxLines:
1361                     setMaxLines(a.getInt(attr, -1));
1362                     break;
1363 
1364                 case com.android.internal.R.styleable.TextView_maxHeight:
1365                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1366                     break;
1367 
1368                 case com.android.internal.R.styleable.TextView_lines:
1369                     setLines(a.getInt(attr, -1));
1370                     break;
1371 
1372                 case com.android.internal.R.styleable.TextView_height:
1373                     setHeight(a.getDimensionPixelSize(attr, -1));
1374                     break;
1375 
1376                 case com.android.internal.R.styleable.TextView_minLines:
1377                     setMinLines(a.getInt(attr, -1));
1378                     break;
1379 
1380                 case com.android.internal.R.styleable.TextView_minHeight:
1381                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1382                     break;
1383 
1384                 case com.android.internal.R.styleable.TextView_maxEms:
1385                     setMaxEms(a.getInt(attr, -1));
1386                     break;
1387 
1388                 case com.android.internal.R.styleable.TextView_maxWidth:
1389                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1390                     break;
1391 
1392                 case com.android.internal.R.styleable.TextView_ems:
1393                     setEms(a.getInt(attr, -1));
1394                     break;
1395 
1396                 case com.android.internal.R.styleable.TextView_width:
1397                     setWidth(a.getDimensionPixelSize(attr, -1));
1398                     break;
1399 
1400                 case com.android.internal.R.styleable.TextView_minEms:
1401                     setMinEms(a.getInt(attr, -1));
1402                     break;
1403 
1404                 case com.android.internal.R.styleable.TextView_minWidth:
1405                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1406                     break;
1407 
1408                 case com.android.internal.R.styleable.TextView_gravity:
1409                     setGravity(a.getInt(attr, -1));
1410                     break;
1411 
1412                 case com.android.internal.R.styleable.TextView_hint:
1413                     mHintId = a.getResourceId(attr, Resources.ID_NULL);
1414                     hint = a.getText(attr);
1415                     break;
1416 
1417                 case com.android.internal.R.styleable.TextView_text:
1418                     textIsSetFromXml = true;
1419                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1420                     text = a.getText(attr);
1421                     break;
1422 
1423                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1424                     if (a.getBoolean(attr, false)) {
1425                         setHorizontallyScrolling(true);
1426                     }
1427                     break;
1428 
1429                 case com.android.internal.R.styleable.TextView_singleLine:
1430                     singleLine = a.getBoolean(attr, singleLine);
1431                     break;
1432 
1433                 case com.android.internal.R.styleable.TextView_ellipsize:
1434                     ellipsize = a.getInt(attr, ellipsize);
1435                     break;
1436 
1437                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1438                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1439                     break;
1440 
1441                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1442                     if (!a.getBoolean(attr, true)) {
1443                         setIncludeFontPadding(false);
1444                     }
1445                     break;
1446 
1447                 case com.android.internal.R.styleable.TextView_cursorVisible:
1448                     if (!a.getBoolean(attr, true)) {
1449                         setCursorVisible(false);
1450                     }
1451                     break;
1452 
1453                 case com.android.internal.R.styleable.TextView_maxLength:
1454                     maxlength = a.getInt(attr, -1);
1455                     break;
1456 
1457                 case com.android.internal.R.styleable.TextView_textScaleX:
1458                     setTextScaleX(a.getFloat(attr, 1.0f));
1459                     break;
1460 
1461                 case com.android.internal.R.styleable.TextView_freezesText:
1462                     mFreezesText = a.getBoolean(attr, false);
1463                     break;
1464 
1465                 case com.android.internal.R.styleable.TextView_enabled:
1466                     setEnabled(a.getBoolean(attr, isEnabled()));
1467                     break;
1468 
1469                 case com.android.internal.R.styleable.TextView_password:
1470                     password = a.getBoolean(attr, password);
1471                     break;
1472 
1473                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1474                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1475                     break;
1476 
1477                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1478                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1479                     break;
1480 
1481                 case com.android.internal.R.styleable.TextView_inputType:
1482                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1483                     break;
1484 
1485                 case com.android.internal.R.styleable.TextView_allowUndo:
1486                     createEditorIfNeeded();
1487                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1488                     break;
1489 
1490                 case com.android.internal.R.styleable.TextView_imeOptions:
1491                     createEditorIfNeeded();
1492                     mEditor.createInputContentTypeIfNeeded();
1493                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1494                             mEditor.mInputContentType.imeOptions);
1495                     break;
1496 
1497                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1498                     createEditorIfNeeded();
1499                     mEditor.createInputContentTypeIfNeeded();
1500                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1501                     break;
1502 
1503                 case com.android.internal.R.styleable.TextView_imeActionId:
1504                     createEditorIfNeeded();
1505                     mEditor.createInputContentTypeIfNeeded();
1506                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1507                             mEditor.mInputContentType.imeActionId);
1508                     break;
1509 
1510                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1511                     setPrivateImeOptions(a.getString(attr));
1512                     break;
1513 
1514                 case com.android.internal.R.styleable.TextView_editorExtras:
1515                     try {
1516                         setInputExtras(a.getResourceId(attr, 0));
1517                     } catch (XmlPullParserException e) {
1518                         Log.w(LOG_TAG, "Failure reading input extras", e);
1519                     } catch (IOException e) {
1520                         Log.w(LOG_TAG, "Failure reading input extras", e);
1521                     }
1522                     break;
1523 
1524                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1525                     mCursorDrawableRes = a.getResourceId(attr, 0);
1526                     break;
1527 
1528                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1529                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1530                     break;
1531 
1532                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1533                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1534                     break;
1535 
1536                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1537                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1538                     break;
1539 
1540                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1541                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1542                     break;
1543 
1544                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1545                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1546                     break;
1547 
1548                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1549                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1550                     break;
1551 
1552                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1553                     setTextIsSelectable(a.getBoolean(attr, false));
1554                     break;
1555 
1556                 case com.android.internal.R.styleable.TextView_breakStrategy:
1557                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1558                     break;
1559 
1560                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1561                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1562                     break;
1563 
1564                 case com.android.internal.R.styleable.TextView_lineBreakStyle:
1565                     mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE);
1566                     break;
1567 
1568                 case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
1569                     mLineBreakWordStyle = a.getInt(attr,
1570                             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
1571                     break;
1572 
1573                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1574                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1575                     break;
1576 
1577                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1578                     autoSizeStepGranularityInPx = a.getDimension(attr,
1579                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1580                     break;
1581 
1582                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1583                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1584                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1585                     break;
1586 
1587                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1588                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1589                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1590                     break;
1591 
1592                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1593                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1594                     if (autoSizeStepSizeArrayResId > 0) {
1595                         final TypedArray autoSizePresetTextSizes = a.getResources()
1596                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1597                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1598                         autoSizePresetTextSizes.recycle();
1599                     }
1600                     break;
1601                 case com.android.internal.R.styleable.TextView_justificationMode:
1602                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1603                     break;
1604 
1605                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1606                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1607                     break;
1608 
1609                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1610                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1611                     break;
1612 
1613                 case com.android.internal.R.styleable.TextView_lineHeight:
1614                     TypedValue peekValue = a.peekValue(attr);
1615                     if (peekValue != null && peekValue.type == TypedValue.TYPE_DIMENSION) {
1616                         lineHeightUnit = peekValue.getComplexUnit();
1617                         lineHeight = TypedValue.complexToFloat(peekValue.data);
1618                     } else {
1619                         lineHeight = a.getDimensionPixelSize(attr, -1);
1620                     }
1621                     break;
1622                 case com.android.internal.R.styleable.TextView_useBoundsForWidth:
1623                     mUseBoundsForWidth = a.getBoolean(attr, false);
1624                     hasUseBoundForWidthValue = true;
1625                     break;
1626                 case com.android.internal.R.styleable
1627                         .TextView_shiftDrawingOffsetForStartOverhang:
1628                     mShiftDrawingOffsetForStartOverhang = a.getBoolean(attr, false);
1629                     break;
1630                 case com.android.internal.R.styleable
1631                         .TextView_useLocalePreferredLineHeightForMinimum:
1632                     mUseLocalePreferredLineHeightForMinimum = a.getBoolean(attr, false);
1633                     break;
1634             }
1635         }
1636 
1637         a.recycle();
1638 
1639         BufferType bufferType = BufferType.EDITABLE;
1640 
1641         final int variation =
1642                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1643         final boolean passwordInputType = variation
1644                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1645         final boolean webPasswordInputType = variation
1646                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1647         final boolean numberPasswordInputType = variation
1648                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1649 
1650         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1651         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1652         if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
1653             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL;
1654         } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) {
1655             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
1656         } else {
1657             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
1658         }
1659 
1660         if (!hasUseBoundForWidthValue) {
1661             if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
1662                 mUseBoundsForWidth = ClientFlags.useBoundsForWidth();
1663             } else {
1664                 mUseBoundsForWidth = false;
1665             }
1666         }
1667 
1668         // TODO(b/179693024): Use a ChangeId instead.
1669         mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R;
1670 
1671         if (inputMethod != null) {
1672             Class<?> c;
1673 
1674             try {
1675                 c = Class.forName(inputMethod.toString());
1676             } catch (ClassNotFoundException ex) {
1677                 throw new RuntimeException(ex);
1678             }
1679 
1680             try {
1681                 createEditorIfNeeded();
1682                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1683             } catch (InstantiationException ex) {
1684                 throw new RuntimeException(ex);
1685             } catch (IllegalAccessException ex) {
1686                 throw new RuntimeException(ex);
1687             }
1688             try {
1689                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1690                         ? inputType
1691                         : mEditor.mKeyListener.getInputType();
1692             } catch (IncompatibleClassChangeError e) {
1693                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1694             }
1695         } else if (digits != null) {
1696             createEditorIfNeeded();
1697             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1698             // If no input type was specified, we will default to generic
1699             // text, since we can't tell the IME about the set of digits
1700             // that was selected.
1701             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1702                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1703         } else if (inputType != EditorInfo.TYPE_NULL) {
1704             setInputType(inputType, true);
1705             // If set, the input type overrides what was set using the deprecated singleLine flag.
1706             singleLine = !isMultilineInputType(inputType);
1707         } else if (phone) {
1708             createEditorIfNeeded();
1709             mEditor.mKeyListener = DialerKeyListener.getInstance();
1710             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1711         } else if (numeric != 0) {
1712             createEditorIfNeeded();
1713             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1714                     null,  // locale
1715                     (numeric & SIGNED) != 0,
1716                     (numeric & DECIMAL) != 0);
1717             inputType = mEditor.mKeyListener.getInputType();
1718             mEditor.mInputType = inputType;
1719         } else if (autotext || autocap != -1) {
1720             TextKeyListener.Capitalize cap;
1721 
1722             inputType = EditorInfo.TYPE_CLASS_TEXT;
1723 
1724             switch (autocap) {
1725                 case 1:
1726                     cap = TextKeyListener.Capitalize.SENTENCES;
1727                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1728                     break;
1729 
1730                 case 2:
1731                     cap = TextKeyListener.Capitalize.WORDS;
1732                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1733                     break;
1734 
1735                 case 3:
1736                     cap = TextKeyListener.Capitalize.CHARACTERS;
1737                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1738                     break;
1739 
1740                 default:
1741                     cap = TextKeyListener.Capitalize.NONE;
1742                     break;
1743             }
1744 
1745             createEditorIfNeeded();
1746             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1747             mEditor.mInputType = inputType;
1748         } else if (editable) {
1749             createEditorIfNeeded();
1750             mEditor.mKeyListener = TextKeyListener.getInstance();
1751             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1752         } else if (isTextSelectable()) {
1753             // Prevent text changes from keyboard.
1754             if (mEditor != null) {
1755                 mEditor.mKeyListener = null;
1756                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1757             }
1758             bufferType = BufferType.SPANNABLE;
1759             // So that selection can be changed using arrow keys and touch is handled.
1760             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1761         } else {
1762             if (mEditor != null) mEditor.mKeyListener = null;
1763 
1764             switch (buffertype) {
1765                 case 0:
1766                     bufferType = BufferType.NORMAL;
1767                     break;
1768                 case 1:
1769                     bufferType = BufferType.SPANNABLE;
1770                     break;
1771                 case 2:
1772                     bufferType = BufferType.EDITABLE;
1773                     break;
1774             }
1775         }
1776 
1777         if (mEditor != null) {
1778             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1779                     numberPasswordInputType);
1780         }
1781 
1782         if (selectallonfocus) {
1783             createEditorIfNeeded();
1784             mEditor.mSelectAllOnFocus = true;
1785 
1786             if (bufferType == BufferType.NORMAL) {
1787                 bufferType = BufferType.SPANNABLE;
1788             }
1789         }
1790 
1791         // Set up the tint (if needed) before setting the drawables so that it
1792         // gets applied correctly.
1793         if (drawableTint != null || drawableTintMode != null) {
1794             if (mDrawables == null) {
1795                 mDrawables = new Drawables(context);
1796             }
1797             if (drawableTint != null) {
1798                 mDrawables.mTintList = drawableTint;
1799                 mDrawables.mHasTint = true;
1800             }
1801             if (drawableTintMode != null) {
1802                 mDrawables.mBlendMode = drawableTintMode;
1803                 mDrawables.mHasTintMode = true;
1804             }
1805         }
1806 
1807         // This call will save the initial left/right drawables
1808         setCompoundDrawablesWithIntrinsicBounds(
1809                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1810         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1811         setCompoundDrawablePadding(drawablePadding);
1812 
1813         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1814         // of lines of height are unchanged for multi-line TextViews.
1815         setInputTypeSingleLine(singleLine);
1816         applySingleLine(singleLine, singleLine, singleLine,
1817                 // Does not apply automated max length filter since length filter will be resolved
1818                 // later in this function.
1819                 false
1820         );
1821 
1822         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1823             ellipsize = ELLIPSIZE_END;
1824         }
1825 
1826         switch (ellipsize) {
1827             case ELLIPSIZE_START:
1828                 setEllipsize(TextUtils.TruncateAt.START);
1829                 break;
1830             case ELLIPSIZE_MIDDLE:
1831                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1832                 break;
1833             case ELLIPSIZE_END:
1834                 setEllipsize(TextUtils.TruncateAt.END);
1835                 break;
1836             case ELLIPSIZE_MARQUEE:
1837                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1838                     setHorizontalFadingEdgeEnabled(true);
1839                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1840                 } else {
1841                     setHorizontalFadingEdgeEnabled(false);
1842                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1843                 }
1844                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1845                 break;
1846         }
1847 
1848         final boolean isPassword = password || passwordInputType || webPasswordInputType
1849                 || numberPasswordInputType;
1850         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1851                 && (mEditor.mInputType
1852                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1853                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1854         if (isMonospaceEnforced) {
1855             attributes.mTypefaceIndex = MONOSPACE;
1856         }
1857 
1858         mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment;
1859         applyTextAppearance(attributes);
1860 
1861         if (isPassword) {
1862             setTransformationMethod(PasswordTransformationMethod.getInstance());
1863         }
1864 
1865         // For addressing b/145128646
1866         // For the performance reason, we limit characters for single line text field.
1867         if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) {
1868             mSingleLineLengthFilter = new InputFilter.LengthFilter(
1869                 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
1870         }
1871 
1872         if (mSingleLineLengthFilter != null) {
1873             setFilters(new InputFilter[] { mSingleLineLengthFilter });
1874         } else if (maxlength >= 0) {
1875             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1876         } else {
1877             setFilters(NO_FILTERS);
1878         }
1879 
1880         setText(text, bufferType);
1881         if (mText == null) {
1882             mText = "";
1883         }
1884         if (mTransformed == null) {
1885             mTransformed = "";
1886         }
1887 
1888         if (textIsSetFromXml) {
1889             mTextSetFromXmlOrResourceId = true;
1890         }
1891 
1892         if (hint != null) setHint(hint);
1893 
1894         /*
1895          * Views are not normally clickable unless specified to be.
1896          * However, TextViews that have input or movement methods *are*
1897          * clickable by default. By setting clickable here, we implicitly set focusable as well
1898          * if not overridden by the developer.
1899          */
1900         a = context.obtainStyledAttributes(
1901                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1902         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1903         boolean clickable = canInputOrMove || isClickable();
1904         boolean longClickable = canInputOrMove || isLongClickable();
1905         int focusable = getFocusable();
1906         boolean isAutoHandwritingEnabled = true;
1907 
1908         n = a.getIndexCount();
1909         for (int i = 0; i < n; i++) {
1910             int attr = a.getIndex(i);
1911 
1912             switch (attr) {
1913                 case com.android.internal.R.styleable.View_focusable:
1914                     TypedValue val = new TypedValue();
1915                     if (a.getValue(attr, val)) {
1916                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1917                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1918                                 : val.data;
1919                     }
1920                     break;
1921 
1922                 case com.android.internal.R.styleable.View_clickable:
1923                     clickable = a.getBoolean(attr, clickable);
1924                     break;
1925 
1926                 case com.android.internal.R.styleable.View_longClickable:
1927                     longClickable = a.getBoolean(attr, longClickable);
1928                     break;
1929 
1930                 case com.android.internal.R.styleable.View_autoHandwritingEnabled:
1931                     isAutoHandwritingEnabled = a.getBoolean(attr, true);
1932                     break;
1933             }
1934         }
1935         a.recycle();
1936 
1937         // Some apps were relying on the undefined behavior of focusable winning over
1938         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1939         // when starting with EditText and setting only focusable=false). To keep those apps from
1940         // breaking, re-apply the focusable attribute here.
1941         if (focusable != getFocusable()) {
1942             setFocusable(focusable);
1943         }
1944         setClickable(clickable);
1945         setLongClickable(longClickable);
1946         setAutoHandwritingEnabled(isAutoHandwritingEnabled);
1947 
1948         if (mEditor != null) mEditor.prepareCursorControllers();
1949 
1950         // If not explicitly specified this view is important for accessibility.
1951         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1952             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1953         }
1954 
1955         if (supportsAutoSizeText()) {
1956             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1957                 // If uniform auto-size has been specified but preset values have not been set then
1958                 // replace the auto-size configuration values that have not been specified with the
1959                 // defaults.
1960                 if (!mHasPresetAutoSizeValues) {
1961                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1962 
1963                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1964                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1965                                 TypedValue.COMPLEX_UNIT_SP,
1966                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1967                                 displayMetrics);
1968                     }
1969 
1970                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1971                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1972                                 TypedValue.COMPLEX_UNIT_SP,
1973                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1974                                 displayMetrics);
1975                     }
1976 
1977                     if (autoSizeStepGranularityInPx
1978                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1979                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1980                     }
1981 
1982                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1983                             autoSizeMaxTextSizeInPx,
1984                             autoSizeStepGranularityInPx);
1985                 }
1986 
1987                 setupAutoSizeText();
1988             }
1989         } else {
1990             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1991         }
1992 
1993         if (firstBaselineToTopHeight >= 0) {
1994             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1995         }
1996         if (lastBaselineToBottomHeight >= 0) {
1997             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1998         }
1999         if (lineHeight >= 0) {
2000             if (lineHeightUnit == -1) {
2001                 setLineHeightPx(lineHeight);
2002             } else {
2003                 setLineHeight(lineHeightUnit, lineHeight);
2004             }
2005         }
2006     }
2007 
2008     // Update mText and mPrecomputed
setTextInternal(@ullable CharSequence text)2009     private void setTextInternal(@Nullable CharSequence text) {
2010         mText = text;
2011         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
2012         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
2013     }
2014 
2015     /**
2016      * Specify whether this widget should automatically scale the text to try to perfectly fit
2017      * within the layout bounds by using the default auto-size configuration.
2018      *
2019      * @param autoSizeTextType the type of auto-size. Must be one of
2020      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
2021      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
2022      *
2023      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
2024      *
2025      * @attr ref android.R.styleable#TextView_autoSizeTextType
2026      *
2027      * @see #getAutoSizeTextType()
2028      */
setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)2029     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
2030         if (supportsAutoSizeText()) {
2031             switch (autoSizeTextType) {
2032                 case AUTO_SIZE_TEXT_TYPE_NONE:
2033                     clearAutoSizeConfiguration();
2034                     break;
2035                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
2036                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2037                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
2038                             TypedValue.COMPLEX_UNIT_SP,
2039                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
2040                             displayMetrics);
2041                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
2042                             TypedValue.COMPLEX_UNIT_SP,
2043                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
2044                             displayMetrics);
2045 
2046                     validateAndSetAutoSizeTextTypeUniformConfiguration(
2047                             autoSizeMinTextSizeInPx,
2048                             autoSizeMaxTextSizeInPx,
2049                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
2050                     if (setupAutoSizeText()) {
2051                         autoSizeText();
2052                         invalidate();
2053                     }
2054                     break;
2055                 default:
2056                     throw new IllegalArgumentException(
2057                             "Unknown auto-size text type: " + autoSizeTextType);
2058             }
2059         }
2060     }
2061 
2062     /**
2063      * Specify whether this widget should automatically scale the text to try to perfectly fit
2064      * within the layout bounds. If all the configuration params are valid the type of auto-size is
2065      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
2066      *
2067      * @param autoSizeMinTextSize the minimum text size available for auto-size
2068      * @param autoSizeMaxTextSize the maximum text size available for auto-size
2069      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
2070      *                                the minimum and maximum text size in order to build the set of
2071      *                                text sizes the system uses to choose from when auto-sizing
2072      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
2073      *             possible dimension units
2074      *
2075      * @throws IllegalArgumentException if any of the configuration params are invalid.
2076      *
2077      * @attr ref android.R.styleable#TextView_autoSizeTextType
2078      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2079      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2080      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
2081      *
2082      * @see #setAutoSizeTextTypeWithDefaults(int)
2083      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2084      * @see #getAutoSizeMinTextSize()
2085      * @see #getAutoSizeMaxTextSize()
2086      * @see #getAutoSizeStepGranularity()
2087      * @see #getAutoSizeTextAvailableSizes()
2088      */
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)2089     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
2090             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
2091         if (supportsAutoSizeText()) {
2092             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2093             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
2094                     unit, autoSizeMinTextSize, displayMetrics);
2095             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
2096                     unit, autoSizeMaxTextSize, displayMetrics);
2097             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
2098                     unit, autoSizeStepGranularity, displayMetrics);
2099 
2100             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
2101                     autoSizeMaxTextSizeInPx,
2102                     autoSizeStepGranularityInPx);
2103 
2104             if (setupAutoSizeText()) {
2105                 autoSizeText();
2106                 invalidate();
2107             }
2108         }
2109     }
2110 
2111     /**
2112      * Specify whether this widget should automatically scale the text to try to perfectly fit
2113      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
2114      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
2115      *
2116      * @param presetSizes an {@code int} array of sizes in pixels
2117      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
2118      *             the possible dimension units
2119      *
2120      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
2121      *
2122      * @attr ref android.R.styleable#TextView_autoSizeTextType
2123      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
2124      *
2125      * @see #setAutoSizeTextTypeWithDefaults(int)
2126      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2127      * @see #getAutoSizeMinTextSize()
2128      * @see #getAutoSizeMaxTextSize()
2129      * @see #getAutoSizeTextAvailableSizes()
2130      */
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)2131     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
2132         if (supportsAutoSizeText()) {
2133             final int presetSizesLength = presetSizes.length;
2134             if (presetSizesLength > 0) {
2135                 int[] presetSizesInPx = new int[presetSizesLength];
2136 
2137                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
2138                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
2139                 } else {
2140                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
2141                     // Convert all to sizes to pixels.
2142                     for (int i = 0; i < presetSizesLength; i++) {
2143                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
2144                             presetSizes[i], displayMetrics));
2145                     }
2146                 }
2147 
2148                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
2149                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
2150                     throw new IllegalArgumentException("None of the preset sizes is valid: "
2151                             + Arrays.toString(presetSizes));
2152                 }
2153             } else {
2154                 mHasPresetAutoSizeValues = false;
2155             }
2156 
2157             if (setupAutoSizeText()) {
2158                 autoSizeText();
2159                 invalidate();
2160             }
2161         }
2162     }
2163 
2164     /**
2165      * Returns the type of auto-size set for this widget.
2166      *
2167      * @return an {@code int} corresponding to one of the auto-size types:
2168      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
2169      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
2170      *
2171      * @attr ref android.R.styleable#TextView_autoSizeTextType
2172      *
2173      * @see #setAutoSizeTextTypeWithDefaults(int)
2174      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2175      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2176      */
2177     @InspectableProperty(enumMapping = {
2178             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
2179             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
2180     })
2181     @AutoSizeTextType
getAutoSizeTextType()2182     public int getAutoSizeTextType() {
2183         return mAutoSizeTextType;
2184     }
2185 
2186     /**
2187      * @return the current auto-size step granularity in pixels.
2188      *
2189      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
2190      *
2191      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2192      */
2193     @InspectableProperty
getAutoSizeStepGranularity()2194     public int getAutoSizeStepGranularity() {
2195         return Math.round(mAutoSizeStepGranularityInPx);
2196     }
2197 
2198     /**
2199      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
2200      *         if auto-size has not been configured this function returns {@code -1}.
2201      *
2202      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
2203      *
2204      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2205      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2206      */
2207     @InspectableProperty
getAutoSizeMinTextSize()2208     public int getAutoSizeMinTextSize() {
2209         return Math.round(mAutoSizeMinTextSizeInPx);
2210     }
2211 
2212     /**
2213      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
2214      *         if auto-size has not been configured this function returns {@code -1}.
2215      *
2216      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
2217      *
2218      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2219      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2220      */
2221     @InspectableProperty
getAutoSizeMaxTextSize()2222     public int getAutoSizeMaxTextSize() {
2223         return Math.round(mAutoSizeMaxTextSizeInPx);
2224     }
2225 
2226     /**
2227      * @return the current auto-size {@code int} sizes array (in pixels).
2228      *
2229      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
2230      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
2231      */
getAutoSizeTextAvailableSizes()2232     public int[] getAutoSizeTextAvailableSizes() {
2233         return mAutoSizeTextSizesInPx;
2234     }
2235 
setupAutoSizeUniformPresetSizes(TypedArray textSizes)2236     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
2237         final int textSizesLength = textSizes.length();
2238         final int[] parsedSizes = new int[textSizesLength];
2239 
2240         if (textSizesLength > 0) {
2241             for (int i = 0; i < textSizesLength; i++) {
2242                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
2243             }
2244             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
2245             setupAutoSizeUniformPresetSizesConfiguration();
2246         }
2247     }
2248 
setupAutoSizeUniformPresetSizesConfiguration()2249     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
2250         final int sizesLength = mAutoSizeTextSizesInPx.length;
2251         mHasPresetAutoSizeValues = sizesLength > 0;
2252         if (mHasPresetAutoSizeValues) {
2253             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2254             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
2255             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
2256             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2257         }
2258         return mHasPresetAutoSizeValues;
2259     }
2260 
2261     /**
2262      * If all params are valid then save the auto-size configuration.
2263      *
2264      * @throws IllegalArgumentException if any of the params are invalid
2265      */
validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2266     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
2267             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
2268         // First validate.
2269         if (autoSizeMinTextSizeInPx <= 0) {
2270             throw new IllegalArgumentException("Minimum auto-size text size ("
2271                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
2272         }
2273 
2274         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2275             throw new IllegalArgumentException("Maximum auto-size text size ("
2276                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2277                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2278         }
2279 
2280         if (autoSizeStepGranularityInPx <= 0) {
2281             throw new IllegalArgumentException("The auto-size step granularity ("
2282                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2283         }
2284 
2285         // All good, persist the configuration.
2286         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2287         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2288         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2289         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2290         mHasPresetAutoSizeValues = false;
2291     }
2292 
clearAutoSizeConfiguration()2293     private void clearAutoSizeConfiguration() {
2294         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2295         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2296         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2297         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2298         mAutoSizeTextSizesInPx = EmptyArray.INT;
2299         mNeedsAutoSizeText = false;
2300     }
2301 
2302     // Returns distinct sorted positive values.
cleanupAutoSizePresetSizes(int[] presetValues)2303     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2304         final int presetValuesLength = presetValues.length;
2305         if (presetValuesLength == 0) {
2306             return presetValues;
2307         }
2308         Arrays.sort(presetValues);
2309 
2310         final IntArray uniqueValidSizes = new IntArray();
2311         for (int i = 0; i < presetValuesLength; i++) {
2312             final int currentPresetValue = presetValues[i];
2313 
2314             if (currentPresetValue > 0
2315                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2316                 uniqueValidSizes.add(currentPresetValue);
2317             }
2318         }
2319 
2320         return presetValuesLength == uniqueValidSizes.size()
2321             ? presetValues
2322             : uniqueValidSizes.toArray();
2323     }
2324 
setupAutoSizeText()2325     private boolean setupAutoSizeText() {
2326         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2327             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2328             // not have a predefined set of sizes or if the current sizes array is empty.
2329             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2330                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2331                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2332                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2333                 for (int i = 0; i < autoSizeValuesLength; i++) {
2334                     autoSizeTextSizesInPx[i] = Math.round(
2335                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2336                 }
2337                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2338             }
2339 
2340             mNeedsAutoSizeText = true;
2341         } else {
2342             mNeedsAutoSizeText = false;
2343         }
2344 
2345         return mNeedsAutoSizeText;
2346     }
2347 
parseDimensionArray(TypedArray dimens)2348     private int[] parseDimensionArray(TypedArray dimens) {
2349         if (dimens == null) {
2350             return null;
2351         }
2352         int[] result = new int[dimens.length()];
2353         for (int i = 0; i < result.length; i++) {
2354             result[i] = dimens.getDimensionPixelSize(i, 0);
2355         }
2356         return result;
2357     }
2358 
2359     /**
2360      * @hide
2361      */
2362     @TestApi
2363     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)2364     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
2365         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2366             if (resultCode == Activity.RESULT_OK && data != null) {
2367                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2368                 if (result != null) {
2369                     if (isTextEditable()) {
2370                         ClipData clip = ClipData.newPlainText("", result);
2371                         ContentInfo payload =
2372                                 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build();
2373                         performReceiveContent(payload);
2374                         if (mEditor != null) {
2375                             mEditor.refreshTextActionMode();
2376                         }
2377                     } else {
2378                         if (result.length() > 0) {
2379                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2380                                 .show();
2381                         }
2382                     }
2383                 }
2384             } else if (mSpannable != null) {
2385                 // Reset the selection.
2386                 Selection.setSelection(mSpannable, getSelectionEnd());
2387             }
2388         }
2389     }
2390 
2391     /**
2392      * Sets the Typeface taking into account the given attributes.
2393      *
2394      * @param typeface a typeface
2395      * @param familyName family name string, e.g. "serif"
2396      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2397      * @param style a typeface style
2398      * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED}
2399      *               if not specified.
2400      */
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)2401     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2402             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2403             @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
2404                     int weight) {
2405         if (typeface == null && familyName != null) {
2406             // Lookup normal Typeface from system font map.
2407             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2408             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2409         } else if (typeface != null) {
2410             resolveStyleAndSetTypeface(typeface, style, weight);
2411         } else {  // both typeface and familyName is null.
2412             switch (typefaceIndex) {
2413                 case SANS:
2414                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2415                     break;
2416                 case SERIF:
2417                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2418                     break;
2419                 case MONOSPACE:
2420                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2421                     break;
2422                 case DEFAULT_TYPEFACE:
2423                 default:
2424                     resolveStyleAndSetTypeface(null, style, weight);
2425                     break;
2426             }
2427         }
2428     }
2429 
resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)2430     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2431             @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
2432                     int weight) {
2433         if (weight >= 0) {
2434             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2435             final boolean italic = (style & Typeface.ITALIC) != 0;
2436             setTypeface(Typeface.create(typeface, weight, italic));
2437         } else {
2438             setTypeface(typeface, style);
2439         }
2440     }
2441 
setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2442     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2443         boolean hasRelativeDrawables = (start != null) || (end != null);
2444         if (hasRelativeDrawables) {
2445             Drawables dr = mDrawables;
2446             if (dr == null) {
2447                 mDrawables = dr = new Drawables(getContext());
2448             }
2449             mDrawables.mOverride = true;
2450             final Rect compoundRect = dr.mCompoundRect;
2451             int[] state = getDrawableState();
2452             if (start != null) {
2453                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2454                 start.setState(state);
2455                 start.copyBounds(compoundRect);
2456                 start.setCallback(this);
2457 
2458                 dr.mDrawableStart = start;
2459                 dr.mDrawableSizeStart = compoundRect.width();
2460                 dr.mDrawableHeightStart = compoundRect.height();
2461             } else {
2462                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2463             }
2464             if (end != null) {
2465                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2466                 end.setState(state);
2467                 end.copyBounds(compoundRect);
2468                 end.setCallback(this);
2469 
2470                 dr.mDrawableEnd = end;
2471                 dr.mDrawableSizeEnd = compoundRect.width();
2472                 dr.mDrawableHeightEnd = compoundRect.height();
2473             } else {
2474                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2475             }
2476             resetResolvedDrawables();
2477             resolveDrawables();
2478             applyCompoundDrawableTint();
2479         }
2480     }
2481 
2482     @android.view.RemotableViewMethod
2483     @Override
setEnabled(boolean enabled)2484     public void setEnabled(boolean enabled) {
2485         if (enabled == isEnabled()) {
2486             return;
2487         }
2488 
2489         if (!enabled) {
2490             // Hide the soft input if the currently active TextView is disabled
2491             InputMethodManager imm = getInputMethodManager();
2492             if (imm != null) {
2493                 imm.hideSoftInputFromView(this, 0);
2494             }
2495         }
2496 
2497         super.setEnabled(enabled);
2498 
2499         if (enabled) {
2500             // Make sure IME is updated with current editor info.
2501             InputMethodManager imm = getInputMethodManager();
2502             if (imm != null) imm.restartInput(this);
2503         }
2504 
2505         // Will change text color
2506         if (mEditor != null) {
2507             mEditor.invalidateTextDisplayList();
2508             mEditor.prepareCursorControllers();
2509 
2510             // start or stop the cursor blinking as appropriate
2511             mEditor.makeBlink();
2512         }
2513     }
2514 
2515     /**
2516      * Sets the typeface and style in which the text should be displayed,
2517      * and turns on the fake bold and italic bits in the Paint if the
2518      * Typeface that you provided does not have all the bits in the
2519      * style that you specified.
2520      *
2521      * @attr ref android.R.styleable#TextView_typeface
2522      * @attr ref android.R.styleable#TextView_textStyle
2523      */
setTypeface(@ullable Typeface tf, @Typeface.Style int style)2524     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2525         if (style > 0) {
2526             if (tf == null) {
2527                 tf = Typeface.defaultFromStyle(style);
2528             } else {
2529                 tf = Typeface.create(tf, style);
2530             }
2531 
2532             setTypeface(tf);
2533             // now compute what (if any) algorithmic styling is needed
2534             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2535             int need = style & ~typefaceStyle;
2536             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2537             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2538         } else {
2539             mTextPaint.setFakeBoldText(false);
2540             mTextPaint.setTextSkewX(0);
2541             setTypeface(tf);
2542         }
2543     }
2544 
2545     /**
2546      * Subclasses override this to specify that they have a KeyListener
2547      * by default even if not specifically called for in the XML options.
2548      */
getDefaultEditable()2549     protected boolean getDefaultEditable() {
2550         return false;
2551     }
2552 
2553     /**
2554      * Subclasses override this to specify a default movement method.
2555      */
getDefaultMovementMethod()2556     protected MovementMethod getDefaultMovementMethod() {
2557         return null;
2558     }
2559 
2560     /**
2561      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2562      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2563      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2564      * the return value from this method to Spannable or Editable, respectively.
2565      *
2566      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2567      * should make your own copy first.</p>
2568      *
2569      * @return The text displayed by the text view.
2570      * @attr ref android.R.styleable#TextView_text
2571      */
2572     @ViewDebug.CapturedViewProperty
2573     @InspectableProperty
getText()2574     public CharSequence getText() {
2575         if (mUseTextPaddingForUiTranslation) {
2576             ViewTranslationCallback callback = getViewTranslationCallback();
2577             if (callback != null && callback instanceof TextViewTranslationCallback) {
2578                 TextViewTranslationCallback defaultCallback =
2579                         (TextViewTranslationCallback) callback;
2580                 if (defaultCallback.isTextPaddingEnabled()
2581                         && defaultCallback.isShowingTranslation()) {
2582                     return defaultCallback.getPaddedText(mText, mTransformed);
2583                 }
2584             }
2585         }
2586         return mText;
2587     }
2588 
2589     /**
2590      * Returns the length, in characters, of the text managed by this TextView
2591      * @return The length of the text managed by the TextView in characters.
2592      */
length()2593     public int length() {
2594         return mText.length();
2595     }
2596 
2597     /**
2598      * Return the text that TextView is displaying as an Editable object. If the text is not
2599      * editable, null is returned.
2600      *
2601      * @see #getText
2602      */
getEditableText()2603     public Editable getEditableText() {
2604         return (mText instanceof Editable) ? (Editable) mText : null;
2605     }
2606 
2607     /**
2608      * @hide
2609      */
2610     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getTransformed()2611     public CharSequence getTransformed() {
2612         return mTransformed;
2613     }
2614 
2615     /**
2616      * Gets the vertical distance between lines of text, in pixels.
2617      * Note that markup within the text can cause individual lines
2618      * to be taller or shorter than this height, and the layout may
2619      * contain additional first-or last-line padding.
2620      * @return The height of one standard line in pixels.
2621      */
2622     @InspectableProperty
getLineHeight()2623     public int getLineHeight() {
2624         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2625     }
2626 
2627     /**
2628      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2629      * This value can be null if the text or width has recently changed.
2630      * @return The Layout that is currently being used to display the text.
2631      */
getLayout()2632     public final Layout getLayout() {
2633         return mLayout;
2634     }
2635 
2636     /**
2637      * @return the {@link android.text.Layout} that is currently being used to
2638      * display the hint text. This can be null.
2639      */
2640     @UnsupportedAppUsage
getHintLayout()2641     final Layout getHintLayout() {
2642         return mHintLayout;
2643     }
2644 
2645     /**
2646      * Retrieve the {@link android.content.UndoManager} that is currently associated
2647      * with this TextView.  By default there is no associated UndoManager, so null
2648      * is returned.  One can be associated with the TextView through
2649      * {@link #setUndoManager(android.content.UndoManager, String)}
2650      *
2651      * @hide
2652      */
getUndoManager()2653     public final UndoManager getUndoManager() {
2654         // TODO: Consider supporting a global undo manager.
2655         throw new UnsupportedOperationException("not implemented");
2656     }
2657 
2658 
2659     /**
2660      * @hide
2661      */
2662     @VisibleForTesting
getEditorForTesting()2663     public final Editor getEditorForTesting() {
2664         return mEditor;
2665     }
2666 
2667     /**
2668      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2669      * done, all edit operations on the TextView will result in appropriate
2670      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2671      * stack.
2672      *
2673      * @param undoManager The {@link android.content.UndoManager} to associate with
2674      * this TextView, or null to clear any existing association.
2675      * @param tag String tag identifying this particular TextView owner in the
2676      * UndoManager.  This is used to keep the correct association with the
2677      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2678      *
2679      * @hide
2680      */
setUndoManager(UndoManager undoManager, String tag)2681     public final void setUndoManager(UndoManager undoManager, String tag) {
2682         // TODO: Consider supporting a global undo manager. An implementation will need to:
2683         // * createEditorIfNeeded()
2684         // * Promote to BufferType.EDITABLE if needed.
2685         // * Update the UndoManager and UndoOwner.
2686         // Likewise it will need to be able to restore the default UndoManager.
2687         throw new UnsupportedOperationException("not implemented");
2688     }
2689 
2690     /**
2691      * Gets the current {@link KeyListener} for the TextView.
2692      * This will frequently be null for non-EditText TextViews.
2693      * @return the current key listener for this TextView.
2694      *
2695      * @attr ref android.R.styleable#TextView_numeric
2696      * @attr ref android.R.styleable#TextView_digits
2697      * @attr ref android.R.styleable#TextView_phoneNumber
2698      * @attr ref android.R.styleable#TextView_inputMethod
2699      * @attr ref android.R.styleable#TextView_capitalize
2700      * @attr ref android.R.styleable#TextView_autoText
2701      */
getKeyListener()2702     public final KeyListener getKeyListener() {
2703         return mEditor == null ? null : mEditor.mKeyListener;
2704     }
2705 
2706     /**
2707      * Sets the key listener to be used with this TextView.  This can be null
2708      * to disallow user input.  Note that this method has significant and
2709      * subtle interactions with soft keyboards and other input method:
2710      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2711      * for important details.  Calling this method will replace the current
2712      * content type of the text view with the content type returned by the
2713      * key listener.
2714      * <p>
2715      * Be warned that if you want a TextView with a key listener or movement
2716      * method not to be focusable, or if you want a TextView without a
2717      * key listener or movement method to be focusable, you must call
2718      * {@link #setFocusable} again after calling this to get the focusability
2719      * back the way you want it.
2720      *
2721      * @attr ref android.R.styleable#TextView_numeric
2722      * @attr ref android.R.styleable#TextView_digits
2723      * @attr ref android.R.styleable#TextView_phoneNumber
2724      * @attr ref android.R.styleable#TextView_inputMethod
2725      * @attr ref android.R.styleable#TextView_capitalize
2726      * @attr ref android.R.styleable#TextView_autoText
2727      */
setKeyListener(KeyListener input)2728     public void setKeyListener(KeyListener input) {
2729         mListenerChanged = true;
2730         setKeyListenerOnly(input);
2731         fixFocusableAndClickableSettings();
2732 
2733         if (input != null) {
2734             createEditorIfNeeded();
2735             setInputTypeFromEditor();
2736         } else {
2737             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2738         }
2739 
2740         InputMethodManager imm = getInputMethodManager();
2741         if (imm != null) imm.restartInput(this);
2742     }
2743 
setInputTypeFromEditor()2744     private void setInputTypeFromEditor() {
2745         try {
2746             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2747         } catch (IncompatibleClassChangeError e) {
2748             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2749         }
2750         // Change inputType, without affecting transformation.
2751         // No need to applySingleLine since mSingleLine is unchanged.
2752         setInputTypeSingleLine(mSingleLine);
2753     }
2754 
setKeyListenerOnly(KeyListener input)2755     private void setKeyListenerOnly(KeyListener input) {
2756         if (mEditor == null && input == null) return; // null is the default value
2757 
2758         createEditorIfNeeded();
2759         if (mEditor.mKeyListener != input) {
2760             mEditor.mKeyListener = input;
2761             if (input != null && !(mText instanceof Editable)) {
2762                 setText(mText);
2763             }
2764 
2765             setFilters((Editable) mText, mFilters);
2766         }
2767     }
2768 
2769     /**
2770      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2771      * which provides positioning, scrolling, and text selection functionality.
2772      * This will frequently be null for non-EditText TextViews.
2773      * @return the movement method being used for this TextView.
2774      * @see android.text.method.MovementMethod
2775      */
getMovementMethod()2776     public final MovementMethod getMovementMethod() {
2777         return mMovement;
2778     }
2779 
2780     /**
2781      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2782      * for this TextView. This can be null to disallow using the arrow keys to move the
2783      * cursor or scroll the view.
2784      * <p>
2785      * Be warned that if you want a TextView with a key listener or movement
2786      * method not to be focusable, or if you want a TextView without a
2787      * key listener or movement method to be focusable, you must call
2788      * {@link #setFocusable} again after calling this to get the focusability
2789      * back the way you want it.
2790      */
setMovementMethod(MovementMethod movement)2791     public final void setMovementMethod(MovementMethod movement) {
2792         if (mMovement != movement) {
2793             mMovement = movement;
2794 
2795             if (movement != null && mSpannable == null) {
2796                 setText(mText);
2797             }
2798 
2799             fixFocusableAndClickableSettings();
2800 
2801             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2802             // mMovement
2803             if (mEditor != null) mEditor.prepareCursorControllers();
2804         }
2805     }
2806 
fixFocusableAndClickableSettings()2807     private void fixFocusableAndClickableSettings() {
2808         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2809             setFocusable(FOCUSABLE);
2810             setClickable(true);
2811             setLongClickable(true);
2812         } else {
2813             setFocusable(FOCUSABLE_AUTO);
2814             setClickable(false);
2815             setLongClickable(false);
2816         }
2817     }
2818 
2819     /**
2820      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2821      * This is frequently null, except for single-line and password fields.
2822      * @return the current transformation method for this TextView.
2823      *
2824      * @attr ref android.R.styleable#TextView_password
2825      * @attr ref android.R.styleable#TextView_singleLine
2826      */
getTransformationMethod()2827     public final TransformationMethod getTransformationMethod() {
2828         return mTransformation;
2829     }
2830 
2831     /**
2832      * Sets the transformation that is applied to the text that this
2833      * TextView is displaying.
2834      *
2835      * @attr ref android.R.styleable#TextView_password
2836      * @attr ref android.R.styleable#TextView_singleLine
2837      */
setTransformationMethod(TransformationMethod method)2838     public final void setTransformationMethod(TransformationMethod method) {
2839         if (mEditor != null) {
2840             mEditor.setTransformationMethod(method);
2841         } else {
2842             setTransformationMethodInternal(method, /* updateText */ true);
2843         }
2844     }
2845 
2846     /**
2847      * Set the transformation that is applied to the text that this TextView is displaying,
2848      * optionally call the setText.
2849      * @param method the new transformation method to be set.
2850      * @param updateText whether the call {@link #setText} which will update the TextView to display
2851      *                   the new content. This method is helpful when updating
2852      *                   {@link TransformationMethod} inside {@link #setText}. It should only be
2853      *                   false if text will be updated immediately after this call, otherwise the
2854      *                   TextView will enter an inconsistent state.
2855      */
setTransformationMethodInternal(@ullable TransformationMethod method, boolean updateText)2856     void setTransformationMethodInternal(@Nullable TransformationMethod method,
2857             boolean updateText) {
2858         if (method == mTransformation) {
2859             // Avoid the setText() below if the transformation is
2860             // the same.
2861             return;
2862         }
2863         if (mTransformation != null) {
2864             if (mSpannable != null) {
2865                 mSpannable.removeSpan(mTransformation);
2866             }
2867         }
2868 
2869         mTransformation = method;
2870 
2871         if (method instanceof TransformationMethod2) {
2872             TransformationMethod2 method2 = (TransformationMethod2) method;
2873             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2874             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2875         } else {
2876             mAllowTransformationLengthChange = false;
2877         }
2878 
2879         if (updateText) {
2880             if (Flags.insertModeNotUpdateSelection()) {
2881                 // Update the transformation text.
2882                 if (mTransformation == null) {
2883                     mTransformed = mText;
2884                 } else {
2885                     mTransformed = mTransformation.getTransformation(mText, this);
2886                 }
2887                 if (mTransformed == null) {
2888                     // Should not happen if the transformation method follows the non-null
2889                     // postcondition.
2890                     mTransformed = "";
2891                 }
2892                 final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
2893 
2894                 // If the mText is a Spannable and the new TransformationMethod needs to listen to
2895                 // its updates, apply the watcher on it.
2896                 if (mTransformation != null && mText instanceof Spannable
2897                         && (!mAllowTransformationLengthChange || isOffsetMapping)) {
2898                     Spannable sp = (Spannable) mText;
2899                     final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
2900                     sp.setSpan(mTransformation, 0, mText.length(),
2901                             Spanned.SPAN_INCLUSIVE_INCLUSIVE
2902                                     | (priority << Spanned.SPAN_PRIORITY_SHIFT));
2903                 }
2904                 if (mLayout != null) {
2905                     nullLayouts();
2906                     requestLayout();
2907                     invalidate();
2908                 }
2909             } else {
2910                 setText(mText);
2911             }
2912         }
2913 
2914         if (hasPasswordTransformationMethod()) {
2915             notifyViewAccessibilityStateChangedIfNeeded(
2916                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2917         }
2918 
2919         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2920         // getTextDirectionHeuristic, needs reset
2921         mTextDir = getTextDirectionHeuristic();
2922     }
2923 
2924     /**
2925      * Returns the top padding of the view, plus space for the top
2926      * Drawable if any.
2927      */
getCompoundPaddingTop()2928     public int getCompoundPaddingTop() {
2929         final Drawables dr = mDrawables;
2930         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2931             return mPaddingTop;
2932         } else {
2933             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2934         }
2935     }
2936 
2937     /**
2938      * Returns the bottom padding of the view, plus space for the bottom
2939      * Drawable if any.
2940      */
getCompoundPaddingBottom()2941     public int getCompoundPaddingBottom() {
2942         final Drawables dr = mDrawables;
2943         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2944             return mPaddingBottom;
2945         } else {
2946             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2947         }
2948     }
2949 
2950     /**
2951      * Returns the left padding of the view, plus space for the left
2952      * Drawable if any.
2953      */
getCompoundPaddingLeft()2954     public int getCompoundPaddingLeft() {
2955         final Drawables dr = mDrawables;
2956         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2957             return mPaddingLeft;
2958         } else {
2959             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2960         }
2961     }
2962 
2963     /**
2964      * Returns the right padding of the view, plus space for the right
2965      * Drawable if any.
2966      */
getCompoundPaddingRight()2967     public int getCompoundPaddingRight() {
2968         final Drawables dr = mDrawables;
2969         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2970             return mPaddingRight;
2971         } else {
2972             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2973         }
2974     }
2975 
2976     /**
2977      * Returns the start padding of the view, plus space for the start
2978      * Drawable if any.
2979      */
getCompoundPaddingStart()2980     public int getCompoundPaddingStart() {
2981         resolveDrawables();
2982         switch(getLayoutDirection()) {
2983             default:
2984             case LAYOUT_DIRECTION_LTR:
2985                 return getCompoundPaddingLeft();
2986             case LAYOUT_DIRECTION_RTL:
2987                 return getCompoundPaddingRight();
2988         }
2989     }
2990 
2991     /**
2992      * Returns the end padding of the view, plus space for the end
2993      * Drawable if any.
2994      */
getCompoundPaddingEnd()2995     public int getCompoundPaddingEnd() {
2996         resolveDrawables();
2997         switch(getLayoutDirection()) {
2998             default:
2999             case LAYOUT_DIRECTION_LTR:
3000                 return getCompoundPaddingRight();
3001             case LAYOUT_DIRECTION_RTL:
3002                 return getCompoundPaddingLeft();
3003         }
3004     }
3005 
3006     /**
3007      * Returns the extended top padding of the view, including both the
3008      * top Drawable if any and any extra space to keep more than maxLines
3009      * of text from showing.  It is only valid to call this after measuring.
3010      */
getExtendedPaddingTop()3011     public int getExtendedPaddingTop() {
3012         if (mMaxMode != LINES) {
3013             return getCompoundPaddingTop();
3014         }
3015 
3016         if (mLayout == null) {
3017             assumeLayout();
3018         }
3019 
3020         if (mLayout.getLineCount() <= mMaximum) {
3021             return getCompoundPaddingTop();
3022         }
3023 
3024         int top = getCompoundPaddingTop();
3025         int bottom = getCompoundPaddingBottom();
3026         int viewht = getHeight() - top - bottom;
3027         int layoutht = mLayout.getLineTop(mMaximum);
3028 
3029         if (layoutht >= viewht) {
3030             return top;
3031         }
3032 
3033         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3034         if (gravity == Gravity.TOP) {
3035             return top;
3036         } else if (gravity == Gravity.BOTTOM) {
3037             return top + viewht - layoutht;
3038         } else { // (gravity == Gravity.CENTER_VERTICAL)
3039             return top + (viewht - layoutht) / 2;
3040         }
3041     }
3042 
3043     /**
3044      * Returns the extended bottom padding of the view, including both the
3045      * bottom Drawable if any and any extra space to keep more than maxLines
3046      * of text from showing.  It is only valid to call this after measuring.
3047      */
getExtendedPaddingBottom()3048     public int getExtendedPaddingBottom() {
3049         if (mMaxMode != LINES) {
3050             return getCompoundPaddingBottom();
3051         }
3052 
3053         if (mLayout == null) {
3054             assumeLayout();
3055         }
3056 
3057         if (mLayout.getLineCount() <= mMaximum) {
3058             return getCompoundPaddingBottom();
3059         }
3060 
3061         int top = getCompoundPaddingTop();
3062         int bottom = getCompoundPaddingBottom();
3063         int viewht = getHeight() - top - bottom;
3064         int layoutht = mLayout.getLineTop(mMaximum);
3065 
3066         if (layoutht >= viewht) {
3067             return bottom;
3068         }
3069 
3070         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3071         if (gravity == Gravity.TOP) {
3072             return bottom + viewht - layoutht;
3073         } else if (gravity == Gravity.BOTTOM) {
3074             return bottom;
3075         } else { // (gravity == Gravity.CENTER_VERTICAL)
3076             return bottom + (viewht - layoutht) / 2;
3077         }
3078     }
3079 
3080     /**
3081      * Returns the total left padding of the view, including the left
3082      * Drawable if any.
3083      */
getTotalPaddingLeft()3084     public int getTotalPaddingLeft() {
3085         return getCompoundPaddingLeft();
3086     }
3087 
3088     /**
3089      * Returns the total right padding of the view, including the right
3090      * Drawable if any.
3091      */
getTotalPaddingRight()3092     public int getTotalPaddingRight() {
3093         return getCompoundPaddingRight();
3094     }
3095 
3096     /**
3097      * Returns the total start padding of the view, including the start
3098      * Drawable if any.
3099      */
getTotalPaddingStart()3100     public int getTotalPaddingStart() {
3101         return getCompoundPaddingStart();
3102     }
3103 
3104     /**
3105      * Returns the total end padding of the view, including the end
3106      * Drawable if any.
3107      */
getTotalPaddingEnd()3108     public int getTotalPaddingEnd() {
3109         return getCompoundPaddingEnd();
3110     }
3111 
3112     /**
3113      * Returns the total top padding of the view, including the top
3114      * Drawable if any, the extra space to keep more than maxLines
3115      * from showing, and the vertical offset for gravity, if any.
3116      */
getTotalPaddingTop()3117     public int getTotalPaddingTop() {
3118         return getExtendedPaddingTop() + getVerticalOffset(true);
3119     }
3120 
3121     /**
3122      * Returns the total bottom padding of the view, including the bottom
3123      * Drawable if any, the extra space to keep more than maxLines
3124      * from showing, and the vertical offset for gravity, if any.
3125      */
getTotalPaddingBottom()3126     public int getTotalPaddingBottom() {
3127         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
3128     }
3129 
3130     /**
3131      * Sets the Drawables (if any) to appear to the left of, above, to the
3132      * right of, and below the text. Use {@code null} if you do not want a
3133      * Drawable there. The Drawables must already have had
3134      * {@link Drawable#setBounds} called.
3135      * <p>
3136      * Calling this method will overwrite any Drawables previously set using
3137      * {@link #setCompoundDrawablesRelative} or related methods.
3138      *
3139      * @attr ref android.R.styleable#TextView_drawableLeft
3140      * @attr ref android.R.styleable#TextView_drawableTop
3141      * @attr ref android.R.styleable#TextView_drawableRight
3142      * @attr ref android.R.styleable#TextView_drawableBottom
3143      */
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3144     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
3145             @Nullable Drawable right, @Nullable Drawable bottom) {
3146         Drawables dr = mDrawables;
3147 
3148         // We're switching to absolute, discard relative.
3149         if (dr != null) {
3150             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3151             dr.mDrawableStart = null;
3152             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
3153             dr.mDrawableEnd = null;
3154             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3155             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3156         }
3157 
3158         final boolean drawables = left != null || top != null || right != null || bottom != null;
3159         if (!drawables) {
3160             // Clearing drawables...  can we free the data structure?
3161             if (dr != null) {
3162                 if (!dr.hasMetadata()) {
3163                     mDrawables = null;
3164                 } else {
3165                     // We need to retain the last set padding, so just clear
3166                     // out all of the fields in the existing structure.
3167                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
3168                         if (dr.mShowing[i] != null) {
3169                             dr.mShowing[i].setCallback(null);
3170                         }
3171                         dr.mShowing[i] = null;
3172                     }
3173                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3174                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3175                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3176                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3177                 }
3178             }
3179         } else {
3180             if (dr == null) {
3181                 mDrawables = dr = new Drawables(getContext());
3182             }
3183 
3184             mDrawables.mOverride = false;
3185 
3186             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
3187                 dr.mShowing[Drawables.LEFT].setCallback(null);
3188             }
3189             dr.mShowing[Drawables.LEFT] = left;
3190 
3191             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3192                 dr.mShowing[Drawables.TOP].setCallback(null);
3193             }
3194             dr.mShowing[Drawables.TOP] = top;
3195 
3196             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
3197                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3198             }
3199             dr.mShowing[Drawables.RIGHT] = right;
3200 
3201             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3202                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3203             }
3204             dr.mShowing[Drawables.BOTTOM] = bottom;
3205 
3206             final Rect compoundRect = dr.mCompoundRect;
3207             int[] state;
3208 
3209             state = getDrawableState();
3210 
3211             if (left != null) {
3212                 left.setState(state);
3213                 left.copyBounds(compoundRect);
3214                 left.setCallback(this);
3215                 dr.mDrawableSizeLeft = compoundRect.width();
3216                 dr.mDrawableHeightLeft = compoundRect.height();
3217             } else {
3218                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3219             }
3220 
3221             if (right != null) {
3222                 right.setState(state);
3223                 right.copyBounds(compoundRect);
3224                 right.setCallback(this);
3225                 dr.mDrawableSizeRight = compoundRect.width();
3226                 dr.mDrawableHeightRight = compoundRect.height();
3227             } else {
3228                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3229             }
3230 
3231             if (top != null) {
3232                 top.setState(state);
3233                 top.copyBounds(compoundRect);
3234                 top.setCallback(this);
3235                 dr.mDrawableSizeTop = compoundRect.height();
3236                 dr.mDrawableWidthTop = compoundRect.width();
3237             } else {
3238                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3239             }
3240 
3241             if (bottom != null) {
3242                 bottom.setState(state);
3243                 bottom.copyBounds(compoundRect);
3244                 bottom.setCallback(this);
3245                 dr.mDrawableSizeBottom = compoundRect.height();
3246                 dr.mDrawableWidthBottom = compoundRect.width();
3247             } else {
3248                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3249             }
3250         }
3251 
3252         // Save initial left/right drawables
3253         if (dr != null) {
3254             dr.mDrawableLeftInitial = left;
3255             dr.mDrawableRightInitial = right;
3256         }
3257 
3258         resetResolvedDrawables();
3259         resolveDrawables();
3260         applyCompoundDrawableTint();
3261         invalidate();
3262         requestLayout();
3263     }
3264 
3265     /**
3266      * Sets the Drawables (if any) to appear to the left of, above, to the
3267      * right of, and below the text. Use 0 if you do not want a Drawable there.
3268      * The Drawables' bounds will be set to their intrinsic bounds.
3269      * <p>
3270      * Calling this method will overwrite any Drawables previously set using
3271      * {@link #setCompoundDrawablesRelative} or related methods.
3272      *
3273      * @param left Resource identifier of the left Drawable.
3274      * @param top Resource identifier of the top Drawable.
3275      * @param right Resource identifier of the right Drawable.
3276      * @param bottom Resource identifier of the bottom Drawable.
3277      *
3278      * @attr ref android.R.styleable#TextView_drawableLeft
3279      * @attr ref android.R.styleable#TextView_drawableTop
3280      * @attr ref android.R.styleable#TextView_drawableRight
3281      * @attr ref android.R.styleable#TextView_drawableBottom
3282      */
3283     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)3284     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
3285             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
3286         final Context context = getContext();
3287         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
3288                 top != 0 ? context.getDrawable(top) : null,
3289                 right != 0 ? context.getDrawable(right) : null,
3290                 bottom != 0 ? context.getDrawable(bottom) : null);
3291     }
3292 
3293     /**
3294      * Sets the Drawables (if any) to appear to the left of, above, to the
3295      * right of, and below the text. Use {@code null} if you do not want a
3296      * Drawable there. The Drawables' bounds will be set to their intrinsic
3297      * bounds.
3298      * <p>
3299      * Calling this method will overwrite any Drawables previously set using
3300      * {@link #setCompoundDrawablesRelative} or related methods.
3301      *
3302      * @attr ref android.R.styleable#TextView_drawableLeft
3303      * @attr ref android.R.styleable#TextView_drawableTop
3304      * @attr ref android.R.styleable#TextView_drawableRight
3305      * @attr ref android.R.styleable#TextView_drawableBottom
3306      */
3307     @android.view.RemotableViewMethod
setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3308     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
3309             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
3310 
3311         if (left != null) {
3312             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
3313         }
3314         if (right != null) {
3315             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
3316         }
3317         if (top != null) {
3318             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3319         }
3320         if (bottom != null) {
3321             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3322         }
3323         setCompoundDrawables(left, top, right, bottom);
3324     }
3325 
3326     /**
3327      * Sets the Drawables (if any) to appear to the start of, above, to the end
3328      * of, and below the text. Use {@code null} if you do not want a Drawable
3329      * there. The Drawables must already have had {@link Drawable#setBounds}
3330      * called.
3331      * <p>
3332      * Calling this method will overwrite any Drawables previously set using
3333      * {@link #setCompoundDrawables} or related methods.
3334      *
3335      * @attr ref android.R.styleable#TextView_drawableStart
3336      * @attr ref android.R.styleable#TextView_drawableTop
3337      * @attr ref android.R.styleable#TextView_drawableEnd
3338      * @attr ref android.R.styleable#TextView_drawableBottom
3339      */
3340     @android.view.RemotableViewMethod
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3341     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
3342             @Nullable Drawable end, @Nullable Drawable bottom) {
3343         Drawables dr = mDrawables;
3344 
3345         // We're switching to relative, discard absolute.
3346         if (dr != null) {
3347             if (dr.mShowing[Drawables.LEFT] != null) {
3348                 dr.mShowing[Drawables.LEFT].setCallback(null);
3349             }
3350             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3351             if (dr.mShowing[Drawables.RIGHT] != null) {
3352                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3353             }
3354             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3355             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3356             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3357         }
3358 
3359         final boolean drawables = start != null || top != null
3360                 || end != null || bottom != null;
3361 
3362         if (!drawables) {
3363             // Clearing drawables...  can we free the data structure?
3364             if (dr != null) {
3365                 if (!dr.hasMetadata()) {
3366                     mDrawables = null;
3367                 } else {
3368                     // We need to retain the last set padding, so just clear
3369                     // out all of the fields in the existing structure.
3370                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3371                     dr.mDrawableStart = null;
3372                     if (dr.mShowing[Drawables.TOP] != null) {
3373                         dr.mShowing[Drawables.TOP].setCallback(null);
3374                     }
3375                     dr.mShowing[Drawables.TOP] = null;
3376                     if (dr.mDrawableEnd != null) {
3377                         dr.mDrawableEnd.setCallback(null);
3378                     }
3379                     dr.mDrawableEnd = null;
3380                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3381                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3382                     }
3383                     dr.mShowing[Drawables.BOTTOM] = null;
3384                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3385                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3386                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3387                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3388                 }
3389             }
3390         } else {
3391             if (dr == null) {
3392                 mDrawables = dr = new Drawables(getContext());
3393             }
3394 
3395             mDrawables.mOverride = true;
3396 
3397             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3398                 dr.mDrawableStart.setCallback(null);
3399             }
3400             dr.mDrawableStart = start;
3401 
3402             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3403                 dr.mShowing[Drawables.TOP].setCallback(null);
3404             }
3405             dr.mShowing[Drawables.TOP] = top;
3406 
3407             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3408                 dr.mDrawableEnd.setCallback(null);
3409             }
3410             dr.mDrawableEnd = end;
3411 
3412             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3413                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3414             }
3415             dr.mShowing[Drawables.BOTTOM] = bottom;
3416 
3417             final Rect compoundRect = dr.mCompoundRect;
3418             int[] state;
3419 
3420             state = getDrawableState();
3421 
3422             if (start != null) {
3423                 start.setState(state);
3424                 start.copyBounds(compoundRect);
3425                 start.setCallback(this);
3426                 dr.mDrawableSizeStart = compoundRect.width();
3427                 dr.mDrawableHeightStart = compoundRect.height();
3428             } else {
3429                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3430             }
3431 
3432             if (end != null) {
3433                 end.setState(state);
3434                 end.copyBounds(compoundRect);
3435                 end.setCallback(this);
3436                 dr.mDrawableSizeEnd = compoundRect.width();
3437                 dr.mDrawableHeightEnd = compoundRect.height();
3438             } else {
3439                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3440             }
3441 
3442             if (top != null) {
3443                 top.setState(state);
3444                 top.copyBounds(compoundRect);
3445                 top.setCallback(this);
3446                 dr.mDrawableSizeTop = compoundRect.height();
3447                 dr.mDrawableWidthTop = compoundRect.width();
3448             } else {
3449                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3450             }
3451 
3452             if (bottom != null) {
3453                 bottom.setState(state);
3454                 bottom.copyBounds(compoundRect);
3455                 bottom.setCallback(this);
3456                 dr.mDrawableSizeBottom = compoundRect.height();
3457                 dr.mDrawableWidthBottom = compoundRect.width();
3458             } else {
3459                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3460             }
3461         }
3462 
3463         resetResolvedDrawables();
3464         resolveDrawables();
3465         invalidate();
3466         requestLayout();
3467     }
3468 
3469     /**
3470      * Sets the Drawables (if any) to appear to the start of, above, to the end
3471      * of, and below the text. Use 0 if you do not want a Drawable there. The
3472      * Drawables' bounds will be set to their intrinsic bounds.
3473      * <p>
3474      * Calling this method will overwrite any Drawables previously set using
3475      * {@link #setCompoundDrawables} or related methods.
3476      *
3477      * @param start Resource identifier of the start Drawable.
3478      * @param top Resource identifier of the top Drawable.
3479      * @param end Resource identifier of the end Drawable.
3480      * @param bottom Resource identifier of the bottom Drawable.
3481      *
3482      * @attr ref android.R.styleable#TextView_drawableStart
3483      * @attr ref android.R.styleable#TextView_drawableTop
3484      * @attr ref android.R.styleable#TextView_drawableEnd
3485      * @attr ref android.R.styleable#TextView_drawableBottom
3486      */
3487     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3488     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3489             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3490         final Context context = getContext();
3491         setCompoundDrawablesRelativeWithIntrinsicBounds(
3492                 start != 0 ? context.getDrawable(start) : null,
3493                 top != 0 ? context.getDrawable(top) : null,
3494                 end != 0 ? context.getDrawable(end) : null,
3495                 bottom != 0 ? context.getDrawable(bottom) : null);
3496     }
3497 
3498     /**
3499      * Sets the Drawables (if any) to appear to the start of, above, to the end
3500      * of, and below the text. Use {@code null} if you do not want a Drawable
3501      * there. The Drawables' bounds will be set to their intrinsic bounds.
3502      * <p>
3503      * Calling this method will overwrite any Drawables previously set using
3504      * {@link #setCompoundDrawables} or related methods.
3505      *
3506      * @attr ref android.R.styleable#TextView_drawableStart
3507      * @attr ref android.R.styleable#TextView_drawableTop
3508      * @attr ref android.R.styleable#TextView_drawableEnd
3509      * @attr ref android.R.styleable#TextView_drawableBottom
3510      */
3511     @android.view.RemotableViewMethod
setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3512     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3513             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3514 
3515         if (start != null) {
3516             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3517         }
3518         if (end != null) {
3519             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3520         }
3521         if (top != null) {
3522             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3523         }
3524         if (bottom != null) {
3525             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3526         }
3527         setCompoundDrawablesRelative(start, top, end, bottom);
3528     }
3529 
3530     /**
3531      * Returns drawables for the left, top, right, and bottom borders.
3532      *
3533      * @attr ref android.R.styleable#TextView_drawableLeft
3534      * @attr ref android.R.styleable#TextView_drawableTop
3535      * @attr ref android.R.styleable#TextView_drawableRight
3536      * @attr ref android.R.styleable#TextView_drawableBottom
3537      */
3538     @NonNull
getCompoundDrawables()3539     public Drawable[] getCompoundDrawables() {
3540         final Drawables dr = mDrawables;
3541         if (dr != null) {
3542             return dr.mShowing.clone();
3543         } else {
3544             return new Drawable[] { null, null, null, null };
3545         }
3546     }
3547 
3548     /**
3549      * Returns drawables for the start, top, end, and bottom borders.
3550      *
3551      * @attr ref android.R.styleable#TextView_drawableStart
3552      * @attr ref android.R.styleable#TextView_drawableTop
3553      * @attr ref android.R.styleable#TextView_drawableEnd
3554      * @attr ref android.R.styleable#TextView_drawableBottom
3555      */
3556     @NonNull
getCompoundDrawablesRelative()3557     public Drawable[] getCompoundDrawablesRelative() {
3558         final Drawables dr = mDrawables;
3559         if (dr != null) {
3560             return new Drawable[] {
3561                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3562                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3563             };
3564         } else {
3565             return new Drawable[] { null, null, null, null };
3566         }
3567     }
3568 
3569     /**
3570      * Sets the size of the padding between the compound drawables and
3571      * the text.
3572      *
3573      * @attr ref android.R.styleable#TextView_drawablePadding
3574      */
3575     @android.view.RemotableViewMethod
setCompoundDrawablePadding(int pad)3576     public void setCompoundDrawablePadding(int pad) {
3577         Drawables dr = mDrawables;
3578         if (pad == 0) {
3579             if (dr != null) {
3580                 dr.mDrawablePadding = pad;
3581             }
3582         } else {
3583             if (dr == null) {
3584                 mDrawables = dr = new Drawables(getContext());
3585             }
3586             dr.mDrawablePadding = pad;
3587         }
3588 
3589         invalidate();
3590         requestLayout();
3591     }
3592 
3593     /**
3594      * Returns the padding between the compound drawables and the text.
3595      *
3596      * @attr ref android.R.styleable#TextView_drawablePadding
3597      */
3598     @InspectableProperty(name = "drawablePadding")
getCompoundDrawablePadding()3599     public int getCompoundDrawablePadding() {
3600         final Drawables dr = mDrawables;
3601         return dr != null ? dr.mDrawablePadding : 0;
3602     }
3603 
3604     /**
3605      * Applies a tint to the compound drawables. Does not modify the
3606      * current tint mode, which is {@link BlendMode#SRC_IN} by default.
3607      * <p>
3608      * Subsequent calls to
3609      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3610      * and related methods will automatically mutate the drawables and apply
3611      * the specified tint and tint mode using
3612      * {@link Drawable#setTintList(ColorStateList)}.
3613      *
3614      * @param tint the tint to apply, may be {@code null} to clear tint
3615      *
3616      * @attr ref android.R.styleable#TextView_drawableTint
3617      * @see #getCompoundDrawableTintList()
3618      * @see Drawable#setTintList(ColorStateList)
3619      */
setCompoundDrawableTintList(@ullable ColorStateList tint)3620     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3621         if (mDrawables == null) {
3622             mDrawables = new Drawables(getContext());
3623         }
3624         mDrawables.mTintList = tint;
3625         mDrawables.mHasTint = true;
3626 
3627         applyCompoundDrawableTint();
3628     }
3629 
3630     /**
3631      * @return the tint applied to the compound drawables
3632      * @attr ref android.R.styleable#TextView_drawableTint
3633      * @see #setCompoundDrawableTintList(ColorStateList)
3634      */
3635     @InspectableProperty(name = "drawableTint")
getCompoundDrawableTintList()3636     public ColorStateList getCompoundDrawableTintList() {
3637         return mDrawables != null ? mDrawables.mTintList : null;
3638     }
3639 
3640     /**
3641      * Specifies the blending mode used to apply the tint specified by
3642      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3643      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3644      *
3645      * @param tintMode the blending mode used to apply the tint, may be
3646      *                 {@code null} to clear tint
3647      * @attr ref android.R.styleable#TextView_drawableTintMode
3648      * @see #setCompoundDrawableTintList(ColorStateList)
3649      * @see Drawable#setTintMode(PorterDuff.Mode)
3650      */
setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3651     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3652         setCompoundDrawableTintBlendMode(tintMode != null
3653                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3654     }
3655 
3656     /**
3657      * Specifies the blending mode used to apply the tint specified by
3658      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3659      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3660      *
3661      * @param blendMode the blending mode used to apply the tint, may be
3662      *                 {@code null} to clear tint
3663      * @attr ref android.R.styleable#TextView_drawableTintMode
3664      * @see #setCompoundDrawableTintList(ColorStateList)
3665      * @see Drawable#setTintBlendMode(BlendMode)
3666      */
setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3667     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3668         if (mDrawables == null) {
3669             mDrawables = new Drawables(getContext());
3670         }
3671         mDrawables.mBlendMode = blendMode;
3672         mDrawables.mHasTintMode = true;
3673 
3674         applyCompoundDrawableTint();
3675     }
3676 
3677     /**
3678      * Returns the blending mode used to apply the tint to the compound
3679      * drawables, if specified.
3680      *
3681      * @return the blending mode used to apply the tint to the compound
3682      *         drawables
3683      * @attr ref android.R.styleable#TextView_drawableTintMode
3684      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3685      *
3686      */
3687     @InspectableProperty(name = "drawableTintMode")
getCompoundDrawableTintMode()3688     public PorterDuff.Mode getCompoundDrawableTintMode() {
3689         BlendMode mode = getCompoundDrawableTintBlendMode();
3690         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3691     }
3692 
3693     /**
3694      * Returns the blending mode used to apply the tint to the compound
3695      * drawables, if specified.
3696      *
3697      * @return the blending mode used to apply the tint to the compound
3698      *         drawables
3699      * @attr ref android.R.styleable#TextView_drawableTintMode
3700      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3701      */
3702     @InspectableProperty(name = "drawableBlendMode",
3703             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
getCompoundDrawableTintBlendMode()3704     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3705         return mDrawables != null ? mDrawables.mBlendMode : null;
3706     }
3707 
applyCompoundDrawableTint()3708     private void applyCompoundDrawableTint() {
3709         if (mDrawables == null) {
3710             return;
3711         }
3712 
3713         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3714             final ColorStateList tintList = mDrawables.mTintList;
3715             final BlendMode blendMode = mDrawables.mBlendMode;
3716             final boolean hasTint = mDrawables.mHasTint;
3717             final boolean hasTintMode = mDrawables.mHasTintMode;
3718             final int[] state = getDrawableState();
3719 
3720             for (Drawable dr : mDrawables.mShowing) {
3721                 if (dr == null) {
3722                     continue;
3723                 }
3724 
3725                 if (dr == mDrawables.mDrawableError) {
3726                     // From a developer's perspective, the error drawable isn't
3727                     // a compound drawable. Don't apply the generic compound
3728                     // drawable tint to it.
3729                     continue;
3730                 }
3731 
3732                 dr.mutate();
3733 
3734                 if (hasTint) {
3735                     dr.setTintList(tintList);
3736                 }
3737 
3738                 if (hasTintMode) {
3739                     dr.setTintBlendMode(blendMode);
3740                 }
3741 
3742                 // The drawable (or one of its children) may not have been
3743                 // stateful before applying the tint, so let's try again.
3744                 if (dr.isStateful()) {
3745                     dr.setState(state);
3746                 }
3747             }
3748         }
3749     }
3750 
3751     /**
3752      * @inheritDoc
3753      *
3754      * @see #setFirstBaselineToTopHeight(int)
3755      * @see #setLastBaselineToBottomHeight(int)
3756      */
3757     @Override
setPadding(int left, int top, int right, int bottom)3758     public void setPadding(int left, int top, int right, int bottom) {
3759         if (left != mPaddingLeft
3760                 || right != mPaddingRight
3761                 || top != mPaddingTop
3762                 ||  bottom != mPaddingBottom) {
3763             nullLayouts();
3764         }
3765 
3766         // the super call will requestLayout()
3767         super.setPadding(left, top, right, bottom);
3768         invalidate();
3769     }
3770 
3771     /**
3772      * @inheritDoc
3773      *
3774      * @see #setFirstBaselineToTopHeight(int)
3775      * @see #setLastBaselineToBottomHeight(int)
3776      */
3777     @Override
setPaddingRelative(int start, int top, int end, int bottom)3778     public void setPaddingRelative(int start, int top, int end, int bottom) {
3779         if (start != getPaddingStart()
3780                 || end != getPaddingEnd()
3781                 || top != mPaddingTop
3782                 || bottom != mPaddingBottom) {
3783             nullLayouts();
3784         }
3785 
3786         // the super call will requestLayout()
3787         super.setPaddingRelative(start, top, end, bottom);
3788         invalidate();
3789     }
3790 
3791     /**
3792      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3793      * the distance between the top of the TextView and first line's baseline.
3794      * <p>
3795      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3796      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3797      *
3798      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3799      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3800      * Moreover since this function sets the top padding, if the height of the TextView is less than
3801      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3802      * down and bottom will be clipped.
3803      *
3804      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3805      *      in pixels
3806      *
3807      * @see #getFirstBaselineToTopHeight()
3808      * @see #setLastBaselineToBottomHeight(int)
3809      * @see #setPadding(int, int, int, int)
3810      * @see #setPaddingRelative(int, int, int, int)
3811      *
3812      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3813      */
setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3814     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3815         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3816 
3817         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3818         final int fontMetricsTop;
3819         if (getIncludeFontPadding()) {
3820             fontMetricsTop = fontMetrics.top;
3821         } else {
3822             fontMetricsTop = fontMetrics.ascent;
3823         }
3824 
3825         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3826         // in settings). At the moment, we don't.
3827 
3828         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3829             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3830             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3831         }
3832     }
3833 
3834     /**
3835      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3836      * the distance between the bottom of the TextView and the last line's baseline.
3837      * <p>
3838      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3839      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3840      *
3841      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3842      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3843      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3844      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3845      * clipped.
3846      *
3847      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3848      *      in pixels
3849      *
3850      * @see #getLastBaselineToBottomHeight()
3851      * @see #setFirstBaselineToTopHeight(int)
3852      * @see #setPadding(int, int, int, int)
3853      * @see #setPaddingRelative(int, int, int, int)
3854      *
3855      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3856      */
setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3857     public void setLastBaselineToBottomHeight(
3858             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3859         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3860 
3861         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3862         final int fontMetricsBottom;
3863         if (getIncludeFontPadding()) {
3864             fontMetricsBottom = fontMetrics.bottom;
3865         } else {
3866             fontMetricsBottom = fontMetrics.descent;
3867         }
3868 
3869         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3870         // in settings). At the moment, we don't.
3871 
3872         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3873             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3874             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3875         }
3876     }
3877 
3878     /**
3879      * Returns the distance between the first text baseline and the top of this TextView.
3880      *
3881      * @see #setFirstBaselineToTopHeight(int)
3882      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3883      */
3884     @InspectableProperty
getFirstBaselineToTopHeight()3885     public int getFirstBaselineToTopHeight() {
3886         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3887     }
3888 
3889     /**
3890      * Returns the distance between the last text baseline and the bottom of this TextView.
3891      *
3892      * @see #setLastBaselineToBottomHeight(int)
3893      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3894      */
3895     @InspectableProperty
getLastBaselineToBottomHeight()3896     public int getLastBaselineToBottomHeight() {
3897         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3898     }
3899 
3900     /**
3901      * Gets the autolink mask of the text.
3902      *
3903      * See {@link Linkify#ALL} and peers for possible values.
3904      *
3905      * @attr ref android.R.styleable#TextView_autoLink
3906      */
3907     @InspectableProperty(name = "autoLink", flagMapping = {
3908             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3909             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3910             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3911             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3912     })
getAutoLinkMask()3913     public final int getAutoLinkMask() {
3914         return mAutoLinkMask;
3915     }
3916 
3917     /**
3918      * Sets the Drawable corresponding to the selection handle used for
3919      * positioning the cursor within text. The Drawable defaults to the value
3920      * of the textSelectHandle attribute.
3921      * Note that any change applied to the handle Drawable will not be visible
3922      * until the handle is hidden and then drawn again.
3923      *
3924      * @see #setTextSelectHandle(int)
3925      * @attr ref android.R.styleable#TextView_textSelectHandle
3926      */
3927     @android.view.RemotableViewMethod
setTextSelectHandle(@onNull Drawable textSelectHandle)3928     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3929         Preconditions.checkNotNull(textSelectHandle,
3930                 "The text select handle should not be null.");
3931         mTextSelectHandle = textSelectHandle;
3932         mTextSelectHandleRes = 0;
3933         if (mEditor != null) {
3934             mEditor.loadHandleDrawables(true /* overwrite */);
3935         }
3936     }
3937 
3938     /**
3939      * Sets the Drawable corresponding to the selection handle used for
3940      * positioning the cursor within text. The Drawable defaults to the value
3941      * of the textSelectHandle attribute.
3942      * Note that any change applied to the handle Drawable will not be visible
3943      * until the handle is hidden and then drawn again.
3944      *
3945      * @see #setTextSelectHandle(Drawable)
3946      * @attr ref android.R.styleable#TextView_textSelectHandle
3947      */
3948     @android.view.RemotableViewMethod
setTextSelectHandle(@rawableRes int textSelectHandle)3949     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3950         Preconditions.checkArgument(textSelectHandle != 0,
3951                 "The text select handle should be a valid drawable resource id.");
3952         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3953     }
3954 
3955     /**
3956      * Returns the Drawable corresponding to the selection handle used
3957      * for positioning the cursor within text.
3958      * Note that any change applied to the handle Drawable will not be visible
3959      * until the handle is hidden and then drawn again.
3960      *
3961      * @return the text select handle drawable
3962      *
3963      * @see #setTextSelectHandle(Drawable)
3964      * @see #setTextSelectHandle(int)
3965      * @attr ref android.R.styleable#TextView_textSelectHandle
3966      */
getTextSelectHandle()3967     @Nullable public Drawable getTextSelectHandle() {
3968         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3969             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3970         }
3971         return mTextSelectHandle;
3972     }
3973 
3974     /**
3975      * Sets the Drawable corresponding to the left handle used
3976      * for selecting text. The Drawable defaults to the value of the
3977      * textSelectHandleLeft attribute.
3978      * Note that any change applied to the handle Drawable will not be visible
3979      * until the handle is hidden and then drawn again.
3980      *
3981      * @see #setTextSelectHandleLeft(int)
3982      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3983      */
3984     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3985     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3986         Preconditions.checkNotNull(textSelectHandleLeft,
3987                 "The left text select handle should not be null.");
3988         mTextSelectHandleLeft = textSelectHandleLeft;
3989         mTextSelectHandleLeftRes = 0;
3990         if (mEditor != null) {
3991             mEditor.loadHandleDrawables(true /* overwrite */);
3992         }
3993     }
3994 
3995     /**
3996      * Sets the Drawable corresponding to the left handle used
3997      * for selecting text. The Drawable defaults to the value of the
3998      * textSelectHandleLeft attribute.
3999      * Note that any change applied to the handle Drawable will not be visible
4000      * until the handle is hidden and then drawn again.
4001      *
4002      * @see #setTextSelectHandleLeft(Drawable)
4003      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
4004      */
4005     @android.view.RemotableViewMethod
setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)4006     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
4007         Preconditions.checkArgument(textSelectHandleLeft != 0,
4008                 "The text select left handle should be a valid drawable resource id.");
4009         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
4010     }
4011 
4012     /**
4013      * Returns the Drawable corresponding to the left handle used
4014      * for selecting text.
4015      * Note that any change applied to the handle Drawable will not be visible
4016      * until the handle is hidden and then drawn again.
4017      *
4018      * @return the left text selection handle drawable
4019      *
4020      * @see #setTextSelectHandleLeft(Drawable)
4021      * @see #setTextSelectHandleLeft(int)
4022      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
4023      */
getTextSelectHandleLeft()4024     @Nullable public Drawable getTextSelectHandleLeft() {
4025         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
4026             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
4027         }
4028         return mTextSelectHandleLeft;
4029     }
4030 
4031     /**
4032      * Sets the Drawable corresponding to the right handle used
4033      * for selecting text. The Drawable defaults to the value of the
4034      * textSelectHandleRight attribute.
4035      * Note that any change applied to the handle Drawable will not be visible
4036      * until the handle is hidden and then drawn again.
4037      *
4038      * @see #setTextSelectHandleRight(int)
4039      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4040      */
4041     @android.view.RemotableViewMethod
setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)4042     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
4043         Preconditions.checkNotNull(textSelectHandleRight,
4044                 "The right text select handle should not be null.");
4045         mTextSelectHandleRight = textSelectHandleRight;
4046         mTextSelectHandleRightRes = 0;
4047         if (mEditor != null) {
4048             mEditor.loadHandleDrawables(true /* overwrite */);
4049         }
4050     }
4051 
4052     /**
4053      * Sets the Drawable corresponding to the right handle used
4054      * for selecting text. The Drawable defaults to the value of the
4055      * textSelectHandleRight attribute.
4056      * Note that any change applied to the handle Drawable will not be visible
4057      * until the handle is hidden and then drawn again.
4058      *
4059      * @see #setTextSelectHandleRight(Drawable)
4060      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4061      */
4062     @android.view.RemotableViewMethod
setTextSelectHandleRight(@rawableRes int textSelectHandleRight)4063     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
4064         Preconditions.checkArgument(textSelectHandleRight != 0,
4065                 "The text select right handle should be a valid drawable resource id.");
4066         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
4067     }
4068 
4069     /**
4070      * Returns the Drawable corresponding to the right handle used
4071      * for selecting text.
4072      * Note that any change applied to the handle Drawable will not be visible
4073      * until the handle is hidden and then drawn again.
4074      *
4075      * @return the right text selection handle drawable
4076      *
4077      * @see #setTextSelectHandleRight(Drawable)
4078      * @see #setTextSelectHandleRight(int)
4079      * @attr ref android.R.styleable#TextView_textSelectHandleRight
4080      */
getTextSelectHandleRight()4081     @Nullable public Drawable getTextSelectHandleRight() {
4082         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
4083             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
4084         }
4085         return mTextSelectHandleRight;
4086     }
4087 
4088     /**
4089      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
4090      * value of the textCursorDrawable attribute.
4091      * Note that any change applied to the cursor Drawable will not be visible
4092      * until the cursor is hidden and then drawn again.
4093      *
4094      * @see #setTextCursorDrawable(int)
4095      * @attr ref android.R.styleable#TextView_textCursorDrawable
4096      */
setTextCursorDrawable(@ullable Drawable textCursorDrawable)4097     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
4098         mCursorDrawable = textCursorDrawable;
4099         mCursorDrawableRes = 0;
4100         if (mEditor != null) {
4101             mEditor.loadCursorDrawable();
4102         }
4103     }
4104 
4105     /**
4106      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
4107      * value of the textCursorDrawable attribute.
4108      * Note that any change applied to the cursor Drawable will not be visible
4109      * until the cursor is hidden and then drawn again.
4110      *
4111      * @see #setTextCursorDrawable(Drawable)
4112      * @attr ref android.R.styleable#TextView_textCursorDrawable
4113      */
setTextCursorDrawable(@rawableRes int textCursorDrawable)4114     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
4115         setTextCursorDrawable(
4116                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
4117     }
4118 
4119     /**
4120      * Returns the Drawable corresponding to the text cursor.
4121      * Note that any change applied to the cursor Drawable will not be visible
4122      * until the cursor is hidden and then drawn again.
4123      *
4124      * @return the text cursor drawable
4125      *
4126      * @see #setTextCursorDrawable(Drawable)
4127      * @see #setTextCursorDrawable(int)
4128      * @attr ref android.R.styleable#TextView_textCursorDrawable
4129      */
getTextCursorDrawable()4130     @Nullable public Drawable getTextCursorDrawable() {
4131         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
4132             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
4133         }
4134         return mCursorDrawable;
4135     }
4136 
4137     /**
4138      * Sets the text appearance from the specified style resource.
4139      * <p>
4140      * Use a framework-defined {@code TextAppearance} style like
4141      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
4142      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
4143      * set of attributes that can be used in a custom style.
4144      *
4145      * @param resId the resource identifier of the style to apply
4146      * @attr ref android.R.styleable#TextView_textAppearance
4147      */
4148     @SuppressWarnings("deprecation")
setTextAppearance(@tyleRes int resId)4149     public void setTextAppearance(@StyleRes int resId) {
4150         setTextAppearance(mContext, resId);
4151     }
4152 
4153     /**
4154      * Sets the text color, size, style, hint color, and highlight color
4155      * from the specified TextAppearance resource.
4156      *
4157      * @deprecated Use {@link #setTextAppearance(int)} instead.
4158      */
4159     @Deprecated
setTextAppearance(Context context, @StyleRes int resId)4160     public void setTextAppearance(Context context, @StyleRes int resId) {
4161         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
4162         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
4163         readTextAppearance(context, ta, attributes, false /* styleArray */);
4164         ta.recycle();
4165         applyTextAppearance(attributes);
4166     }
4167 
4168     /**
4169      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
4170      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
4171      */
4172     private static class TextAppearanceAttributes {
4173         int mTextColorHighlight = 0;
4174         int mSearchResultHighlightColor = 0;
4175         int mFocusedSearchResultHighlightColor = 0;
4176         ColorStateList mTextColor = null;
4177         ColorStateList mTextColorHint = null;
4178         ColorStateList mTextColorLink = null;
4179         int mTextSize = -1;
4180         int mTextSizeUnit = -1;
4181         LocaleList mTextLocales = null;
4182         String mFontFamily = null;
4183         Typeface mFontTypeface = null;
4184         boolean mFontFamilyExplicit = false;
4185         int mTypefaceIndex = -1;
4186         int mTextStyle = 0;
4187         int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
4188         boolean mAllCaps = false;
4189         int mShadowColor = 0;
4190         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
4191         boolean mHasElegant = false;
4192         boolean mElegant = false;
4193         boolean mHasFallbackLineSpacing = false;
4194         boolean mFallbackLineSpacing = false;
4195         boolean mHasLetterSpacing = false;
4196         float mLetterSpacing = 0;
4197         String mFontFeatureSettings = null;
4198         String mFontVariationSettings = null;
4199         boolean mHasLineBreakStyle = false;
4200         boolean mHasLineBreakWordStyle = false;
4201         int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
4202         int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
4203 
4204         @Override
toString()4205         public String toString() {
4206             return "TextAppearanceAttributes {\n"
4207                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
4208                     + "    mSearchResultHighlightColor: " + mSearchResultHighlightColor + "\n"
4209                     + "    mFocusedSearchResultHighlightColor: "
4210                     + mFocusedSearchResultHighlightColor + "\n"
4211                     + "    mTextColor:" + mTextColor + "\n"
4212                     + "    mTextColorHint:" + mTextColorHint + "\n"
4213                     + "    mTextColorLink:" + mTextColorLink + "\n"
4214                     + "    mTextSize:" + mTextSize + "\n"
4215                     + "    mTextSizeUnit:" + mTextSizeUnit + "\n"
4216                     + "    mTextLocales:" + mTextLocales + "\n"
4217                     + "    mFontFamily:" + mFontFamily + "\n"
4218                     + "    mFontTypeface:" + mFontTypeface + "\n"
4219                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
4220                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
4221                     + "    mTextStyle:" + mTextStyle + "\n"
4222                     + "    mFontWeight:" + mFontWeight + "\n"
4223                     + "    mAllCaps:" + mAllCaps + "\n"
4224                     + "    mShadowColor:" + mShadowColor + "\n"
4225                     + "    mShadowDx:" + mShadowDx + "\n"
4226                     + "    mShadowDy:" + mShadowDy + "\n"
4227                     + "    mShadowRadius:" + mShadowRadius + "\n"
4228                     + "    mHasElegant:" + mHasElegant + "\n"
4229                     + "    mElegant:" + mElegant + "\n"
4230                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
4231                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
4232                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
4233                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
4234                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
4235                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
4236                     + "    mHasLineBreakStyle:" + mHasLineBreakStyle + "\n"
4237                     + "    mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n"
4238                     + "    mLineBreakStyle:" + mLineBreakStyle + "\n"
4239                     + "    mLineBreakWordStyle:" + mLineBreakWordStyle + "\n"
4240                     + "}";
4241         }
4242     }
4243 
4244     // Maps styleable attributes that exist both in TextView style and TextAppearance.
4245     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
4246     static {
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)4247         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
4248                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor)4249         sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor,
4250                 com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor);
sAppearanceValues.put( com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor)4251         sAppearanceValues.put(
4252                 com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor,
4253                 com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)4254         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
4255                 com.android.internal.R.styleable.TextAppearance_textColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)4256         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
4257                 com.android.internal.R.styleable.TextAppearance_textColorHint);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)4258         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
4259                 com.android.internal.R.styleable.TextAppearance_textColorLink);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)4260         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
4261                 com.android.internal.R.styleable.TextAppearance_textSize);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)4262         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
4263                 com.android.internal.R.styleable.TextAppearance_textLocale);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)4264         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
4265                 com.android.internal.R.styleable.TextAppearance_typeface);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)4266         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
4267                 com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)4268         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
4269                 com.android.internal.R.styleable.TextAppearance_textStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4270         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
4271                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4272         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
4273                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4274         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
4275                 com.android.internal.R.styleable.TextAppearance_shadowColor);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4276         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
4277                 com.android.internal.R.styleable.TextAppearance_shadowDx);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4278         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
4279                 com.android.internal.R.styleable.TextAppearance_shadowDy);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4280         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
4281                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4282         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
4283                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4284         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
4285                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4286         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
4287                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4288         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
4289                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4290         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
4291                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, com.android.internal.R.styleable.TextAppearance_lineBreakStyle)4292         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle,
4293                 com.android.internal.R.styleable.TextAppearance_lineBreakStyle);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle)4294         sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle,
4295                 com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle);
4296     }
4297 
4298     /**
4299      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
4300      * set. If the TypedArray contains a value that was already set in the given attributes, that
4301      * will be overridden.
4302      *
4303      * @param context The Context to be used
4304      * @param appearance The TypedArray to read properties from
4305      * @param attributes the TextAppearanceAttributes to fill in
4306      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
4307      *                   what attribute indexes will be used to read the properties.
4308      */
readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)4309     private void readTextAppearance(Context context, TypedArray appearance,
4310             TextAppearanceAttributes attributes, boolean styleArray) {
4311         final int n = appearance.getIndexCount();
4312         for (int i = 0; i < n; i++) {
4313             final int attr = appearance.getIndex(i);
4314             int index = attr;
4315             // Translate style array index ids to TextAppearance ids.
4316             if (styleArray) {
4317                 index = sAppearanceValues.get(attr, -1);
4318                 if (index == -1) {
4319                     // This value is not part of a Text Appearance and should be ignored.
4320                     continue;
4321                 }
4322             }
4323             switch (index) {
4324                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
4325                     attributes.mTextColorHighlight =
4326                             appearance.getColor(attr, attributes.mTextColorHighlight);
4327                     break;
4328                 case com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor:
4329                     attributes.mSearchResultHighlightColor =
4330                             appearance.getColor(attr, attributes.mSearchResultHighlightColor);
4331                     break;
4332                 case com.android.internal.R.styleable
4333                         .TextAppearance_focusedSearchResultHighlightColor:
4334                     attributes.mFocusedSearchResultHighlightColor =
4335                             appearance.getColor(attr,
4336                                     attributes.mFocusedSearchResultHighlightColor);
4337                     break;
4338                 case com.android.internal.R.styleable.TextAppearance_textColor:
4339                     attributes.mTextColor = appearance.getColorStateList(attr);
4340                     break;
4341                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
4342                     attributes.mTextColorHint = appearance.getColorStateList(attr);
4343                     break;
4344                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
4345                     attributes.mTextColorLink = appearance.getColorStateList(attr);
4346                     break;
4347                 case com.android.internal.R.styleable.TextAppearance_textSize:
4348                     attributes.mTextSize =
4349                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
4350                     attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit();
4351                     break;
4352                 case com.android.internal.R.styleable.TextAppearance_textLocale:
4353                     final String localeString = appearance.getString(attr);
4354                     if (localeString != null) {
4355                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
4356                         if (!localeList.isEmpty()) {
4357                             attributes.mTextLocales = localeList;
4358                         }
4359                     }
4360                     break;
4361                 case com.android.internal.R.styleable.TextAppearance_typeface:
4362                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
4363                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4364                         attributes.mFontFamily = null;
4365                     }
4366                     break;
4367                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
4368                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
4369                         try {
4370                             attributes.mFontTypeface = appearance.getFont(attr);
4371                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
4372                             // Expected if it is not a font resource.
4373                         }
4374                     }
4375                     if (attributes.mFontTypeface == null) {
4376                         attributes.mFontFamily = appearance.getString(attr);
4377                     }
4378                     attributes.mFontFamilyExplicit = true;
4379                     break;
4380                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4381                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4382                     break;
4383                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4384                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4385                     break;
4386                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4387                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4388                     break;
4389                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4390                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4391                     break;
4392                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4393                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4394                     break;
4395                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4396                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4397                     break;
4398                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4399                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4400                     break;
4401                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4402                     attributes.mHasElegant = true;
4403                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4404                     break;
4405                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4406                     attributes.mHasFallbackLineSpacing = true;
4407                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4408                             attributes.mFallbackLineSpacing);
4409                     break;
4410                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4411                     attributes.mHasLetterSpacing = true;
4412                     attributes.mLetterSpacing =
4413                             appearance.getFloat(attr, attributes.mLetterSpacing);
4414                     break;
4415                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4416                     attributes.mFontFeatureSettings = appearance.getString(attr);
4417                     break;
4418                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4419                     attributes.mFontVariationSettings = appearance.getString(attr);
4420                     break;
4421                 case com.android.internal.R.styleable.TextAppearance_lineBreakStyle:
4422                     attributes.mHasLineBreakStyle = true;
4423                     attributes.mLineBreakStyle =
4424                             appearance.getInt(attr, attributes.mLineBreakStyle);
4425                     break;
4426                 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
4427                     attributes.mHasLineBreakWordStyle = true;
4428                     attributes.mLineBreakWordStyle =
4429                             appearance.getInt(attr, attributes.mLineBreakWordStyle);
4430                     break;
4431                 default:
4432             }
4433         }
4434     }
4435 
applyTextAppearance(TextAppearanceAttributes attributes)4436     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4437         if (attributes.mTextColor != null) {
4438             setTextColor(attributes.mTextColor);
4439         }
4440 
4441         if (attributes.mTextColorHint != null) {
4442             setHintTextColor(attributes.mTextColorHint);
4443         }
4444 
4445         if (attributes.mTextColorLink != null) {
4446             setLinkTextColor(attributes.mTextColorLink);
4447         }
4448 
4449         if (attributes.mTextColorHighlight != 0) {
4450             setHighlightColor(attributes.mTextColorHighlight);
4451         }
4452 
4453         if (attributes.mSearchResultHighlightColor != 0) {
4454             setSearchResultHighlightColor(attributes.mSearchResultHighlightColor);
4455         }
4456 
4457         if (attributes.mFocusedSearchResultHighlightColor != 0) {
4458             setFocusedSearchResultHighlightColor(attributes.mFocusedSearchResultHighlightColor);
4459         }
4460 
4461         if (attributes.mTextSize != -1) {
4462             mTextSizeUnit = attributes.mTextSizeUnit;
4463             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4464         }
4465 
4466         if (attributes.mTextLocales != null) {
4467             setTextLocales(attributes.mTextLocales);
4468         }
4469 
4470         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4471             attributes.mFontFamily = null;
4472         }
4473         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4474                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4475 
4476         if (attributes.mShadowColor != 0) {
4477             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4478                     attributes.mShadowColor);
4479         }
4480 
4481         if (attributes.mAllCaps) {
4482             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4483         }
4484 
4485         if (attributes.mHasElegant) {
4486             setElegantTextHeight(attributes.mElegant);
4487         }
4488 
4489         if (attributes.mHasFallbackLineSpacing) {
4490             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4491         }
4492 
4493         if (attributes.mHasLetterSpacing) {
4494             setLetterSpacing(attributes.mLetterSpacing);
4495         }
4496 
4497         if (attributes.mFontFeatureSettings != null) {
4498             setFontFeatureSettings(attributes.mFontFeatureSettings);
4499         }
4500 
4501         if (attributes.mFontVariationSettings != null) {
4502             setFontVariationSettings(attributes.mFontVariationSettings);
4503         }
4504 
4505         if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) {
4506             updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle,
4507                     attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle,
4508                     attributes.mLineBreakWordStyle);
4509         }
4510     }
4511 
4512     /**
4513      * Updates the LineBreakConfig from the TextAppearance.
4514      *
4515      * This method updates the given line configuration from the TextAppearance. This method will
4516      * request new layout if line break config has been changed.
4517      *
4518      * @param isLineBreakStyleSpecified true if the line break style is specified.
4519      * @param isLineBreakWordStyleSpecified true if the line break word style is specified.
4520      * @param lineBreakStyle the value of the line break style in the TextAppearance.
4521      * @param lineBreakWordStyle the value of the line break word style in the TextAppearance.
4522      */
updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, boolean isLineBreakWordStyleSpecified, @LineBreakConfig.LineBreakStyle int lineBreakStyle, @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)4523     private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified,
4524             boolean isLineBreakWordStyleSpecified,
4525             @LineBreakConfig.LineBreakStyle int lineBreakStyle,
4526             @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
4527         boolean updated = false;
4528         if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) {
4529             mLineBreakStyle = lineBreakStyle;
4530             updated = true;
4531         }
4532         if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) {
4533             mLineBreakWordStyle = lineBreakWordStyle;
4534             updated = true;
4535         }
4536         if (updated && mLayout != null) {
4537             nullLayouts();
4538             requestLayout();
4539             invalidate();
4540         }
4541     }
4542     /**
4543      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4544      * the first member of {@link #getTextLocales()}.
4545      * @return the default primary {@link Locale} of the text in this TextView.
4546      */
4547     @NonNull
getTextLocale()4548     public Locale getTextLocale() {
4549         return mTextPaint.getTextLocale();
4550     }
4551 
4552     /**
4553      * Get the default {@link LocaleList} of the text in this TextView.
4554      * @return the default {@link LocaleList} of the text in this TextView.
4555      */
4556     @NonNull @Size(min = 1)
getTextLocales()4557     public LocaleList getTextLocales() {
4558         return mTextPaint.getTextLocales();
4559     }
4560 
changeListenerLocaleTo(@ullable Locale locale)4561     private void changeListenerLocaleTo(@Nullable Locale locale) {
4562         if (mListenerChanged) {
4563             // If a listener has been explicitly set, don't change it. We may break something.
4564             return;
4565         }
4566         // The following null check is not absolutely necessary since all calling points of
4567         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4568         // here in case others would want to call this method in the future.
4569         if (mEditor != null) {
4570             KeyListener listener = mEditor.mKeyListener;
4571             if (listener instanceof DigitsKeyListener) {
4572                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4573             } else if (listener instanceof DateKeyListener) {
4574                 listener = DateKeyListener.getInstance(locale);
4575             } else if (listener instanceof TimeKeyListener) {
4576                 listener = TimeKeyListener.getInstance(locale);
4577             } else if (listener instanceof DateTimeKeyListener) {
4578                 listener = DateTimeKeyListener.getInstance(locale);
4579             } else {
4580                 return;
4581             }
4582             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4583             setKeyListenerOnly(listener);
4584             setInputTypeFromEditor();
4585             if (wasPasswordType) {
4586                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4587                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4588                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4589                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4590                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4591                 }
4592             }
4593         }
4594     }
4595 
4596     /**
4597      * Set the default {@link Locale} of the text in this TextView to a one-member
4598      * {@link LocaleList} containing just the given Locale.
4599      *
4600      * @param locale the {@link Locale} for drawing text, must not be null.
4601      *
4602      * @see #setTextLocales
4603      */
setTextLocale(@onNull Locale locale)4604     public void setTextLocale(@NonNull Locale locale) {
4605         mLocalesChanged = true;
4606         mTextPaint.setTextLocale(locale);
4607         if (mLayout != null) {
4608             nullLayouts();
4609             requestLayout();
4610             invalidate();
4611         }
4612     }
4613 
4614     /**
4615      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4616      *
4617      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4618      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4619      * other aspects of text display, including line breaking.
4620      *
4621      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4622      *
4623      * @see Paint#setTextLocales
4624      */
setTextLocales(@onNull @izemin = 1) LocaleList locales)4625     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4626         mLocalesChanged = true;
4627         mTextPaint.setTextLocales(locales);
4628         if (mLayout != null) {
4629             nullLayouts();
4630             requestLayout();
4631             invalidate();
4632         }
4633     }
4634 
4635     @Override
onConfigurationChanged(Configuration newConfig)4636     protected void onConfigurationChanged(Configuration newConfig) {
4637         super.onConfigurationChanged(newConfig);
4638         if (!mLocalesChanged) {
4639             mTextPaint.setTextLocales(LocaleList.getDefault());
4640             if (mLayout != null) {
4641                 nullLayouts();
4642                 requestLayout();
4643                 invalidate();
4644             }
4645         }
4646         if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) {
4647             mFontWeightAdjustment = newConfig.fontWeightAdjustment;
4648             setTypeface(getTypeface());
4649         }
4650 
4651         InputMethodManager imm = getInputMethodManager();
4652         // if orientation changed and this TextView is currently served.
4653         if (mLastOrientation != newConfig.orientation
4654                 && imm != null && imm.hasActiveInputConnection(this)) {
4655             // EditorInfo.internalImeOptions are out of date.
4656             imm.restartInput(this);
4657         }
4658         mLastOrientation = newConfig.orientation;
4659     }
4660 
4661     /**
4662      * @return the size (in pixels) of the default text size in this TextView.
4663      */
4664     @InspectableProperty
4665     @ViewDebug.ExportedProperty(category = "text")
getTextSize()4666     public float getTextSize() {
4667         return mTextPaint.getTextSize();
4668     }
4669 
4670     /**
4671      * @return the size (in scaled pixels) of the default text size in this TextView.
4672      * @hide
4673      */
4674     @ViewDebug.ExportedProperty(category = "text")
getScaledTextSize()4675     public float getScaledTextSize() {
4676         return mTextPaint.getTextSize() / mTextPaint.density;
4677     }
4678 
4679     /** @hide */
4680     @ViewDebug.ExportedProperty(category = "text", mapping = {
4681             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4682             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4683             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4684             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4685     })
getTypefaceStyle()4686     public int getTypefaceStyle() {
4687         Typeface typeface = mTextPaint.getTypeface();
4688         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4689     }
4690 
4691     /**
4692      * Set the default text size to the given value, interpreted as "scaled
4693      * pixel" units.  This size is adjusted based on the current density and
4694      * user font size preference.
4695      *
4696      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4697      *
4698      * @param size The scaled pixel size.
4699      *
4700      * @attr ref android.R.styleable#TextView_textSize
4701      */
4702     @android.view.RemotableViewMethod
setTextSize(float size)4703     public void setTextSize(float size) {
4704         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4705     }
4706 
4707     /**
4708      * Set the default text size to a given unit and value. See {@link
4709      * TypedValue} for the possible dimension units.
4710      *
4711      * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
4712      *
4713      * @param unit The desired dimension unit.
4714      * @param size The desired size in the given units.
4715      *
4716      * @attr ref android.R.styleable#TextView_textSize
4717      */
setTextSize(int unit, float size)4718     public void setTextSize(int unit, float size) {
4719         if (!isAutoSizeEnabled()) {
4720             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4721         }
4722     }
4723 
4724     @NonNull
getDisplayMetricsOrSystem()4725     private DisplayMetrics getDisplayMetricsOrSystem() {
4726         Context c = getContext();
4727         Resources r;
4728 
4729         if (c == null) {
4730             r = Resources.getSystem();
4731         } else {
4732             r = c.getResources();
4733         }
4734 
4735         return r.getDisplayMetrics();
4736     }
4737 
setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4738     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4739         mTextSizeUnit = unit;
4740         setRawTextSize(TypedValue.applyDimension(unit, size, getDisplayMetricsOrSystem()),
4741                 shouldRequestLayout);
4742     }
4743 
4744     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setRawTextSize(float size, boolean shouldRequestLayout)4745     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4746         if (size != mTextPaint.getTextSize()) {
4747             mTextPaint.setTextSize(size);
4748 
4749             maybeRecalculateLineHeight();
4750             if (shouldRequestLayout && mLayout != null) {
4751                 // Do not auto-size right after setting the text size.
4752                 mNeedsAutoSizeText = false;
4753                 nullLayouts();
4754                 requestLayout();
4755                 invalidate();
4756             }
4757         }
4758     }
4759 
4760     /**
4761      * Gets the text size unit defined by the developer. It may be specified in resources or be
4762      * passed as the unit argument of {@link #setTextSize(int, float)} at runtime.
4763      *
4764      * @return the dimension type of the text size unit originally defined.
4765      * @see TypedValue#TYPE_DIMENSION
4766      */
getTextSizeUnit()4767     public int getTextSizeUnit() {
4768         return mTextSizeUnit;
4769     }
4770 
4771     /**
4772      * Gets the extent by which text should be stretched horizontally.
4773      * This will usually be 1.0.
4774      * @return The horizontal scale factor.
4775      */
4776     @InspectableProperty
getTextScaleX()4777     public float getTextScaleX() {
4778         return mTextPaint.getTextScaleX();
4779     }
4780 
4781     /**
4782      * Sets the horizontal scale factor for text. The default value
4783      * is 1.0. Values greater than 1.0 stretch the text wider.
4784      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4785      * @param size The horizontal scale factor.
4786      * @attr ref android.R.styleable#TextView_textScaleX
4787      */
4788     @android.view.RemotableViewMethod
setTextScaleX(float size)4789     public void setTextScaleX(float size) {
4790         if (size != mTextPaint.getTextScaleX()) {
4791             mUserSetTextScaleX = true;
4792             mTextPaint.setTextScaleX(size);
4793 
4794             if (mLayout != null) {
4795                 nullLayouts();
4796                 requestLayout();
4797                 invalidate();
4798             }
4799         }
4800     }
4801 
4802     /**
4803      * Sets the typeface and style in which the text should be displayed.
4804      * Note that not all Typeface families actually have bold and italic
4805      * variants, so you may need to use
4806      * {@link #setTypeface(Typeface, int)} to get the appearance
4807      * that you actually want.
4808      *
4809      * @see #getTypeface()
4810      *
4811      * @attr ref android.R.styleable#TextView_fontFamily
4812      * @attr ref android.R.styleable#TextView_typeface
4813      * @attr ref android.R.styleable#TextView_textStyle
4814      */
setTypeface(@ullable Typeface tf)4815     public void setTypeface(@Nullable Typeface tf) {
4816         mOriginalTypeface = tf;
4817         if (mFontWeightAdjustment != 0
4818                 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
4819             if (tf == null) {
4820                 if (Flags.fixNullTypefaceBolding()) {
4821                     tf = Typeface.DEFAULT_BOLD;
4822                 } else {
4823                     tf = Typeface.DEFAULT;
4824                 }
4825             } else {
4826                 int newWeight = Math.min(
4827                         Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
4828                         FontStyle.FONT_WEIGHT_MAX);
4829                 int typefaceStyle = tf != null ? tf.getStyle() : 0;
4830                 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0;
4831                 tf = Typeface.create(tf, newWeight, italic);
4832             }
4833         }
4834         if (mTextPaint.getTypeface() != tf) {
4835             mTextPaint.setTypeface(tf);
4836 
4837             if (mLayout != null) {
4838                 nullLayouts();
4839                 requestLayout();
4840                 invalidate();
4841             }
4842         }
4843     }
4844 
4845     /**
4846      * Gets the current {@link Typeface} that is used to style the text.
4847      * @return The current Typeface.
4848      *
4849      * @see #setTypeface(Typeface)
4850      *
4851      * @attr ref android.R.styleable#TextView_fontFamily
4852      * @attr ref android.R.styleable#TextView_typeface
4853      * @attr ref android.R.styleable#TextView_textStyle
4854      */
4855     @InspectableProperty
getTypeface()4856     public Typeface getTypeface() {
4857         return mOriginalTypeface;
4858     }
4859 
4860     /**
4861      * Set the TextView's elegant height metrics flag. This setting selects font
4862      * variants that have not been compacted to fit Latin-based vertical
4863      * metrics, and also increases top and bottom bounds to provide more space.
4864      *
4865      * @param elegant set the paint's elegant metrics flag.
4866      *
4867      * @see #isElegantTextHeight()
4868      * @see Paint#isElegantTextHeight()
4869      *
4870      * @attr ref android.R.styleable#TextView_elegantTextHeight
4871      */
setElegantTextHeight(boolean elegant)4872     public void setElegantTextHeight(boolean elegant) {
4873         if (elegant != mTextPaint.isElegantTextHeight()) {
4874             mTextPaint.setElegantTextHeight(elegant);
4875             if (mLayout != null) {
4876                 nullLayouts();
4877                 requestLayout();
4878                 invalidate();
4879             }
4880         }
4881     }
4882 
4883     /**
4884      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4885      * displaying the text (which is needed to avoid text from consecutive lines running into
4886      * each other). If set, fallback fonts that end up getting used can increase the ascent
4887      * and descent of the lines that they are used on.
4888      * <p/>
4889      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4890      * is typically much taller or deeper than Latin text.
4891      *
4892      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4893      *
4894      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4895      *
4896      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4897      */
setFallbackLineSpacing(boolean enabled)4898     public void setFallbackLineSpacing(boolean enabled) {
4899         int fallbackStrategy;
4900         if (enabled) {
4901             if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
4902                 fallbackStrategy = FALLBACK_LINE_SPACING_ALL;
4903             } else {
4904                 fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
4905             }
4906         } else {
4907             fallbackStrategy = FALLBACK_LINE_SPACING_NONE;
4908         }
4909         if (mUseFallbackLineSpacing != fallbackStrategy) {
4910             mUseFallbackLineSpacing = fallbackStrategy;
4911             if (mLayout != null) {
4912                 nullLayouts();
4913                 requestLayout();
4914                 invalidate();
4915             }
4916         }
4917     }
4918 
4919     /**
4920      * Set true for using width of bounding box as a source of automatic line breaking and drawing.
4921      *
4922      * If this value is false, the TextView determines the View width, drawing offset and automatic
4923      * line breaking based on total advances as text widths. By setting true, use glyph bound's as a
4924      * source of text width.
4925      *
4926      * If the font used for this TextView has glyphs that has negative bearing X or glyph xMax is
4927      * greater than advance, the glyph clipping can be happened because the drawing area may be
4928      * bigger than advance. By setting this to true, the TextView will reserve more spaces for
4929      * drawing are, so clipping can be prevented.
4930      *
4931      * This value is true by default if the target API version is 35 or later.
4932      *
4933      * @param useBoundsForWidth true for using bounding box for width. false for using advances for
4934      *                          width.
4935      * @see #getUseBoundsForWidth()
4936      * @see #setShiftDrawingOffsetForStartOverhang(boolean)
4937      * @see #getShiftDrawingOffsetForStartOverhang()
4938      */
4939     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
setUseBoundsForWidth(boolean useBoundsForWidth)4940     public void setUseBoundsForWidth(boolean useBoundsForWidth) {
4941         if (mUseBoundsForWidth != useBoundsForWidth) {
4942             mUseBoundsForWidth = useBoundsForWidth;
4943             if (mLayout != null) {
4944                 nullLayouts();
4945                 requestLayout();
4946                 invalidate();
4947             }
4948         }
4949     }
4950 
4951     /**
4952      * Returns true if using bounding box as a width, false for using advance as a width.
4953      *
4954      * @see #setUseBoundsForWidth(boolean)
4955      * @see #setShiftDrawingOffsetForStartOverhang(boolean)
4956      * @see #getShiftDrawingOffsetForStartOverhang()
4957      * @return True if using bounding box for width, false if using advance for width.
4958      */
4959     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
getUseBoundsForWidth()4960     public boolean getUseBoundsForWidth() {
4961         return mUseBoundsForWidth;
4962     }
4963 
4964     /**
4965      * Set true for shifting the drawing x offset for showing overhang at the start position.
4966      *
4967      * This flag is ignored if the {@link #getUseBoundsForWidth()} is false.
4968      *
4969      * If this value is false, the TextView draws text from the zero even if there is a glyph stroke
4970      * in a region where the x coordinate is negative. TextView clips the stroke in the region where
4971      * the X coordinate is negative unless the parents has {@link ViewGroup#getClipChildren()} to
4972      * true. This is useful for aligning multiple TextViews vertically.
4973      *
4974      * If this value is true, the TextView draws text with shifting the x coordinate of the drawing
4975      * bounding box. This prevents the clipping even if the parents doesn't have
4976      * {@link ViewGroup#getClipChildren()} to true.
4977      *
4978      * This value is false by default.
4979      *
4980      * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for showing
4981      *                                           the stroke that is in the region whre the x
4982      *                                           coorinate is negative.
4983      * @see #setUseBoundsForWidth(boolean)
4984      * @see #getUseBoundsForWidth()
4985      */
4986     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang)4987     public void setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang) {
4988         if (mShiftDrawingOffsetForStartOverhang != shiftDrawingOffsetForStartOverhang) {
4989             mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
4990             if (mLayout != null) {
4991                 nullLayouts();
4992                 requestLayout();
4993                 invalidate();
4994             }
4995         }
4996     }
4997 
4998     /**
4999      * Returns true if shifting the drawing x offset for start overhang.
5000      *
5001      * @see #setShiftDrawingOffsetForStartOverhang(boolean)
5002      * @see #setUseBoundsForWidth(boolean)
5003      * @see #getUseBoundsForWidth()
5004      * @return True if shifting the drawing x offset for start overhang.
5005      */
5006     @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
getShiftDrawingOffsetForStartOverhang()5007     public boolean getShiftDrawingOffsetForStartOverhang() {
5008         return mShiftDrawingOffsetForStartOverhang;
5009     }
5010 
5011     /**
5012      * Set the minimum font metrics used for line spacing.
5013      *
5014      * <p>
5015      * {@code null} is the default value. If {@code null} is set or left as default, the font
5016      * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
5017      *
5018      * <p>
5019      * The minimum meaning here is the minimum value of line spacing: maximum value of
5020      * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
5021      *
5022      * <p>
5023      * By setting this value, each line will have minimum line spacing regardless of the text
5024      * rendered. For example, usually Japanese script has larger vertical metrics than Latin script.
5025      * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
5026      * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese,
5027      * the line spacing for Japanese is reserved if the TextView contains English text. If the
5028      * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font
5029      * metrics is used.
5030      *
5031      * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value
5032      *                           obtained by
5033      *                           {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
5034      * @see #getMinimumFontMetrics()
5035      * @see Layout#getMinimumFontMetrics()
5036      * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5037      * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5038      * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5039      */
5040     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
setMinimumFontMetrics(@ullable Paint.FontMetrics minimumFontMetrics)5041     public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
5042         mMinimumFontMetrics = minimumFontMetrics;
5043     }
5044 
5045     /**
5046      * Get the minimum font metrics used for line spacing.
5047      *
5048      * @see #setMinimumFontMetrics(Paint.FontMetrics)
5049      * @see Layout#getMinimumFontMetrics()
5050      * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5051      * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5052      * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
5053      *
5054      * @return a minimum font metrics. {@code null} for using the value obtained by
5055      *         {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
5056      */
5057     @Nullable
5058     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
getMinimumFontMetrics()5059     public Paint.FontMetrics getMinimumFontMetrics() {
5060         return mMinimumFontMetrics;
5061     }
5062 
5063     /**
5064      * Returns true if the locale preferred line height is used for the minimum line height.
5065      *
5066      * @return true if using locale preferred line height for the minimum line height. Otherwise
5067      *         false.
5068      *
5069      * @see #setLocalePreferredLineHeightForMinimumUsed(boolean)
5070      * @see #setMinimumFontMetrics(Paint.FontMetrics)
5071      * @see #getMinimumFontMetrics()
5072      */
5073     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
isLocalePreferredLineHeightForMinimumUsed()5074     public boolean isLocalePreferredLineHeightForMinimumUsed() {
5075         return mUseLocalePreferredLineHeightForMinimum;
5076     }
5077 
5078     /**
5079      * Set true if the locale preferred line height is used for the minimum line height.
5080      *
5081      * By setting this flag to true is equivalenet to call
5082      * {@link #setMinimumFontMetrics(Paint.FontMetrics)} with the one obtained by
5083      * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}.
5084      *
5085      * If custom minimum line height was specified by
5086      * {@link #setMinimumFontMetrics(Paint.FontMetrics)}, this flag will be ignored.
5087      *
5088      * @param flag true for using locale preferred line height for the minimum line height.
5089      * @see #isLocalePreferredLineHeightForMinimumUsed()
5090      * @see #setMinimumFontMetrics(Paint.FontMetrics)
5091      * @see #getMinimumFontMetrics()
5092      */
5093     @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
setLocalePreferredLineHeightForMinimumUsed(boolean flag)5094     public void setLocalePreferredLineHeightForMinimumUsed(boolean flag) {
5095         mUseLocalePreferredLineHeightForMinimum = flag;
5096     }
5097 
5098     /**
5099      * @return whether fallback line spacing is enabled, {@code true} by default
5100      *
5101      * @see #setFallbackLineSpacing(boolean)
5102      *
5103      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
5104      */
5105     @InspectableProperty
isFallbackLineSpacing()5106     public boolean isFallbackLineSpacing() {
5107         return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE;
5108     }
5109 
isFallbackLineSpacingForBoringLayout()5110     private boolean isFallbackLineSpacingForBoringLayout() {
5111         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL;
5112     }
5113 
5114     // Package privte for accessing from Editor.java
isFallbackLineSpacingForStaticLayout()5115     /* package */ boolean isFallbackLineSpacingForStaticLayout() {
5116         return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL
5117                 || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
5118     }
5119 
5120     /**
5121      * Get the value of the TextView's elegant height metrics flag. This setting selects font
5122      * variants that have not been compacted to fit Latin-based vertical
5123      * metrics, and also increases top and bottom bounds to provide more space.
5124      * @return {@code true} if the elegant height metrics flag is set.
5125      *
5126      * @see #setElegantTextHeight(boolean)
5127      * @see Paint#setElegantTextHeight(boolean)
5128      */
5129     @InspectableProperty
isElegantTextHeight()5130     public boolean isElegantTextHeight() {
5131         return mTextPaint.isElegantTextHeight();
5132     }
5133 
5134     /**
5135      * Gets the text letter-space value, which determines the spacing between characters.
5136      * The value returned is in ems. Normally, this value is 0.0.
5137      * @return The text letter-space value in ems.
5138      *
5139      * @see #setLetterSpacing(float)
5140      * @see Paint#setLetterSpacing
5141      */
5142     @InspectableProperty
getLetterSpacing()5143     public float getLetterSpacing() {
5144         return mTextPaint.getLetterSpacing();
5145     }
5146 
5147     /**
5148      * Sets text letter-spacing in em units.  Typical values
5149      * for slight expansion will be around 0.05.  Negative values tighten text.
5150      *
5151      * @see #getLetterSpacing()
5152      * @see Paint#getLetterSpacing
5153      *
5154      * @param letterSpacing A text letter-space value in ems.
5155      * @attr ref android.R.styleable#TextView_letterSpacing
5156      */
5157     @android.view.RemotableViewMethod
setLetterSpacing(float letterSpacing)5158     public void setLetterSpacing(float letterSpacing) {
5159         if (letterSpacing != mTextPaint.getLetterSpacing()) {
5160             mTextPaint.setLetterSpacing(letterSpacing);
5161 
5162             if (mLayout != null) {
5163                 nullLayouts();
5164                 requestLayout();
5165                 invalidate();
5166             }
5167         }
5168     }
5169 
5170     /**
5171      * Returns the font feature settings. The format is the same as the CSS
5172      * font-feature-settings attribute:
5173      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
5174      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
5175      *
5176      * @return the currently set font feature settings.  Default is null.
5177      *
5178      * @see #setFontFeatureSettings(String)
5179      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
5180      */
5181     @InspectableProperty
5182     @Nullable
getFontFeatureSettings()5183     public String getFontFeatureSettings() {
5184         return mTextPaint.getFontFeatureSettings();
5185     }
5186 
5187     /**
5188      * Returns the font variation settings.
5189      *
5190      * @return the currently set font variation settings.  Returns null if no variation is
5191      * specified.
5192      *
5193      * @see #setFontVariationSettings(String)
5194      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
5195      */
5196     @Nullable
getFontVariationSettings()5197     public String getFontVariationSettings() {
5198         return mTextPaint.getFontVariationSettings();
5199     }
5200 
5201     /**
5202      * Sets the break strategy for breaking paragraphs into lines. The default value for
5203      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
5204      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
5205      * text "dancing" when being edited.
5206      * <p>
5207      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
5208      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
5209      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
5210      * improves the structure of text layout however has performance impact and requires more time
5211      * to do the text layout.</p>
5212      * <p>
5213      * Compared with {@link #setLineBreakStyle(int)}, line break style with different strictness is
5214      * evaluated in the ICU to identify the potential breakpoints. In
5215      * {@link #setBreakStrategy(int)}, line break strategy handles the post processing of ICU's line
5216      * break result. It aims to evaluate ICU's breakpoints and break the lines based on the
5217      * constraint.
5218      * </p>
5219      *
5220      * @attr ref android.R.styleable#TextView_breakStrategy
5221      * @see #getBreakStrategy()
5222      * @see #setHyphenationFrequency(int)
5223      */
setBreakStrategy(@ayout.BreakStrategy int breakStrategy)5224     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
5225         mBreakStrategy = breakStrategy;
5226         if (mLayout != null) {
5227             nullLayouts();
5228             requestLayout();
5229             invalidate();
5230         }
5231     }
5232 
5233     /**
5234      * Gets the current strategy for breaking paragraphs into lines.
5235      * @return the current strategy for breaking paragraphs into lines.
5236      *
5237      * @attr ref android.R.styleable#TextView_breakStrategy
5238      * @see #setBreakStrategy(int)
5239      */
5240     @InspectableProperty(enumMapping = {
5241             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
5242             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
5243             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
5244     })
5245     @Layout.BreakStrategy
getBreakStrategy()5246     public int getBreakStrategy() {
5247         return mBreakStrategy;
5248     }
5249 
5250     /**
5251      * Sets the frequency of automatic hyphenation to use when determining word breaks.
5252      * The default value for both TextView and {@link EditText} is
5253      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
5254      * is set from the theme.
5255      * <p/>
5256      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
5257      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
5258      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
5259      * improves the structure of text layout however has performance impact and requires more time
5260      * to do the text layout.
5261      * <p/>
5262      * Note: Before Android Q, in the theme hyphenation frequency is set to
5263      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
5264      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
5265      *
5266      * @param hyphenationFrequency the hyphenation frequency to use, one of
5267      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
5268      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
5269      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
5270      * @attr ref android.R.styleable#TextView_hyphenationFrequency
5271      * @see #getHyphenationFrequency()
5272      * @see #getBreakStrategy()
5273      */
setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)5274     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
5275         mHyphenationFrequency = hyphenationFrequency;
5276         if (mLayout != null) {
5277             nullLayouts();
5278             requestLayout();
5279             invalidate();
5280         }
5281     }
5282 
5283     /**
5284      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
5285      * @return the current frequency of automatic hyphenation to be used when determining word
5286      * breaks.
5287      *
5288      * @attr ref android.R.styleable#TextView_hyphenationFrequency
5289      * @see #setHyphenationFrequency(int)
5290      */
5291     @InspectableProperty(enumMapping = {
5292             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
5293             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
5294             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
5295     })
5296     @Layout.HyphenationFrequency
getHyphenationFrequency()5297     public int getHyphenationFrequency() {
5298         return mHyphenationFrequency;
5299     }
5300 
5301     /**
5302      * Sets the line-break style for text wrapping.
5303      *
5304      * <p>Line-break style specifies the line-break strategies that can be used
5305      * for text wrapping. The line-break style affects rule-based line breaking
5306      * by specifying the strictness of line-breaking rules.
5307      *
5308      * <p>The following are types of line-break styles:
5309      * <ul>
5310      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}
5311      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}
5312      *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}
5313      * </ul>
5314      *
5315      * <p>The default line-break style is
5316      * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
5317      * line-breaking rules are used.
5318      *
5319      * <p>See the
5320      * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
5321      * line-break property</a> for more information.
5322      *
5323      * @param lineBreakStyle The line-break style for the text.
5324      */
setLineBreakStyle(@ineBreakConfig.LineBreakStyle int lineBreakStyle)5325     public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
5326         if (mLineBreakStyle != lineBreakStyle) {
5327             mLineBreakStyle = lineBreakStyle;
5328             if (mLayout != null) {
5329                 nullLayouts();
5330                 requestLayout();
5331                 invalidate();
5332             }
5333         }
5334     }
5335 
5336     /**
5337      * Sets the line-break word style for text wrapping.
5338      *
5339      * <p>The line-break word style affects dictionary-based line breaking by
5340      * providing phrase-based line-breaking opportunities. Use
5341      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
5342      * phrase-based line breaking.
5343      *
5344      * <p>The default line-break word style is
5345      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
5346      * no line-breaking word style is used.
5347      *
5348      * <p>See the
5349      * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
5350      * word-break property</a> for more information.
5351      *
5352      * @param lineBreakWordStyle The line-break word style for the text.
5353      */
setLineBreakWordStyle(@ineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)5354     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
5355         if (mLineBreakWordStyle != lineBreakWordStyle) {
5356             mLineBreakWordStyle = lineBreakWordStyle;
5357             if (mLayout != null) {
5358                 nullLayouts();
5359                 requestLayout();
5360                 invalidate();
5361             }
5362         }
5363     }
5364 
5365     /**
5366      * Gets the current line-break style for text wrapping.
5367      *
5368      * @return The line-break style to be used for text wrapping.
5369      */
getLineBreakStyle()5370     public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
5371         return mLineBreakStyle;
5372     }
5373 
5374     /**
5375      * Gets the current line-break word style for text wrapping.
5376      *
5377      * @return The line-break word style to be used for text wrapping.
5378      */
getLineBreakWordStyle()5379     public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
5380         return mLineBreakWordStyle;
5381     }
5382 
5383     /**
5384      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
5385      *
5386      * @return a current {@link PrecomputedText.Params}
5387      * @see PrecomputedText
5388      */
getTextMetricsParams()5389     public @NonNull PrecomputedText.Params getTextMetricsParams() {
5390         return new PrecomputedText.Params(new TextPaint(mTextPaint),
5391                 LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
5392                 getTextDirectionHeuristic(),
5393                 mBreakStrategy, mHyphenationFrequency);
5394     }
5395 
5396     /**
5397      * Apply the text layout parameter.
5398      *
5399      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
5400      * @see PrecomputedText
5401      */
setTextMetricsParams(@onNull PrecomputedText.Params params)5402     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
5403         mTextPaint.set(params.getTextPaint());
5404         mUserSetTextScaleX = true;
5405         mTextDir = params.getTextDirection();
5406         mBreakStrategy = params.getBreakStrategy();
5407         mHyphenationFrequency = params.getHyphenationFrequency();
5408         LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
5409         mLineBreakStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig);
5410         mLineBreakWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig);
5411         if (mLayout != null) {
5412             nullLayouts();
5413             requestLayout();
5414             invalidate();
5415         }
5416     }
5417 
5418     /**
5419      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
5420      * last line is too short for justification, the last line will be displayed with the
5421      * alignment set by {@link android.view.View#setTextAlignment}.
5422      *
5423      * @see #getJustificationMode()
5424      */
5425     @Layout.JustificationMode
5426     @android.view.RemotableViewMethod
setJustificationMode(@ayout.JustificationMode int justificationMode)5427     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
5428         mJustificationMode = justificationMode;
5429         if (mLayout != null) {
5430             nullLayouts();
5431             requestLayout();
5432             invalidate();
5433         }
5434     }
5435 
5436     /**
5437      * @return true if currently paragraph justification mode.
5438      *
5439      * @see #setJustificationMode(int)
5440      */
5441     @InspectableProperty(enumMapping = {
5442             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
5443             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
5444     })
getJustificationMode()5445     public @Layout.JustificationMode int getJustificationMode() {
5446         return mJustificationMode;
5447     }
5448 
5449     /**
5450      * Sets font feature settings. The format is the same as the CSS
5451      * font-feature-settings attribute:
5452      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
5453      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
5454      *
5455      * @param fontFeatureSettings font feature settings represented as CSS compatible string
5456      *
5457      * @see #getFontFeatureSettings()
5458      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
5459      *
5460      * @attr ref android.R.styleable#TextView_fontFeatureSettings
5461      */
5462     @android.view.RemotableViewMethod
setFontFeatureSettings(@ullable String fontFeatureSettings)5463     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
5464         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
5465             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
5466 
5467             if (mLayout != null) {
5468                 nullLayouts();
5469                 requestLayout();
5470                 invalidate();
5471             }
5472         }
5473     }
5474 
5475 
5476     /**
5477      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
5478      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
5479      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
5480      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
5481      * are invalid. If a specified axis name is not defined in the font, the settings will be
5482      * ignored.
5483      *
5484      * <p>
5485      * Examples,
5486      * <ul>
5487      * <li>Set font width to 150.
5488      * <pre>
5489      * <code>
5490      *   TextView textView = (TextView) findViewById(R.id.textView);
5491      *   textView.setFontVariationSettings("'wdth' 150");
5492      * </code>
5493      * </pre>
5494      * </li>
5495      *
5496      * <li>Set the font slant to 20 degrees and ask for italic style.
5497      * <pre>
5498      * <code>
5499      *   TextView textView = (TextView) findViewById(R.id.textView);
5500      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
5501      * </code>
5502      * </pre>
5503      * </p>
5504      * </li>
5505      * </ul>
5506      *
5507      * @param fontVariationSettings font variation settings. You can pass null or empty string as
5508      *                              no variation settings.
5509      * @return true if the given settings is effective to at least one font file underlying this
5510      *         TextView. This function also returns true for empty settings string. Otherwise
5511      *         returns false.
5512      *
5513      * @throws IllegalArgumentException If given string is not a valid font variation settings
5514      *                                  format.
5515      *
5516      * @see #getFontVariationSettings()
5517      * @see FontVariationAxis
5518      *
5519      * @attr ref android.R.styleable#TextView_fontVariationSettings
5520      */
5521     @android.view.RemotableViewMethod
setFontVariationSettings(@ullable String fontVariationSettings)5522     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
5523         final String existingSettings = mTextPaint.getFontVariationSettings();
5524         if (fontVariationSettings == existingSettings
5525                 || (fontVariationSettings != null
5526                         && fontVariationSettings.equals(existingSettings))) {
5527             return true;
5528         }
5529         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
5530 
5531         if (effective && mLayout != null) {
5532             nullLayouts();
5533             requestLayout();
5534             invalidate();
5535         }
5536         return effective;
5537     }
5538 
5539     /**
5540      * Sets the text color for all the states (normal, selected,
5541      * focused) to be this color.
5542      *
5543      * @param color A color value in the form 0xAARRGGBB.
5544      * Do not pass a resource ID. To get a color value from a resource ID, call
5545      * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}.
5546      *
5547      * @see #setTextColor(ColorStateList)
5548      * @see #getTextColors()
5549      *
5550      * @attr ref android.R.styleable#TextView_textColor
5551      */
5552     @android.view.RemotableViewMethod
setTextColor(@olorInt int color)5553     public void setTextColor(@ColorInt int color) {
5554         mTextColor = ColorStateList.valueOf(color);
5555         updateTextColors();
5556     }
5557 
5558     /**
5559      * Sets the text color.
5560      *
5561      * @see #setTextColor(int)
5562      * @see #getTextColors()
5563      * @see #setHintTextColor(ColorStateList)
5564      * @see #setLinkTextColor(ColorStateList)
5565      *
5566      * @attr ref android.R.styleable#TextView_textColor
5567      */
5568     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)5569     public void setTextColor(ColorStateList colors) {
5570         if (colors == null) {
5571             throw new NullPointerException();
5572         }
5573 
5574         mTextColor = colors;
5575         updateTextColors();
5576     }
5577 
5578     /**
5579      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
5580      *
5581      * @see #setTextColor(ColorStateList)
5582      * @see #setTextColor(int)
5583      *
5584      * @attr ref android.R.styleable#TextView_textColor
5585      */
5586     @InspectableProperty(name = "textColor")
getTextColors()5587     public final ColorStateList getTextColors() {
5588         return mTextColor;
5589     }
5590 
5591     /**
5592      * Return the current color selected for normal text.
5593      *
5594      * @return Returns the current text color.
5595      */
5596     @ColorInt
getCurrentTextColor()5597     public final int getCurrentTextColor() {
5598         return mCurTextColor;
5599     }
5600 
5601     /**
5602      * Sets the color used to display the selection highlight.
5603      *
5604      * @attr ref android.R.styleable#TextView_textColorHighlight
5605      */
5606     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)5607     public void setHighlightColor(@ColorInt int color) {
5608         if (mHighlightColor != color) {
5609             mHighlightColor = color;
5610             invalidate();
5611         }
5612     }
5613 
5614     /**
5615      * @return the color used to display the selection highlight
5616      *
5617      * @see #setHighlightColor(int)
5618      *
5619      * @attr ref android.R.styleable#TextView_textColorHighlight
5620      */
5621     @InspectableProperty(name = "textColorHighlight")
5622     @ColorInt
getHighlightColor()5623     public int getHighlightColor() {
5624         return mHighlightColor;
5625     }
5626 
5627     /**
5628      * Sets whether the soft input method will be made visible when this
5629      * TextView gets focused. The default is true.
5630      */
5631     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)5632     public final void setShowSoftInputOnFocus(boolean show) {
5633         createEditorIfNeeded();
5634         mEditor.mShowSoftInputOnFocus = show;
5635     }
5636 
5637     /**
5638      * Returns whether the soft input method will be made visible when this
5639      * TextView gets focused. The default is true.
5640      */
getShowSoftInputOnFocus()5641     public final boolean getShowSoftInputOnFocus() {
5642         // When there is no Editor, return default true value
5643         return mEditor == null || mEditor.mShowSoftInputOnFocus;
5644     }
5645 
5646     /**
5647      * Gives the text a shadow of the specified blur radius and color, the specified
5648      * distance from its drawn position.
5649      * <p>
5650      * The text shadow produced does not interact with the properties on view
5651      * that are responsible for real time shadows,
5652      * {@link View#getElevation() elevation} and
5653      * {@link View#getTranslationZ() translationZ}.
5654      *
5655      * @see Paint#setShadowLayer(float, float, float, int)
5656      *
5657      * @attr ref android.R.styleable#TextView_shadowColor
5658      * @attr ref android.R.styleable#TextView_shadowDx
5659      * @attr ref android.R.styleable#TextView_shadowDy
5660      * @attr ref android.R.styleable#TextView_shadowRadius
5661      */
setShadowLayer(float radius, float dx, float dy, int color)5662     public void setShadowLayer(float radius, float dx, float dy, int color) {
5663         mTextPaint.setShadowLayer(radius, dx, dy, color);
5664 
5665         mShadowRadius = radius;
5666         mShadowDx = dx;
5667         mShadowDy = dy;
5668         mShadowColor = color;
5669 
5670         // Will change text clip region
5671         if (mEditor != null) {
5672             mEditor.invalidateTextDisplayList();
5673             mEditor.invalidateHandlesAndActionMode();
5674         }
5675         invalidate();
5676     }
5677 
5678     /**
5679      * Gets the radius of the shadow layer.
5680      *
5681      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
5682      *
5683      * @see #setShadowLayer(float, float, float, int)
5684      *
5685      * @attr ref android.R.styleable#TextView_shadowRadius
5686      */
5687     @InspectableProperty
getShadowRadius()5688     public float getShadowRadius() {
5689         return mShadowRadius;
5690     }
5691 
5692     /**
5693      * @return the horizontal offset of the shadow layer
5694      *
5695      * @see #setShadowLayer(float, float, float, int)
5696      *
5697      * @attr ref android.R.styleable#TextView_shadowDx
5698      */
5699     @InspectableProperty
getShadowDx()5700     public float getShadowDx() {
5701         return mShadowDx;
5702     }
5703 
5704     /**
5705      * Gets the vertical offset of the shadow layer.
5706      * @return The vertical offset of the shadow layer.
5707      *
5708      * @see #setShadowLayer(float, float, float, int)
5709      *
5710      * @attr ref android.R.styleable#TextView_shadowDy
5711      */
5712     @InspectableProperty
getShadowDy()5713     public float getShadowDy() {
5714         return mShadowDy;
5715     }
5716 
5717     /**
5718      * Gets the color of the shadow layer.
5719      * @return the color of the shadow layer
5720      *
5721      * @see #setShadowLayer(float, float, float, int)
5722      *
5723      * @attr ref android.R.styleable#TextView_shadowColor
5724      */
5725     @InspectableProperty
5726     @ColorInt
getShadowColor()5727     public int getShadowColor() {
5728         return mShadowColor;
5729     }
5730 
5731     /**
5732      * Gets the {@link TextPaint} used for the text.
5733      * Use this only to consult the Paint's properties and not to change them.
5734      * @return The base paint used for the text.
5735      */
getPaint()5736     public TextPaint getPaint() {
5737         return mTextPaint;
5738     }
5739 
5740     /**
5741      * Sets the autolink mask of the text.  See {@link
5742      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
5743      * possible values.
5744      *
5745      * <p class="note"><b>Note:</b>
5746      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
5747      * is deprecated and should be avoided; see its documentation.
5748      *
5749      * @attr ref android.R.styleable#TextView_autoLink
5750      */
5751     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)5752     public final void setAutoLinkMask(int mask) {
5753         mAutoLinkMask = mask;
5754     }
5755 
5756     /**
5757      * Sets whether the movement method will automatically be set to
5758      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5759      * set to nonzero and links are detected in {@link #setText}.
5760      * The default is true.
5761      *
5762      * @attr ref android.R.styleable#TextView_linksClickable
5763      */
5764     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)5765     public final void setLinksClickable(boolean whether) {
5766         mLinksClickable = whether;
5767     }
5768 
5769     /**
5770      * Returns whether the movement method will automatically be set to
5771      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
5772      * set to nonzero and links are detected in {@link #setText}.
5773      * The default is true.
5774      *
5775      * @attr ref android.R.styleable#TextView_linksClickable
5776      */
5777     @InspectableProperty
getLinksClickable()5778     public final boolean getLinksClickable() {
5779         return mLinksClickable;
5780     }
5781 
5782     /**
5783      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5784      * (by {@link Linkify} or otherwise) if any.  You can call
5785      * {@link URLSpan#getURL} on them to find where they link to
5786      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5787      * to find the region of the text they are attached to.
5788      */
getUrls()5789     public URLSpan[] getUrls() {
5790         if (mText instanceof Spanned) {
5791             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5792         } else {
5793             return new URLSpan[0];
5794         }
5795     }
5796 
5797     /**
5798      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5799      * TextView.
5800      *
5801      * @see #setHintTextColor(ColorStateList)
5802      * @see #getHintTextColors()
5803      * @see #setTextColor(int)
5804      *
5805      * @attr ref android.R.styleable#TextView_textColorHint
5806      */
5807     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)5808     public final void setHintTextColor(@ColorInt int color) {
5809         mHintTextColor = ColorStateList.valueOf(color);
5810         updateTextColors();
5811     }
5812 
5813     /**
5814      * Sets the color of the hint text.
5815      *
5816      * @see #getHintTextColors()
5817      * @see #setHintTextColor(int)
5818      * @see #setTextColor(ColorStateList)
5819      * @see #setLinkTextColor(ColorStateList)
5820      *
5821      * @attr ref android.R.styleable#TextView_textColorHint
5822      */
setHintTextColor(ColorStateList colors)5823     public final void setHintTextColor(ColorStateList colors) {
5824         mHintTextColor = colors;
5825         updateTextColors();
5826     }
5827 
5828     /**
5829      * @return the color of the hint text, for the different states of this TextView.
5830      *
5831      * @see #setHintTextColor(ColorStateList)
5832      * @see #setHintTextColor(int)
5833      * @see #setTextColor(ColorStateList)
5834      * @see #setLinkTextColor(ColorStateList)
5835      *
5836      * @attr ref android.R.styleable#TextView_textColorHint
5837      */
5838     @InspectableProperty(name = "textColorHint")
getHintTextColors()5839     public final ColorStateList getHintTextColors() {
5840         return mHintTextColor;
5841     }
5842 
5843     /**
5844      * <p>Return the current color selected to paint the hint text.</p>
5845      *
5846      * @return Returns the current hint text color.
5847      */
5848     @ColorInt
getCurrentHintTextColor()5849     public final int getCurrentHintTextColor() {
5850         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5851     }
5852 
5853     /**
5854      * Sets the color of links in the text.
5855      *
5856      * @see #setLinkTextColor(ColorStateList)
5857      * @see #getLinkTextColors()
5858      *
5859      * @attr ref android.R.styleable#TextView_textColorLink
5860      */
5861     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)5862     public final void setLinkTextColor(@ColorInt int color) {
5863         mLinkTextColor = ColorStateList.valueOf(color);
5864         updateTextColors();
5865     }
5866 
5867     /**
5868      * Sets the color of links in the text.
5869      *
5870      * @see #setLinkTextColor(int)
5871      * @see #getLinkTextColors()
5872      * @see #setTextColor(ColorStateList)
5873      * @see #setHintTextColor(ColorStateList)
5874      *
5875      * @attr ref android.R.styleable#TextView_textColorLink
5876      */
setLinkTextColor(ColorStateList colors)5877     public final void setLinkTextColor(ColorStateList colors) {
5878         mLinkTextColor = colors;
5879         updateTextColors();
5880     }
5881 
5882     /**
5883      * @return the list of colors used to paint the links in the text, for the different states of
5884      * this TextView
5885      *
5886      * @see #setLinkTextColor(ColorStateList)
5887      * @see #setLinkTextColor(int)
5888      *
5889      * @attr ref android.R.styleable#TextView_textColorLink
5890      */
5891     @InspectableProperty(name = "textColorLink")
getLinkTextColors()5892     public final ColorStateList getLinkTextColors() {
5893         return mLinkTextColor;
5894     }
5895 
5896     /**
5897      * Sets the horizontal alignment of the text and the
5898      * vertical gravity that will be used when there is extra space
5899      * in the TextView beyond what is required for the text itself.
5900      *
5901      * @see android.view.Gravity
5902      * @attr ref android.R.styleable#TextView_gravity
5903      */
5904     @android.view.RemotableViewMethod
setGravity(int gravity)5905     public void setGravity(int gravity) {
5906         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5907             gravity |= Gravity.START;
5908         }
5909         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5910             gravity |= Gravity.TOP;
5911         }
5912 
5913         boolean newLayout = false;
5914 
5915         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5916                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5917             newLayout = true;
5918         }
5919 
5920         if (gravity != mGravity) {
5921             invalidate();
5922         }
5923 
5924         mGravity = gravity;
5925 
5926         if (mLayout != null && newLayout) {
5927             // XXX this is heavy-handed because no actual content changes.
5928             int want = mLayout.getWidth();
5929             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5930 
5931             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5932                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5933         }
5934     }
5935 
5936     /**
5937      * Returns the horizontal and vertical alignment of this TextView.
5938      *
5939      * @see android.view.Gravity
5940      * @attr ref android.R.styleable#TextView_gravity
5941      */
5942     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()5943     public int getGravity() {
5944         return mGravity;
5945     }
5946 
5947     /**
5948      * Gets the flags on the Paint being used to display the text.
5949      * @return The flags on the Paint being used to display the text.
5950      * @see Paint#getFlags
5951      */
getPaintFlags()5952     public int getPaintFlags() {
5953         return mTextPaint.getFlags();
5954     }
5955 
5956     /**
5957      * Sets flags on the Paint being used to display the text and
5958      * reflows the text if they are different from the old flags.
5959      * @see Paint#setFlags
5960      */
5961     @android.view.RemotableViewMethod
setPaintFlags(int flags)5962     public void setPaintFlags(int flags) {
5963         if (mTextPaint.getFlags() != flags) {
5964             mTextPaint.setFlags(flags);
5965 
5966             if (mLayout != null) {
5967                 nullLayouts();
5968                 requestLayout();
5969                 invalidate();
5970             }
5971         }
5972     }
5973 
5974     /**
5975      * Sets whether the text should be allowed to be wider than the
5976      * View is.  If false, it will be wrapped to the width of the View.
5977      *
5978      * @attr ref android.R.styleable#TextView_scrollHorizontally
5979      */
setHorizontallyScrolling(boolean whether)5980     public void setHorizontallyScrolling(boolean whether) {
5981         if (mHorizontallyScrolling != whether) {
5982             mHorizontallyScrolling = whether;
5983 
5984             if (mLayout != null) {
5985                 nullLayouts();
5986                 requestLayout();
5987                 invalidate();
5988             }
5989         }
5990     }
5991 
5992     /**
5993      * Returns whether the text is allowed to be wider than the View.
5994      * If false, the text will be wrapped to the width of the View.
5995      *
5996      * @attr ref android.R.styleable#TextView_scrollHorizontally
5997      * @see #setHorizontallyScrolling(boolean)
5998      */
5999     @InspectableProperty(name = "scrollHorizontally")
isHorizontallyScrollable()6000     public final boolean isHorizontallyScrollable() {
6001         return mHorizontallyScrolling;
6002     }
6003 
6004     /**
6005      * Returns whether the text is allowed to be wider than the View.
6006      * If false, the text will be wrapped to the width of the View.
6007      *
6008      * @attr ref android.R.styleable#TextView_scrollHorizontally
6009      * @hide
6010      */
6011     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
getHorizontallyScrolling()6012     public boolean getHorizontallyScrolling() {
6013         return mHorizontallyScrolling;
6014     }
6015 
6016     /**
6017      * Sets the height of the TextView to be at least {@code minLines} tall.
6018      * <p>
6019      * This value is used for height calculation if LayoutParams does not force TextView to have an
6020      * exact height. Setting this value overrides other previous minimum height configurations such
6021      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
6022      * this value to 1.
6023      *
6024      * @param minLines the minimum height of TextView in terms of number of lines
6025      *
6026      * @see #getMinLines()
6027      * @see #setLines(int)
6028      *
6029      * @attr ref android.R.styleable#TextView_minLines
6030      */
6031     @android.view.RemotableViewMethod
setMinLines(int minLines)6032     public void setMinLines(int minLines) {
6033         mMinimum = minLines;
6034         mMinMode = LINES;
6035 
6036         requestLayout();
6037         invalidate();
6038     }
6039 
6040     /**
6041      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
6042      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
6043      *
6044      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
6045      *         height is not defined in lines
6046      *
6047      * @see #setMinLines(int)
6048      * @see #setLines(int)
6049      *
6050      * @attr ref android.R.styleable#TextView_minLines
6051      */
6052     @InspectableProperty
getMinLines()6053     public int getMinLines() {
6054         return mMinMode == LINES ? mMinimum : -1;
6055     }
6056 
6057     /**
6058      * Sets the height of the TextView to be at least {@code minPixels} tall.
6059      * <p>
6060      * This value is used for height calculation if LayoutParams does not force TextView to have an
6061      * exact height. Setting this value overrides previous minimum height configurations such as
6062      * {@link #setMinLines(int)} or {@link #setLines(int)}.
6063      * <p>
6064      * The value given here is different than {@link #setMinimumHeight(int)}. Between
6065      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
6066      * used to decide the final height.
6067      *
6068      * @param minPixels the minimum height of TextView in terms of pixels
6069      *
6070      * @see #getMinHeight()
6071      * @see #setHeight(int)
6072      *
6073      * @attr ref android.R.styleable#TextView_minHeight
6074      */
6075     @android.view.RemotableViewMethod
setMinHeight(int minPixels)6076     public void setMinHeight(int minPixels) {
6077         mMinimum = minPixels;
6078         mMinMode = PIXELS;
6079 
6080         requestLayout();
6081         invalidate();
6082     }
6083 
6084     /**
6085      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
6086      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
6087      *
6088      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
6089      *         defined in pixels
6090      *
6091      * @see #setMinHeight(int)
6092      * @see #setHeight(int)
6093      *
6094      * @attr ref android.R.styleable#TextView_minHeight
6095      */
getMinHeight()6096     public int getMinHeight() {
6097         return mMinMode == PIXELS ? mMinimum : -1;
6098     }
6099 
6100     /**
6101      * Sets the height of the TextView to be at most {@code maxLines} tall.
6102      * <p>
6103      * This value is used for height calculation if LayoutParams does not force TextView to have an
6104      * exact height. Setting this value overrides previous maximum height configurations such as
6105      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
6106      *
6107      * @param maxLines the maximum height of TextView in terms of number of lines
6108      *
6109      * @see #getMaxLines()
6110      * @see #setLines(int)
6111      *
6112      * @attr ref android.R.styleable#TextView_maxLines
6113      */
6114     @android.view.RemotableViewMethod
setMaxLines(int maxLines)6115     public void setMaxLines(int maxLines) {
6116         mMaximum = maxLines;
6117         mMaxMode = LINES;
6118 
6119         requestLayout();
6120         invalidate();
6121     }
6122 
6123     /**
6124      * Returns the maximum height of TextView in terms of number of lines or -1 if the
6125      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
6126      *
6127      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
6128      *         is not defined in lines.
6129      *
6130      * @see #setMaxLines(int)
6131      * @see #setLines(int)
6132      *
6133      * @attr ref android.R.styleable#TextView_maxLines
6134      */
6135     @InspectableProperty
getMaxLines()6136     public int getMaxLines() {
6137         return mMaxMode == LINES ? mMaximum : -1;
6138     }
6139 
6140     /**
6141      * Sets the height of the TextView to be at most {@code maxPixels} tall.
6142      * <p>
6143      * This value is used for height calculation if LayoutParams does not force TextView to have an
6144      * exact height. Setting this value overrides previous maximum height configurations such as
6145      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
6146      *
6147      * @param maxPixels the maximum height of TextView in terms of pixels
6148      *
6149      * @see #getMaxHeight()
6150      * @see #setHeight(int)
6151      *
6152      * @attr ref android.R.styleable#TextView_maxHeight
6153      */
6154     @android.view.RemotableViewMethod
setMaxHeight(int maxPixels)6155     public void setMaxHeight(int maxPixels) {
6156         mMaximum = maxPixels;
6157         mMaxMode = PIXELS;
6158 
6159         requestLayout();
6160         invalidate();
6161     }
6162 
6163     /**
6164      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
6165      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
6166      *
6167      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
6168      *         is not defined in pixels
6169      *
6170      * @see #setMaxHeight(int)
6171      * @see #setHeight(int)
6172      *
6173      * @attr ref android.R.styleable#TextView_maxHeight
6174      */
6175     @InspectableProperty
getMaxHeight()6176     public int getMaxHeight() {
6177         return mMaxMode == PIXELS ? mMaximum : -1;
6178     }
6179 
6180     /**
6181      * Sets the height of the TextView to be exactly {@code lines} tall.
6182      * <p>
6183      * This value is used for height calculation if LayoutParams does not force TextView to have an
6184      * exact height. Setting this value overrides previous minimum/maximum height configurations
6185      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
6186      * set this value to 1.
6187      *
6188      * @param lines the exact height of the TextView in terms of lines
6189      *
6190      * @see #setHeight(int)
6191      *
6192      * @attr ref android.R.styleable#TextView_lines
6193      */
6194     @android.view.RemotableViewMethod
setLines(int lines)6195     public void setLines(int lines) {
6196         mMaximum = mMinimum = lines;
6197         mMaxMode = mMinMode = LINES;
6198 
6199         requestLayout();
6200         invalidate();
6201     }
6202 
6203     /**
6204      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
6205      * <p>
6206      * This value is used for height calculation if LayoutParams does not force TextView to have an
6207      * exact height. Setting this value overrides previous minimum/maximum height configurations
6208      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
6209      *
6210      * @param pixels the exact height of the TextView in terms of pixels
6211      *
6212      * @see #setLines(int)
6213      *
6214      * @attr ref android.R.styleable#TextView_height
6215      */
6216     @android.view.RemotableViewMethod
setHeight(int pixels)6217     public void setHeight(int pixels) {
6218         mMaximum = mMinimum = pixels;
6219         mMaxMode = mMinMode = PIXELS;
6220 
6221         requestLayout();
6222         invalidate();
6223     }
6224 
6225     /**
6226      * Sets the width of the TextView to be at least {@code minEms} wide.
6227      * <p>
6228      * This value is used for width calculation if LayoutParams does not force TextView to have an
6229      * exact width. Setting this value overrides previous minimum width configurations such as
6230      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
6231      *
6232      * @param minEms the minimum width of TextView in terms of ems
6233      *
6234      * @see #getMinEms()
6235      * @see #setEms(int)
6236      *
6237      * @attr ref android.R.styleable#TextView_minEms
6238      */
6239     @android.view.RemotableViewMethod
setMinEms(int minEms)6240     public void setMinEms(int minEms) {
6241         mMinWidth = minEms;
6242         mMinWidthMode = EMS;
6243 
6244         requestLayout();
6245         invalidate();
6246     }
6247 
6248     /**
6249      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
6250      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
6251      *
6252      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
6253      *         defined in ems
6254      *
6255      * @see #setMinEms(int)
6256      * @see #setEms(int)
6257      *
6258      * @attr ref android.R.styleable#TextView_minEms
6259      */
6260     @InspectableProperty
getMinEms()6261     public int getMinEms() {
6262         return mMinWidthMode == EMS ? mMinWidth : -1;
6263     }
6264 
6265     /**
6266      * Sets the width of the TextView to be at least {@code minPixels} wide.
6267      * <p>
6268      * This value is used for width calculation if LayoutParams does not force TextView to have an
6269      * exact width. Setting this value overrides previous minimum width configurations such as
6270      * {@link #setMinEms(int)} or {@link #setEms(int)}.
6271      * <p>
6272      * The value given here is different than {@link #setMinimumWidth(int)}. Between
6273      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
6274      * to decide the final width.
6275      *
6276      * @param minPixels the minimum width of TextView in terms of pixels
6277      *
6278      * @see #getMinWidth()
6279      * @see #setWidth(int)
6280      *
6281      * @attr ref android.R.styleable#TextView_minWidth
6282      */
6283     @android.view.RemotableViewMethod
setMinWidth(int minPixels)6284     public void setMinWidth(int minPixels) {
6285         mMinWidth = minPixels;
6286         mMinWidthMode = PIXELS;
6287 
6288         requestLayout();
6289         invalidate();
6290     }
6291 
6292     /**
6293      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
6294      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
6295      *
6296      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
6297      *         defined in pixels
6298      *
6299      * @see #setMinWidth(int)
6300      * @see #setWidth(int)
6301      *
6302      * @attr ref android.R.styleable#TextView_minWidth
6303      */
6304     @InspectableProperty
getMinWidth()6305     public int getMinWidth() {
6306         return mMinWidthMode == PIXELS ? mMinWidth : -1;
6307     }
6308 
6309     /**
6310      * Sets the width of the TextView to be at most {@code maxEms} wide.
6311      * <p>
6312      * This value is used for width calculation if LayoutParams does not force TextView to have an
6313      * exact width. Setting this value overrides previous maximum width configurations such as
6314      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
6315      *
6316      * @param maxEms the maximum width of TextView in terms of ems
6317      *
6318      * @see #getMaxEms()
6319      * @see #setEms(int)
6320      *
6321      * @attr ref android.R.styleable#TextView_maxEms
6322      */
6323     @android.view.RemotableViewMethod
setMaxEms(int maxEms)6324     public void setMaxEms(int maxEms) {
6325         mMaxWidth = maxEms;
6326         mMaxWidthMode = EMS;
6327 
6328         requestLayout();
6329         invalidate();
6330     }
6331 
6332     /**
6333      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
6334      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
6335      *
6336      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
6337      *         defined in ems
6338      *
6339      * @see #setMaxEms(int)
6340      * @see #setEms(int)
6341      *
6342      * @attr ref android.R.styleable#TextView_maxEms
6343      */
6344     @InspectableProperty
getMaxEms()6345     public int getMaxEms() {
6346         return mMaxWidthMode == EMS ? mMaxWidth : -1;
6347     }
6348 
6349     /**
6350      * Sets the width of the TextView to be at most {@code maxPixels} wide.
6351      * <p>
6352      * This value is used for width calculation if LayoutParams does not force TextView to have an
6353      * exact width. Setting this value overrides previous maximum width configurations such as
6354      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
6355      *
6356      * @param maxPixels the maximum width of TextView in terms of pixels
6357      *
6358      * @see #getMaxWidth()
6359      * @see #setWidth(int)
6360      *
6361      * @attr ref android.R.styleable#TextView_maxWidth
6362      */
6363     @android.view.RemotableViewMethod
setMaxWidth(int maxPixels)6364     public void setMaxWidth(int maxPixels) {
6365         mMaxWidth = maxPixels;
6366         mMaxWidthMode = PIXELS;
6367 
6368         requestLayout();
6369         invalidate();
6370     }
6371 
6372     /**
6373      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
6374      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
6375      *
6376      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
6377      *         defined in pixels
6378      *
6379      * @see #setMaxWidth(int)
6380      * @see #setWidth(int)
6381      *
6382      * @attr ref android.R.styleable#TextView_maxWidth
6383      */
6384     @InspectableProperty
getMaxWidth()6385     public int getMaxWidth() {
6386         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
6387     }
6388 
6389     /**
6390      * Sets the width of the TextView to be exactly {@code ems} wide.
6391      *
6392      * This value is used for width calculation if LayoutParams does not force TextView to have an
6393      * exact width. Setting this value overrides previous minimum/maximum configurations such as
6394      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
6395      *
6396      * @param ems the exact width of the TextView in terms of ems
6397      *
6398      * @see #setWidth(int)
6399      *
6400      * @attr ref android.R.styleable#TextView_ems
6401      */
6402     @android.view.RemotableViewMethod
setEms(int ems)6403     public void setEms(int ems) {
6404         mMaxWidth = mMinWidth = ems;
6405         mMaxWidthMode = mMinWidthMode = EMS;
6406 
6407         requestLayout();
6408         invalidate();
6409     }
6410 
6411     /**
6412      * Sets the width of the TextView to be exactly {@code pixels} wide.
6413      * <p>
6414      * This value is used for width calculation if LayoutParams does not force TextView to have an
6415      * exact width. Setting this value overrides previous minimum/maximum width configurations
6416      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
6417      *
6418      * @param pixels the exact width of the TextView in terms of pixels
6419      *
6420      * @see #setEms(int)
6421      *
6422      * @attr ref android.R.styleable#TextView_width
6423      */
6424     @android.view.RemotableViewMethod
setWidth(int pixels)6425     public void setWidth(int pixels) {
6426         mMaxWidth = mMinWidth = pixels;
6427         mMaxWidthMode = mMinWidthMode = PIXELS;
6428 
6429         requestLayout();
6430         invalidate();
6431     }
6432 
6433     /**
6434      * Sets line spacing for this TextView.  Each line other than the last line will have its height
6435      * multiplied by {@code mult} and have {@code add} added to it.
6436      *
6437      * @param add The value in pixels that should be added to each line other than the last line.
6438      *            This will be applied after the multiplier
6439      * @param mult The value by which each line height other than the last line will be multiplied
6440      *             by
6441      *
6442      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6443      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6444      */
setLineSpacing(float add, float mult)6445     public void setLineSpacing(float add, float mult) {
6446         if (mSpacingAdd != add || mSpacingMult != mult) {
6447             mSpacingAdd = add;
6448             mSpacingMult = mult;
6449 
6450             if (mLayout != null) {
6451                 nullLayouts();
6452                 requestLayout();
6453                 invalidate();
6454             }
6455         }
6456     }
6457 
6458     /**
6459      * Gets the line spacing multiplier
6460      *
6461      * @return the value by which each line's height is multiplied to get its actual height.
6462      *
6463      * @see #setLineSpacing(float, float)
6464      * @see #getLineSpacingExtra()
6465      *
6466      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
6467      */
6468     @InspectableProperty
getLineSpacingMultiplier()6469     public float getLineSpacingMultiplier() {
6470         return mSpacingMult;
6471     }
6472 
6473     /**
6474      * Gets the line spacing extra space
6475      *
6476      * @return the extra space that is added to the height of each lines of this TextView.
6477      *
6478      * @see #setLineSpacing(float, float)
6479      * @see #getLineSpacingMultiplier()
6480      *
6481      * @attr ref android.R.styleable#TextView_lineSpacingExtra
6482      */
6483     @InspectableProperty
getLineSpacingExtra()6484     public float getLineSpacingExtra() {
6485         return mSpacingAdd;
6486     }
6487 
6488     /**
6489      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
6490      * between subsequent baselines in the TextView.
6491      *
6492      * @param lineHeight the line height in pixels
6493      *
6494      * @see #setLineSpacing(float, float)
6495      * @see #getLineSpacingExtra()
6496      *
6497      * @attr ref android.R.styleable#TextView_lineHeight
6498      */
6499     @android.view.RemotableViewMethod
setLineHeight(@x @ntRangefrom = 0) int lineHeight)6500     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
6501         setLineHeightPx(lineHeight);
6502     }
6503 
setLineHeightPx(@x @loatRangefrom = 0) float lineHeight)6504     private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) {
6505         Preconditions.checkArgumentNonNegative(lineHeight,
6506                 "Expecting non-negative lineHeight while the input is " + lineHeight);
6507 
6508         final int fontHeight = getPaint().getFontMetricsInt(null);
6509         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
6510         // TODO(b/274974975): should this also check if lineSpacing needs to change?
6511         if (lineHeight != fontHeight) {
6512             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
6513             setLineSpacing(lineHeight - fontHeight, 1f);
6514 
6515             mLineHeightComplexDimen =
6516                         TypedValue.createComplexDimension(lineHeight, TypedValue.COMPLEX_UNIT_PX);
6517         }
6518     }
6519 
6520     /**
6521      * Sets an explicit line height to a given unit and value for this TextView. This is equivalent
6522      * to the vertical distance between subsequent baselines in the TextView. See {@link
6523      * TypedValue} for the possible dimension units.
6524      *
6525      * @param unit The desired dimension unit. SP units are strongly recommended so that line height
6526      *             stays proportional to the text size when fonts are scaled up for accessibility.
6527      * @param lineHeight The desired line height in the given units.
6528      *
6529      * @see #setLineSpacing(float, float)
6530      * @see #getLineSpacingExtra()
6531      *
6532      * @attr ref android.R.styleable#TextView_lineHeight
6533      */
6534     @android.view.RemotableViewMethod
setLineHeight( @ypedValue.ComplexDimensionUnit int unit, @FloatRange(from = 0) float lineHeight )6535     public void setLineHeight(
6536             @TypedValue.ComplexDimensionUnit int unit,
6537             @FloatRange(from = 0) float lineHeight
6538     ) {
6539         var metrics = getDisplayMetricsOrSystem();
6540         // We can avoid the recalculation if we know non-linear font scaling isn't being used
6541         // (an optimization for the majority case).
6542         // We also don't try to do the recalculation unless both textSize and lineHeight are in SP.
6543         if (!FontScaleConverterFactory.isNonLinearFontScalingActive(
6544                     getResources().getConfiguration().fontScale)
6545                 || unit != TypedValue.COMPLEX_UNIT_SP
6546                 || mTextSizeUnit != TypedValue.COMPLEX_UNIT_SP
6547         ) {
6548             setLineHeightPx(TypedValue.applyDimension(unit, lineHeight, metrics));
6549 
6550             // Do this last so it overwrites what setLineHeightPx() sets it to.
6551             mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
6552             return;
6553         }
6554 
6555         // Recalculate a proportional line height when non-linear font scaling is in effect.
6556         // Otherwise, a desired 2x line height at font scale 1.0 will not be 2x at font scale 2.0,
6557         // due to non-linear font scaling compressing higher SP sizes. See b/273326061 for details.
6558         // We know they are using SP units for both the text size and the line height
6559         // at this point, so determine the ratio between them. This is the *intended* line spacing
6560         // multiplier if font scale == 1.0. We can then determine what the pixel value for the line
6561         // height would be if we preserved proportions.
6562         var textSizePx = getTextSize();
6563         var textSizeSp = TypedValue.convertPixelsToDimension(
6564                 TypedValue.COMPLEX_UNIT_SP,
6565                 textSizePx,
6566                 metrics
6567         );
6568         var ratio = lineHeight / textSizeSp;
6569         setLineHeightPx(textSizePx * ratio);
6570 
6571         // Do this last so it overwrites what setLineHeightPx() sets it to.
6572         mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
6573     }
6574 
maybeRecalculateLineHeight()6575     private void maybeRecalculateLineHeight() {
6576         if (mLineHeightComplexDimen == 0) {
6577             return;
6578         }
6579         int unit = TypedValue.getUnitFromComplexDimension(mLineHeightComplexDimen);
6580         if (unit != TypedValue.COMPLEX_UNIT_SP) {
6581             // The lineHeight was never supplied in SP, so we didn't do any fancy recalculations
6582             // in setLineHeight(). We don't need to recalculate.
6583             return;
6584         }
6585 
6586         setLineHeight(unit, TypedValue.complexToFloat(mLineHeightComplexDimen));
6587     }
6588 
6589     /**
6590      * Set Highlights
6591      *
6592      * @param highlights A highlight object. Call with null for reset.
6593      *
6594      * @see #getHighlights()
6595      * @see Highlights
6596      */
setHighlights(@ullable Highlights highlights)6597     public void setHighlights(@Nullable Highlights highlights) {
6598         mHighlights = highlights;
6599         mHighlightPathsBogus = true;
6600         invalidate();
6601     }
6602 
6603     /**
6604      * Returns highlights
6605      *
6606      * @return a highlight to be drawn. null if no highlight was set.
6607      *
6608      * @see #setHighlights(Highlights)
6609      * @see Highlights
6610      *
6611      */
6612     @Nullable
getHighlights()6613     public Highlights getHighlights() {
6614         return mHighlights;
6615     }
6616 
6617     /**
6618      * Sets the search result ranges with flatten range representation.
6619      *
6620      * Ranges are represented of flattened inclusive start and exclusive end integers array. The
6621      * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array.
6622      * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the
6623      * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array
6624      * [1, 2, 3, 4].
6625      *
6626      * TextView will render the search result with the highlights with specified color in the theme.
6627      * If there is a focused search result, it is rendered with focused color. By calling this
6628      * method, the focused search index will be cleared.
6629      *
6630      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6631      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6632      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6633      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6634      *
6635      * @see #getSearchResultHighlights()
6636      * @see #setFocusedSearchResultIndex(int)
6637      * @see #getFocusedSearchResultIndex()
6638      * @see #setSearchResultHighlightColor(int)
6639      * @see #getSearchResultHighlightColor()
6640      * @see #setFocusedSearchResultHighlightColor(int)
6641      * @see #getFocusedSearchResultHighlightColor()
6642      *
6643      * @param ranges the flatten ranges of the search result. null for clear.
6644      */
setSearchResultHighlights(@ullable int... ranges)6645     public void setSearchResultHighlights(@Nullable int... ranges) {
6646         if (ranges == null) {
6647             mSearchResultHighlights = null;
6648             mHighlightPathsBogus = true;
6649             return;
6650         }
6651         if (ranges.length % 2 == 1) {
6652             throw new IllegalArgumentException(
6653                     "Flatten ranges must have even numbered elements");
6654         }
6655         for (int j = 0; j < ranges.length / 2; ++j) {
6656             int start = ranges[j * 2];
6657             int end = ranges[j * 2 + 1];
6658             if (start > end) {
6659                 throw new IllegalArgumentException(
6660                         "Reverse range found in the flatten range: " + start + ", " + end + ""
6661                                 + " at " + j + "-th range");
6662             }
6663         }
6664         mHighlightPathsBogus = true;
6665         mSearchResultHighlights = ranges;
6666         mFocusedSearchResultIndex = FOCUSED_SEARCH_RESULT_INDEX_NONE;
6667         invalidate();
6668     }
6669 
6670     /**
6671      * Gets the current search result ranges.
6672      *
6673      * @see #setSearchResultHighlights(int[])
6674      * @see #setFocusedSearchResultIndex(int)
6675      * @see #getFocusedSearchResultIndex()
6676      * @see #setSearchResultHighlightColor(int)
6677      * @see #getSearchResultHighlightColor()
6678      * @see #setFocusedSearchResultHighlightColor(int)
6679      * @see #getFocusedSearchResultHighlightColor()
6680      *
6681      * @return a flatten search result ranges. null if not available.
6682      */
6683     @Nullable
getSearchResultHighlights()6684     public int[] getSearchResultHighlights() {
6685         return mSearchResultHighlights;
6686     }
6687 
6688     /**
6689      * A special index used for {@link #setFocusedSearchResultIndex(int)} and
6690      * {@link #getFocusedSearchResultIndex()} inidicating there is no focused search result.
6691      */
6692     public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1;
6693 
6694     /**
6695      * Sets the focused search result index.
6696      *
6697      * The focused search result is drawn in a focused color.
6698      * Calling {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} for clearing focused search result.
6699      *
6700      * This method must be called after setting search result ranges by
6701      * {@link #setSearchResultHighlights(int[])}.
6702      *
6703      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6704      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6705      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6706      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6707      *
6708      * @see #setSearchResultHighlights(int[])
6709      * @see #getSearchResultHighlights()
6710      * @see #setFocusedSearchResultIndex(int)
6711      * @see #getFocusedSearchResultIndex()
6712      * @see #setSearchResultHighlightColor(int)
6713      * @see #getSearchResultHighlightColor()
6714      * @see #setFocusedSearchResultHighlightColor(int)
6715      * @see #getFocusedSearchResultHighlightColor()
6716      *
6717      * @param index a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
6718      */
setFocusedSearchResultIndex(int index)6719     public void setFocusedSearchResultIndex(int index) {
6720         if (mSearchResultHighlights == null) {
6721             throw new IllegalArgumentException("Search result range must be set beforehand.");
6722         }
6723         if (index < -1 || index >= mSearchResultHighlights.length / 2) {
6724             throw new IllegalArgumentException("Focused index(" + index + ") must be larger than "
6725                     + "-1 and less than range count(" + (mSearchResultHighlights.length / 2) + ")");
6726         }
6727         mFocusedSearchResultIndex = index;
6728         mHighlightPathsBogus = true;
6729         invalidate();
6730     }
6731 
6732     /**
6733      * Gets the focused search result index.
6734      *
6735      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6736      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6737      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6738      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6739      *
6740      * @see #setSearchResultHighlights(int[])
6741      * @see #getSearchResultHighlights()
6742      * @see #setFocusedSearchResultIndex(int)
6743      * @see #getFocusedSearchResultIndex()
6744      * @see #setSearchResultHighlightColor(int)
6745      * @see #getSearchResultHighlightColor()
6746      * @see #setFocusedSearchResultHighlightColor(int)
6747      * @see #getFocusedSearchResultHighlightColor()
6748 
6749      * @return a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
6750      */
getFocusedSearchResultIndex()6751     public int getFocusedSearchResultIndex() {
6752         return mFocusedSearchResultIndex;
6753     }
6754 
6755     /**
6756      * Sets the search result highlight color.
6757      *
6758      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6759      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6760      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6761      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6762      *
6763      * @see #setSearchResultHighlights(int[])
6764      * @see #getSearchResultHighlights()
6765      * @see #setFocusedSearchResultIndex(int)
6766      * @see #getFocusedSearchResultIndex()
6767      * @see #setSearchResultHighlightColor(int)
6768      * @see #getSearchResultHighlightColor()
6769      * @see #setFocusedSearchResultHighlightColor(int)
6770      * @see #getFocusedSearchResultHighlightColor()
6771 
6772      * @param color a search result highlight color.
6773      */
setSearchResultHighlightColor(@olorInt int color)6774     public void setSearchResultHighlightColor(@ColorInt int color) {
6775         mSearchResultHighlightColor = color;
6776     }
6777 
6778     /**
6779      * Gets the search result highlight color.
6780      *
6781      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6782      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6783      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6784      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6785      *
6786      * @see #setSearchResultHighlights(int[])
6787      * @see #getSearchResultHighlights()
6788      * @see #setFocusedSearchResultIndex(int)
6789      * @see #getFocusedSearchResultIndex()
6790      * @see #setSearchResultHighlightColor(int)
6791      * @see #getSearchResultHighlightColor()
6792      * @see #setFocusedSearchResultHighlightColor(int)
6793      * @see #getFocusedSearchResultHighlightColor()
6794 
6795      * @return a search result highlight color.
6796      */
6797     @ColorInt
getSearchResultHighlightColor()6798     public int getSearchResultHighlightColor() {
6799         return mSearchResultHighlightColor;
6800     }
6801 
6802     /**
6803      * Sets focused search result highlight color.
6804      *
6805      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6806      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6807      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6808      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6809      *
6810      * @see #setSearchResultHighlights(int[])
6811      * @see #getSearchResultHighlights()
6812      * @see #setFocusedSearchResultIndex(int)
6813      * @see #getFocusedSearchResultIndex()
6814      * @see #setSearchResultHighlightColor(int)
6815      * @see #getSearchResultHighlightColor()
6816      * @see #setFocusedSearchResultHighlightColor(int)
6817      * @see #getFocusedSearchResultHighlightColor()
6818 
6819      * @param color a focused search result highlight color.
6820      */
setFocusedSearchResultHighlightColor(@olorInt int color)6821     public void setFocusedSearchResultHighlightColor(@ColorInt int color) {
6822         mFocusedSearchResultHighlightColor = color;
6823     }
6824 
6825     /**
6826      * Gets focused search result highlight color.
6827      *
6828      * @attr ref android.R.styleable#TextView_searchResultHighlightColor
6829      * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
6830      * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
6831      * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
6832      *
6833      * @see #setSearchResultHighlights(int[])
6834      * @see #getSearchResultHighlights()
6835      * @see #setFocusedSearchResultIndex(int)
6836      * @see #getFocusedSearchResultIndex()
6837      * @see #setSearchResultHighlightColor(int)
6838      * @see #getSearchResultHighlightColor()
6839      * @see #setFocusedSearchResultHighlightColor(int)
6840      * @see #getFocusedSearchResultHighlightColor()
6841 
6842      * @return a focused search result highlight color.
6843      */
6844     @ColorInt
getFocusedSearchResultHighlightColor()6845     public int getFocusedSearchResultHighlightColor() {
6846         return mFocusedSearchResultHighlightColor;
6847     }
6848 
6849     /**
6850      * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
6851      * will be selected by the ongoing select handwriting gesture. While the gesture preview
6852      * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
6853      * the gesture preview highlight will be cleared.
6854      */
setSelectGesturePreviewHighlight(int start, int end)6855     private void setSelectGesturePreviewHighlight(int start, int end) {
6856         // Selection preview highlight color is the same as selection highlight color.
6857         setGesturePreviewHighlight(start, end, mHighlightColor);
6858     }
6859 
6860     /**
6861      * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
6862      * will be deleted by the ongoing delete handwriting gesture. While the gesture preview
6863      * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
6864      * the gesture preview highlight will be cleared.
6865      */
setDeleteGesturePreviewHighlight(int start, int end)6866     private void setDeleteGesturePreviewHighlight(int start, int end) {
6867         // Deletion preview highlight color is 20% opacity of the default text color.
6868         int color = mTextColor.getDefaultColor();
6869         color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
6870         setGesturePreviewHighlight(start, end, color);
6871     }
6872 
setGesturePreviewHighlight(int start, int end, int color)6873     private void setGesturePreviewHighlight(int start, int end, int color) {
6874         mGesturePreviewHighlightStart = start;
6875         mGesturePreviewHighlightEnd = end;
6876         if (mGesturePreviewHighlightPaint == null) {
6877             mGesturePreviewHighlightPaint = new Paint();
6878             mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL);
6879         }
6880         mGesturePreviewHighlightPaint.setColor(color);
6881 
6882         if (mEditor != null) {
6883             mEditor.hideCursorAndSpanControllers();
6884             mEditor.stopTextActionModeWithPreservingSelection();
6885         }
6886 
6887         mHighlightPathsBogus = true;
6888         invalidate();
6889     }
6890 
clearGesturePreviewHighlight()6891     private void clearGesturePreviewHighlight() {
6892         mGesturePreviewHighlightStart = -1;
6893         mGesturePreviewHighlightEnd = -1;
6894         mHighlightPathsBogus = true;
6895         invalidate();
6896     }
6897 
hasGesturePreviewHighlight()6898     boolean hasGesturePreviewHighlight() {
6899         return mGesturePreviewHighlightStart >= 0;
6900     }
6901 
6902     /**
6903      * Convenience method to append the specified text to the TextView's
6904      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6905      * if it was not already editable.
6906      *
6907      * @param text text to be appended to the already displayed text
6908      */
append(CharSequence text)6909     public final void append(CharSequence text) {
6910         append(text, 0, text.length());
6911     }
6912 
6913     /**
6914      * Convenience method to append the specified text slice to the TextView's
6915      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
6916      * if it was not already editable.
6917      *
6918      * @param text text to be appended to the already displayed text
6919      * @param start the index of the first character in the {@code text}
6920      * @param end the index of the character following the last character in the {@code text}
6921      *
6922      * @see Appendable#append(CharSequence, int, int)
6923      */
append(CharSequence text, int start, int end)6924     public void append(CharSequence text, int start, int end) {
6925         if (!(mText instanceof Editable)) {
6926             setText(mText, BufferType.EDITABLE);
6927         }
6928 
6929         ((Editable) mText).append(text, start, end);
6930 
6931         if (mAutoLinkMask != 0) {
6932             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
6933             // Do not change the movement method for text that support text selection as it
6934             // would prevent an arbitrary cursor displacement.
6935             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
6936                 setMovementMethod(LinkMovementMethod.getInstance());
6937             }
6938         }
6939     }
6940 
updateTextColors()6941     private void updateTextColors() {
6942         boolean inval = false;
6943         final int[] drawableState = getDrawableState();
6944         int color = mTextColor.getColorForState(drawableState, 0);
6945         if (color != mCurTextColor) {
6946             mCurTextColor = color;
6947             inval = true;
6948         }
6949         if (mLinkTextColor != null) {
6950             color = mLinkTextColor.getColorForState(drawableState, 0);
6951             if (color != mTextPaint.linkColor) {
6952                 mTextPaint.linkColor = color;
6953                 inval = true;
6954             }
6955         }
6956         if (mHintTextColor != null) {
6957             color = mHintTextColor.getColorForState(drawableState, 0);
6958             if (color != mCurHintTextColor) {
6959                 mCurHintTextColor = color;
6960                 if (mText.length() == 0) {
6961                     inval = true;
6962                 }
6963             }
6964         }
6965         if (inval) {
6966             // Text needs to be redrawn with the new color
6967             if (mEditor != null) mEditor.invalidateTextDisplayList();
6968             invalidate();
6969         }
6970     }
6971 
6972     @Override
drawableStateChanged()6973     protected void drawableStateChanged() {
6974         super.drawableStateChanged();
6975 
6976         if (mTextColor != null && mTextColor.isStateful()
6977                 || (mHintTextColor != null && mHintTextColor.isStateful())
6978                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
6979             updateTextColors();
6980         }
6981 
6982         if (mDrawables != null) {
6983             final int[] state = getDrawableState();
6984             for (Drawable dr : mDrawables.mShowing) {
6985                 if (dr != null && dr.isStateful() && dr.setState(state)) {
6986                     invalidateDrawable(dr);
6987                 }
6988             }
6989         }
6990     }
6991 
6992     @Override
drawableHotspotChanged(float x, float y)6993     public void drawableHotspotChanged(float x, float y) {
6994         super.drawableHotspotChanged(x, y);
6995 
6996         if (mDrawables != null) {
6997             for (Drawable dr : mDrawables.mShowing) {
6998                 if (dr != null) {
6999                     dr.setHotspot(x, y);
7000                 }
7001             }
7002         }
7003     }
7004 
7005     @Override
onSaveInstanceState()7006     public Parcelable onSaveInstanceState() {
7007         Parcelable superState = super.onSaveInstanceState();
7008 
7009         // Save state if we are forced to
7010         final boolean freezesText = getFreezesText();
7011         boolean hasSelection = false;
7012         int start = -1;
7013         int end = -1;
7014 
7015         if (mText != null) {
7016             start = getSelectionStart();
7017             end = getSelectionEnd();
7018             if (start >= 0 || end >= 0) {
7019                 // Or save state if there is a selection
7020                 hasSelection = true;
7021             }
7022         }
7023 
7024         if (freezesText || hasSelection) {
7025             SavedState ss = new SavedState(superState);
7026 
7027             if (freezesText) {
7028                 if (mText instanceof Spanned) {
7029                     final Spannable sp = new SpannableStringBuilder(mText);
7030 
7031                     if (mEditor != null) {
7032                         removeMisspelledSpans(sp);
7033                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
7034                     }
7035 
7036                     ss.text = sp;
7037                 } else {
7038                     ss.text = mText.toString();
7039                 }
7040             }
7041 
7042             if (hasSelection) {
7043                 // XXX Should also save the current scroll position!
7044                 ss.selStart = start;
7045                 ss.selEnd = end;
7046             }
7047 
7048             if (isFocused() && start >= 0 && end >= 0) {
7049                 ss.frozenWithFocus = true;
7050             }
7051 
7052             ss.error = getError();
7053 
7054             if (mEditor != null) {
7055                 ss.editorState = mEditor.saveInstanceState();
7056             }
7057             return ss;
7058         }
7059 
7060         return superState;
7061     }
7062 
removeMisspelledSpans(Spannable spannable)7063     void removeMisspelledSpans(Spannable spannable) {
7064         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
7065                 SuggestionSpan.class);
7066         for (int i = 0; i < suggestionSpans.length; i++) {
7067             int flags = suggestionSpans[i].getFlags();
7068             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
7069                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
7070                 spannable.removeSpan(suggestionSpans[i]);
7071             }
7072         }
7073     }
7074 
7075     @Override
onRestoreInstanceState(Parcelable state)7076     public void onRestoreInstanceState(Parcelable state) {
7077         if (!(state instanceof SavedState)) {
7078             super.onRestoreInstanceState(state);
7079             return;
7080         }
7081 
7082         SavedState ss = (SavedState) state;
7083         super.onRestoreInstanceState(ss.getSuperState());
7084 
7085         // XXX restore buffer type too, as well as lots of other stuff
7086         if (ss.text != null) {
7087             setText(ss.text);
7088         }
7089 
7090         if (ss.selStart >= 0 && ss.selEnd >= 0) {
7091             if (mSpannable != null) {
7092                 int len = mText.length();
7093 
7094                 if (ss.selStart > len || ss.selEnd > len) {
7095                     String restored = "";
7096 
7097                     if (ss.text != null) {
7098                         restored = "(restored) ";
7099                     }
7100 
7101                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
7102                             + " out of range for " + restored + "text " + mText);
7103                 } else {
7104                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
7105 
7106                     if (ss.frozenWithFocus) {
7107                         createEditorIfNeeded();
7108                         mEditor.mFrozenWithFocus = true;
7109                     }
7110                 }
7111             }
7112         }
7113 
7114         if (ss.error != null) {
7115             final CharSequence error = ss.error;
7116             // Display the error later, after the first layout pass
7117             post(new Runnable() {
7118                 public void run() {
7119                     if (mEditor == null || !mEditor.mErrorWasChanged) {
7120                         setError(error);
7121                     }
7122                 }
7123             });
7124         }
7125 
7126         if (ss.editorState != null) {
7127             createEditorIfNeeded();
7128             mEditor.restoreInstanceState(ss.editorState);
7129         }
7130     }
7131 
7132     /**
7133      * Control whether this text view saves its entire text contents when
7134      * freezing to an icicle, in addition to dynamic state such as cursor
7135      * position.  By default this is false, not saving the text.  Set to true
7136      * if the text in the text view is not being saved somewhere else in
7137      * persistent storage (such as in a content provider) so that if the
7138      * view is later thawed the user will not lose their data. For
7139      * {@link android.widget.EditText} it is always enabled, regardless of
7140      * the value of the attribute.
7141      *
7142      * @param freezesText Controls whether a frozen icicle should include the
7143      * entire text data: true to include it, false to not.
7144      *
7145      * @attr ref android.R.styleable#TextView_freezesText
7146      */
7147     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)7148     public void setFreezesText(boolean freezesText) {
7149         mFreezesText = freezesText;
7150     }
7151 
7152     /**
7153      * Return whether this text view is including its entire text contents
7154      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
7155      *
7156      * @return Returns true if text is included, false if it isn't.
7157      *
7158      * @see #setFreezesText
7159      */
7160     @InspectableProperty
getFreezesText()7161     public boolean getFreezesText() {
7162         return mFreezesText;
7163     }
7164 
7165     ///////////////////////////////////////////////////////////////////////////
7166 
7167     /**
7168      * Sets the Factory used to create new {@link Editable Editables}.
7169      *
7170      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
7171      *
7172      * @see android.text.Editable.Factory
7173      * @see android.widget.TextView.BufferType#EDITABLE
7174      */
setEditableFactory(Editable.Factory factory)7175     public final void setEditableFactory(Editable.Factory factory) {
7176         mEditableFactory = factory;
7177         setText(mText);
7178     }
7179 
7180     /**
7181      * Sets the Factory used to create new {@link Spannable Spannables}.
7182      *
7183      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
7184      *
7185      * @see android.text.Spannable.Factory
7186      * @see android.widget.TextView.BufferType#SPANNABLE
7187      */
setSpannableFactory(Spannable.Factory factory)7188     public final void setSpannableFactory(Spannable.Factory factory) {
7189         mSpannableFactory = factory;
7190         setText(mText);
7191     }
7192 
7193     /**
7194      * Sets the text to be displayed. TextView <em>does not</em> accept
7195      * HTML-like formatting, which you can do with text strings in XML resource files.
7196      * To style your strings, attach android.text.style.* objects to a
7197      * {@link android.text.SpannableString}, or see the
7198      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
7199      * Available Resource Types</a> documentation for an example of setting
7200      * formatted text in the XML resource file.
7201      * <p/>
7202      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7203      * intermediate {@link Spannable Spannables}. Likewise it will use
7204      * {@link android.text.Editable.Factory} to create final or intermediate
7205      * {@link Editable Editables}.
7206      *
7207      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
7208      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
7209      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
7210      *
7211      * @param text text to be displayed
7212      *
7213      * @attr ref android.R.styleable#TextView_text
7214      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
7215      *                                  parameters used to create the PrecomputedText mismatches
7216      *                                  with this TextView.
7217      */
7218     @android.view.RemotableViewMethod(asyncImpl = "setTextAsync")
setText(CharSequence text)7219     public final void setText(CharSequence text) {
7220         setText(text, mBufferType);
7221     }
7222 
7223     /**
7224      * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}.
7225      * This should be called on a background thread, and returns a Runnable which is then must be
7226      * called on the main thread to complete the operation and set text.
7227      * @param text text to be displayed
7228      * @return Runnable that sets text; must be called on the main thread by the caller of this
7229      * method to complete the operation
7230      * @hide
7231      */
7232     @NonNull
setTextAsync(@ullable CharSequence text)7233     public Runnable setTextAsync(@Nullable CharSequence text) {
7234         return () -> setText(text);
7235     }
7236 
7237     /**
7238      * Sets the text to be displayed but retains the cursor position. Same as
7239      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
7240      * new text.
7241      * <p/>
7242      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7243      * intermediate {@link Spannable Spannables}. Likewise it will use
7244      * {@link android.text.Editable.Factory} to create final or intermediate
7245      * {@link Editable Editables}.
7246      *
7247      * @param text text to be displayed
7248      *
7249      * @see #setText(CharSequence)
7250      */
7251     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)7252     public final void setTextKeepState(CharSequence text) {
7253         setTextKeepState(text, mBufferType);
7254     }
7255 
7256     /**
7257      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
7258      * <p/>
7259      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7260      * intermediate {@link Spannable Spannables}. Likewise it will use
7261      * {@link android.text.Editable.Factory} to create final or intermediate
7262      * {@link Editable Editables}.
7263      *
7264      * Subclasses overriding this method should ensure that the following post condition holds,
7265      * in order to guarantee the safety of the view's measurement and layout operations:
7266      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
7267      * will be different from {@code null}.
7268      *
7269      * @param text text to be displayed
7270      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7271      *              stored as a static text, styleable/spannable text, or editable text
7272      *
7273      * @see #setText(CharSequence)
7274      * @see android.widget.TextView.BufferType
7275      * @see #setSpannableFactory(Spannable.Factory)
7276      * @see #setEditableFactory(Editable.Factory)
7277      *
7278      * @attr ref android.R.styleable#TextView_text
7279      * @attr ref android.R.styleable#TextView_bufferType
7280      */
setText(CharSequence text, BufferType type)7281     public void setText(CharSequence text, BufferType type) {
7282         setText(text, type, true, 0);
7283 
7284         // drop any potential mCharWrappper leaks
7285         mCharWrapper = null;
7286     }
7287 
7288     @UnsupportedAppUsage
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)7289     private void setText(CharSequence text, BufferType type,
7290                          boolean notifyBefore, int oldlen) {
7291         if (mEditor != null) {
7292             mEditor.beforeSetText();
7293         }
7294         mTextSetFromXmlOrResourceId = false;
7295         if (text == null) {
7296             text = "";
7297         }
7298 
7299         // If suggestions are not enabled, remove the suggestion spans from the text
7300         if (!isSuggestionsEnabled()) {
7301             text = removeSuggestionSpans(text);
7302         }
7303 
7304         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
7305 
7306         if (text instanceof Spanned
7307                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
7308             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
7309                 setHorizontalFadingEdgeEnabled(true);
7310                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
7311             } else {
7312                 setHorizontalFadingEdgeEnabled(false);
7313                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7314             }
7315             setEllipsize(TextUtils.TruncateAt.MARQUEE);
7316         }
7317 
7318         int n = mFilters.length;
7319         for (int i = 0; i < n; i++) {
7320             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
7321             if (out != null) {
7322                 text = out;
7323             }
7324         }
7325 
7326         if (notifyBefore) {
7327             if (mText != null) {
7328                 oldlen = mText.length();
7329                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
7330             } else {
7331                 sendBeforeTextChanged("", 0, 0, text.length());
7332             }
7333         }
7334 
7335         boolean needEditableForNotification = false;
7336 
7337         if (mListeners != null && mListeners.size() != 0) {
7338             needEditableForNotification = true;
7339         }
7340 
7341         PrecomputedText precomputed =
7342                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
7343         if (type == BufferType.EDITABLE || getKeyListener() != null
7344                 || needEditableForNotification) {
7345             createEditorIfNeeded();
7346             mEditor.forgetUndoRedo();
7347             mEditor.scheduleRestartInputForSetText();
7348             Editable t = mEditableFactory.newEditable(text);
7349             text = t;
7350             setFilters(t, mFilters);
7351         } else if (precomputed != null) {
7352             if (mTextDir == null) {
7353                 mTextDir = getTextDirectionHeuristic();
7354             }
7355             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
7356                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
7357                             mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
7358                                     mLineBreakStyle, mLineBreakWordStyle));
7359             switch (checkResult) {
7360                 case PrecomputedText.Params.UNUSABLE:
7361                     throw new IllegalArgumentException(
7362                         "PrecomputedText's Parameters don't match the parameters of this TextView."
7363                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
7364                         + "to override the settings of this TextView: "
7365                         + "PrecomputedText: " + precomputed.getParams()
7366                         + "TextView: " + getTextMetricsParams());
7367                 case PrecomputedText.Params.NEED_RECOMPUTE:
7368                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
7369                     break;
7370                 case PrecomputedText.Params.USABLE:
7371                     // pass through
7372             }
7373         } else if (type == BufferType.SPANNABLE || mMovement != null) {
7374             text = mSpannableFactory.newSpannable(text);
7375         } else if (!(text instanceof CharWrapper)) {
7376             text = TextUtils.stringOrSpannedString(text);
7377         }
7378 
7379         @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE;
7380         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
7381             a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText);
7382         }
7383 
7384         if (mAutoLinkMask != 0) {
7385             Spannable s2;
7386 
7387             if (type == BufferType.EDITABLE || text instanceof Spannable) {
7388                 s2 = (Spannable) text;
7389             } else {
7390                 s2 = mSpannableFactory.newSpannable(text);
7391             }
7392 
7393             if (Linkify.addLinks(s2, mAutoLinkMask)) {
7394                 text = s2;
7395                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
7396 
7397                 /*
7398                  * We must go ahead and set the text before changing the
7399                  * movement method, because setMovementMethod() may call
7400                  * setText() again to try to upgrade the buffer type.
7401                  */
7402                 setTextInternal(text);
7403                 if (a11yTextChangeType == AccessibilityUtils.NONE) {
7404                     a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN;
7405                 }
7406 
7407                 // Do not change the movement method for text that support text selection as it
7408                 // would prevent an arbitrary cursor displacement.
7409                 if (mLinksClickable && !textCanBeSelected()) {
7410                     setMovementMethod(LinkMovementMethod.getInstance());
7411                 }
7412             }
7413         }
7414 
7415         mBufferType = type;
7416         setTextInternal(text);
7417 
7418         if (mTransformation == null) {
7419             mTransformed = text;
7420         } else {
7421             mTransformed = mTransformation.getTransformation(text, this);
7422         }
7423         if (mTransformed == null) {
7424             // Should not happen if the transformation method follows the non-null postcondition.
7425             mTransformed = "";
7426         }
7427 
7428         final int textLength = text.length();
7429         final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
7430 
7431         if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) {
7432             Spannable sp = (Spannable) text;
7433 
7434             // Remove any ChangeWatchers that might have come from other TextViews.
7435             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
7436             final int count = watchers.length;
7437             for (int i = 0; i < count; i++) {
7438                 sp.removeSpan(watchers[i]);
7439             }
7440 
7441             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
7442 
7443             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
7444                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
7445 
7446             if (mEditor != null) mEditor.addSpanWatchers(sp);
7447 
7448             if (mTransformation != null) {
7449                 final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
7450                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
7451                         | (priority << Spanned.SPAN_PRIORITY_SHIFT));
7452             }
7453 
7454             if (mMovement != null) {
7455                 mMovement.initialize(this, (Spannable) text);
7456 
7457                 /*
7458                  * Initializing the movement method will have set the
7459                  * selection, so reset mSelectionMoved to keep that from
7460                  * interfering with the normal on-focus selection-setting.
7461                  */
7462                 if (mEditor != null) mEditor.mSelectionMoved = false;
7463             }
7464         }
7465 
7466         if (mLayout != null) {
7467             checkForRelayout();
7468         }
7469 
7470         sendOnTextChanged(text, 0, oldlen, textLength);
7471         onTextChanged(text, 0, oldlen, textLength);
7472 
7473         mHideHint = false;
7474 
7475         if (a11yTextChangeType == AccessibilityUtils.TEXT) {
7476             notifyViewAccessibilityStateChangedIfNeeded(
7477                     AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
7478         } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) {
7479             notifyViewAccessibilityStateChangedIfNeeded(
7480                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7481         }
7482 
7483         if (needEditableForNotification) {
7484             sendAfterTextChanged((Editable) text);
7485         } else {
7486             notifyListeningManagersAfterTextChanged();
7487         }
7488 
7489         if (mEditor != null) {
7490             // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
7491             mEditor.prepareCursorControllers();
7492 
7493             mEditor.maybeFireScheduledRestartInputForSetText();
7494         }
7495     }
7496 
7497     /**
7498      * Sets the TextView to display the specified slice of the specified
7499      * char array. You must promise that you will not change the contents
7500      * of the array except for right before another call to setText(),
7501      * since the TextView has no way to know that the text
7502      * has changed and that it needs to invalidate and re-layout.
7503      *
7504      * @throws NullPointerException if text is null
7505      * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length
7506      *
7507      * @param text char array to be displayed
7508      * @param start start index in the char array
7509      * @param len length of char count after {@code start}
7510      */
setText(@onNull char[] text, int start, int len)7511     public final void setText(@NonNull char[] text, int start, int len) {
7512         int oldlen = 0;
7513 
7514         if (start < 0 || len < 0 || start + len > text.length) {
7515             throw new IndexOutOfBoundsException(start + ", " + len);
7516         }
7517 
7518         /*
7519          * We must do the before-notification here ourselves because if
7520          * the old text is a CharWrapper we destroy it before calling
7521          * into the normal path.
7522          */
7523         if (mText != null) {
7524             oldlen = mText.length();
7525             sendBeforeTextChanged(mText, 0, oldlen, len);
7526         } else {
7527             sendBeforeTextChanged("", 0, 0, len);
7528         }
7529 
7530         if (mCharWrapper == null) {
7531             mCharWrapper = new CharWrapper(text, start, len);
7532         } else {
7533             mCharWrapper.set(text, start, len);
7534         }
7535 
7536         setText(mCharWrapper, mBufferType, false, oldlen);
7537     }
7538 
7539     /**
7540      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
7541      * the cursor position. Same as
7542      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
7543      * position (if any) is retained in the new text.
7544      * <p/>
7545      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7546      * intermediate {@link Spannable Spannables}. Likewise it will use
7547      * {@link android.text.Editable.Factory} to create final or intermediate
7548      * {@link Editable Editables}.
7549      *
7550      * @param text text to be displayed
7551      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7552      *              stored as a static text, styleable/spannable text, or editable text
7553      *
7554      * @see #setText(CharSequence, android.widget.TextView.BufferType)
7555      */
setTextKeepState(CharSequence text, BufferType type)7556     public final void setTextKeepState(CharSequence text, BufferType type) {
7557         int start = getSelectionStart();
7558         int end = getSelectionEnd();
7559         int len = text.length();
7560 
7561         setText(text, type);
7562 
7563         if (start >= 0 || end >= 0) {
7564             if (mSpannable != null) {
7565                 Selection.setSelection(mSpannable,
7566                                        Math.max(0, Math.min(start, len)),
7567                                        Math.max(0, Math.min(end, len)));
7568             }
7569         }
7570     }
7571 
7572     /**
7573      * Sets the text to be displayed using a string resource identifier.
7574      *
7575      * @param resid the resource identifier of the string resource to be displayed
7576      *
7577      * @see #setText(CharSequence)
7578      *
7579      * @attr ref android.R.styleable#TextView_text
7580      */
7581     @android.view.RemotableViewMethod
setText(@tringRes int resid)7582     public final void setText(@StringRes int resid) {
7583         setText(getContext().getResources().getText(resid));
7584         mTextSetFromXmlOrResourceId = true;
7585         mTextId = resid;
7586     }
7587 
7588     /**
7589      * Sets the text to be displayed using a string resource identifier and the
7590      * {@link android.widget.TextView.BufferType}.
7591      * <p/>
7592      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
7593      * intermediate {@link Spannable Spannables}. Likewise it will use
7594      * {@link android.text.Editable.Factory} to create final or intermediate
7595      * {@link Editable Editables}.
7596      *
7597      * @param resid the resource identifier of the string resource to be displayed
7598      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
7599      *              stored as a static text, styleable/spannable text, or editable text
7600      *
7601      * @see #setText(int)
7602      * @see #setText(CharSequence)
7603      * @see android.widget.TextView.BufferType
7604      * @see #setSpannableFactory(Spannable.Factory)
7605      * @see #setEditableFactory(Editable.Factory)
7606      *
7607      * @attr ref android.R.styleable#TextView_text
7608      * @attr ref android.R.styleable#TextView_bufferType
7609      */
setText(@tringRes int resid, BufferType type)7610     public final void setText(@StringRes int resid, BufferType type) {
7611         setText(getContext().getResources().getText(resid), type);
7612         mTextSetFromXmlOrResourceId = true;
7613         mTextId = resid;
7614     }
7615 
7616     /**
7617      * Sets the text to be displayed when the text of the TextView is empty.
7618      * Null means to use the normal empty text. The hint does not currently
7619      * participate in determining the size of the view.
7620      *
7621      * @attr ref android.R.styleable#TextView_hint
7622      */
7623     @android.view.RemotableViewMethod
setHint(CharSequence hint)7624     public final void setHint(CharSequence hint) {
7625         setHintInternal(hint);
7626 
7627         if (mEditor != null && isInputMethodTarget()) {
7628             mEditor.reportExtractedText();
7629         }
7630     }
7631 
setHintInternal(CharSequence hint)7632     private void setHintInternal(CharSequence hint) {
7633         mHideHint = false;
7634         mHint = TextUtils.stringOrSpannedString(hint);
7635 
7636         if (mLayout != null) {
7637             checkForRelayout();
7638         }
7639 
7640         if (mText.length() == 0) {
7641             invalidate();
7642         }
7643 
7644         // Invalidate display list if hint is currently used
7645         if (mEditor != null && mText.length() == 0 && mHint != null) {
7646             mEditor.invalidateTextDisplayList();
7647         }
7648     }
7649 
7650     /**
7651      * Sets the text to be displayed when the text of the TextView is empty,
7652      * from a resource.
7653      *
7654      * @attr ref android.R.styleable#TextView_hint
7655      */
7656     @android.view.RemotableViewMethod
setHint(@tringRes int resid)7657     public final void setHint(@StringRes int resid) {
7658         mHintId = resid;
7659         setHint(getContext().getResources().getText(resid));
7660     }
7661 
7662     /**
7663      * Returns the hint that is displayed when the text of the TextView
7664      * is empty.
7665      *
7666      * @attr ref android.R.styleable#TextView_hint
7667      */
7668     @InspectableProperty
7669     @ViewDebug.CapturedViewProperty
getHint()7670     public CharSequence getHint() {
7671         return mHint;
7672     }
7673 
7674     /**
7675      * Temporarily hides the hint text until the text is modified, or the hint text is modified, or
7676      * the view gains or loses focus.
7677      *
7678      * @hide
7679      */
hideHint()7680     public void hideHint() {
7681         if (isShowingHint()) {
7682             mHideHint = true;
7683             invalidate();
7684         }
7685     }
7686 
7687     /**
7688      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
7689      * line characters instead of letting it wrap onto multiple lines.
7690      *
7691      * @attr ref android.R.styleable#TextView_singleLine
7692      */
7693     @InspectableProperty
isSingleLine()7694     public boolean isSingleLine() {
7695         return mSingleLine;
7696     }
7697 
isMultilineInputType(int type)7698     private static boolean isMultilineInputType(int type) {
7699         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
7700                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
7701     }
7702 
7703     /**
7704      * Removes the suggestion spans.
7705      */
removeSuggestionSpans(CharSequence text)7706     CharSequence removeSuggestionSpans(CharSequence text) {
7707         if (text instanceof Spanned) {
7708             Spannable spannable;
7709             if (text instanceof Spannable) {
7710                 spannable = (Spannable) text;
7711             } else {
7712                 spannable = mSpannableFactory.newSpannable(text);
7713             }
7714 
7715             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
7716             if (spans.length == 0) {
7717                 return text;
7718             } else {
7719                 text = spannable;
7720             }
7721 
7722             for (int i = 0; i < spans.length; i++) {
7723                 spannable.removeSpan(spans[i]);
7724             }
7725         }
7726         return text;
7727     }
7728 
7729     /**
7730      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
7731      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
7732      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
7733      * then a soft keyboard will not be displayed for this text view.
7734      *
7735      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
7736      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
7737      * type.
7738      *
7739      * @see #getInputType()
7740      * @see #setRawInputType(int)
7741      * @see android.text.InputType
7742      * @attr ref android.R.styleable#TextView_inputType
7743      */
setInputType(int type)7744     public void setInputType(int type) {
7745         final boolean wasPassword = isPasswordInputType(getInputType());
7746         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
7747         setInputType(type, false);
7748         final boolean isPassword = isPasswordInputType(type);
7749         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
7750         boolean forceUpdate = false;
7751         if (isPassword) {
7752             setTransformationMethod(PasswordTransformationMethod.getInstance());
7753             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
7754                     Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
7755         } else if (isVisiblePassword) {
7756             if (mTransformation == PasswordTransformationMethod.getInstance()) {
7757                 forceUpdate = true;
7758             }
7759             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
7760                     Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
7761         } else if (wasPassword || wasVisiblePassword) {
7762             // not in password mode, clean up typeface and transformation
7763             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
7764                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
7765                     FontStyle.FONT_WEIGHT_UNSPECIFIED);
7766             if (mTransformation == PasswordTransformationMethod.getInstance()) {
7767                 forceUpdate = true;
7768             }
7769         }
7770 
7771         boolean singleLine = !isMultilineInputType(type);
7772 
7773         // We need to update the single line mode if it has changed or we
7774         // were previously in password mode.
7775         if (mSingleLine != singleLine || forceUpdate) {
7776             // Change single line mode, but only change the transformation if
7777             // we are not in password mode.
7778             applySingleLine(singleLine, !isPassword, true, true);
7779         }
7780 
7781         if (!isSuggestionsEnabled()) {
7782             setTextInternal(removeSuggestionSpans(mText));
7783         }
7784 
7785         InputMethodManager imm = getInputMethodManager();
7786         if (imm != null) imm.restartInput(this);
7787     }
7788 
7789     /**
7790      * It would be better to rely on the input type for everything. A password inputType should have
7791      * a password transformation. We should hence use isPasswordInputType instead of this method.
7792      *
7793      * We should:
7794      * - Call setInputType in setKeyListener instead of changing the input type directly (which
7795      * would install the correct transformation).
7796      * - Refuse the installation of a non-password transformation in setTransformation if the input
7797      * type is password.
7798      *
7799      * However, this is like this for legacy reasons and we cannot break existing apps. This method
7800      * is useful since it matches what the user can see (obfuscated text or not).
7801      *
7802      * @return true if the current transformation method is of the password type.
7803      */
hasPasswordTransformationMethod()7804     boolean hasPasswordTransformationMethod() {
7805         return mTransformation instanceof PasswordTransformationMethod;
7806     }
7807 
7808     /**
7809      * Returns true if the current inputType is any type of password.
7810      *
7811      * @hide
7812      */
isAnyPasswordInputType()7813     public boolean isAnyPasswordInputType() {
7814         final int inputType = getInputType();
7815         return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
7816     }
7817 
isPasswordInputType(int inputType)7818     static boolean isPasswordInputType(int inputType) {
7819         final int variation =
7820                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
7821         return variation
7822                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
7823                 || variation
7824                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
7825                 || variation
7826                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
7827     }
7828 
isVisiblePasswordInputType(int inputType)7829     private static boolean isVisiblePasswordInputType(int inputType) {
7830         final int variation =
7831                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
7832         return variation
7833                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
7834     }
7835 
7836     /**
7837      * Directly change the content type integer of the text view, without
7838      * modifying any other state.
7839      * @see #setInputType(int)
7840      * @see android.text.InputType
7841      * @attr ref android.R.styleable#TextView_inputType
7842      */
setRawInputType(int type)7843     public void setRawInputType(int type) {
7844         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
7845         createEditorIfNeeded();
7846         mEditor.mInputType = type;
7847     }
7848 
7849     @Override
getAutofillHints()7850     public String[] getAutofillHints() {
7851         String[] hints = super.getAutofillHints();
7852         if (isAnyPasswordInputType()) {
7853             if (!ArrayUtils.contains(hints, AUTOFILL_HINT_PASSWORD_AUTO)) {
7854                 hints = ArrayUtils.appendElement(String.class, hints,
7855                         AUTOFILL_HINT_PASSWORD_AUTO);
7856             }
7857         }
7858         return hints;
7859     }
7860 
7861     /**
7862      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
7863      *         a {@code Locale} object that can be used to customize key various listeners.
7864      * @see DateKeyListener#getInstance(Locale)
7865      * @see DateTimeKeyListener#getInstance(Locale)
7866      * @see DigitsKeyListener#getInstance(Locale)
7867      * @see TimeKeyListener#getInstance(Locale)
7868      */
7869     @Nullable
getCustomLocaleForKeyListenerOrNull()7870     private Locale getCustomLocaleForKeyListenerOrNull() {
7871         if (!mUseInternationalizedInput) {
7872             // If the application does not target O, stick to the previous behavior.
7873             return null;
7874         }
7875         final LocaleList locales = getImeHintLocales();
7876         if (locales == null) {
7877             // If the application does not explicitly specify IME hint locale, also stick to the
7878             // previous behavior.
7879             return null;
7880         }
7881         return locales.get(0);
7882     }
7883 
7884     @UnsupportedAppUsage
setInputType(int type, boolean direct)7885     private void setInputType(int type, boolean direct) {
7886         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
7887         KeyListener input;
7888         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
7889             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
7890             TextKeyListener.Capitalize cap;
7891             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
7892                 cap = TextKeyListener.Capitalize.CHARACTERS;
7893             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
7894                 cap = TextKeyListener.Capitalize.WORDS;
7895             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
7896                 cap = TextKeyListener.Capitalize.SENTENCES;
7897             } else {
7898                 cap = TextKeyListener.Capitalize.NONE;
7899             }
7900             input = TextKeyListener.getInstance(autotext, cap);
7901         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
7902             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7903             input = DigitsKeyListener.getInstance(
7904                     locale,
7905                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
7906                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
7907             if (locale != null) {
7908                 // Override type, if necessary for i18n.
7909                 int newType = input.getInputType();
7910                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
7911                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
7912                     // The class is different from the original class. So we need to override
7913                     // 'type'. But we want to keep the password flag if it's there.
7914                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
7915                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
7916                     }
7917                     type = newType;
7918                 }
7919             }
7920         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
7921             final Locale locale = getCustomLocaleForKeyListenerOrNull();
7922             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
7923                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
7924                     input = DateKeyListener.getInstance(locale);
7925                     break;
7926                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
7927                     input = TimeKeyListener.getInstance(locale);
7928                     break;
7929                 default:
7930                     input = DateTimeKeyListener.getInstance(locale);
7931                     break;
7932             }
7933             if (mUseInternationalizedInput) {
7934                 type = input.getInputType(); // Override type, if necessary for i18n.
7935             }
7936         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
7937             input = DialerKeyListener.getInstance();
7938         } else {
7939             input = TextKeyListener.getInstance();
7940         }
7941         setRawInputType(type);
7942         mListenerChanged = false;
7943         if (direct) {
7944             createEditorIfNeeded();
7945             mEditor.mKeyListener = input;
7946         } else {
7947             setKeyListenerOnly(input);
7948         }
7949     }
7950 
7951     /**
7952      * Get the type of the editable content.
7953      *
7954      * @see #setInputType(int)
7955      * @see android.text.InputType
7956      */
7957     @InspectableProperty(flagMapping = {
7958             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
7959             @FlagEntry(
7960                     name = "text",
7961                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7962                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
7963             @FlagEntry(
7964                     name = "textUri",
7965                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7966                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
7967             @FlagEntry(
7968                     name = "textEmailAddress",
7969                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7970                     target = InputType.TYPE_CLASS_TEXT
7971                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
7972             @FlagEntry(
7973                     name = "textEmailSubject",
7974                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7975                     target = InputType.TYPE_CLASS_TEXT
7976                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
7977             @FlagEntry(
7978                     name = "textShortMessage",
7979                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7980                     target = InputType.TYPE_CLASS_TEXT
7981                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
7982             @FlagEntry(
7983                     name = "textLongMessage",
7984                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7985                     target = InputType.TYPE_CLASS_TEXT
7986                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
7987             @FlagEntry(
7988                     name = "textPersonName",
7989                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7990                     target = InputType.TYPE_CLASS_TEXT
7991                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
7992             @FlagEntry(
7993                     name = "textPostalAddress",
7994                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
7995                     target = InputType.TYPE_CLASS_TEXT
7996                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
7997             @FlagEntry(
7998                     name = "textPassword",
7999                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8000                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
8001             @FlagEntry(
8002                     name = "textVisiblePassword",
8003                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8004                     target = InputType.TYPE_CLASS_TEXT
8005                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
8006             @FlagEntry(
8007                     name = "textWebEditText",
8008                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8009                     target = InputType.TYPE_CLASS_TEXT
8010                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
8011             @FlagEntry(
8012                     name = "textFilter",
8013                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8014                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
8015             @FlagEntry(
8016                     name = "textPhonetic",
8017                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8018                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
8019             @FlagEntry(
8020                     name = "textWebEmailAddress",
8021                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8022                     target = InputType.TYPE_CLASS_TEXT
8023                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
8024             @FlagEntry(
8025                     name = "textWebPassword",
8026                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8027                     target = InputType.TYPE_CLASS_TEXT
8028                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
8029             @FlagEntry(
8030                     name = "number",
8031                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8032                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
8033             @FlagEntry(
8034                     name = "numberPassword",
8035                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8036                     target = InputType.TYPE_CLASS_NUMBER
8037                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
8038             @FlagEntry(
8039                     name = "phone",
8040                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8041                     target = InputType.TYPE_CLASS_PHONE),
8042             @FlagEntry(
8043                     name = "datetime",
8044                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8045                     target = InputType.TYPE_CLASS_DATETIME
8046                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
8047             @FlagEntry(
8048                     name = "date",
8049                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8050                     target = InputType.TYPE_CLASS_DATETIME
8051                             | InputType.TYPE_DATETIME_VARIATION_DATE),
8052             @FlagEntry(
8053                     name = "time",
8054                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
8055                     target = InputType.TYPE_CLASS_DATETIME
8056                             | InputType.TYPE_DATETIME_VARIATION_TIME),
8057             @FlagEntry(
8058                     name = "textCapCharacters",
8059                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8060                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
8061             @FlagEntry(
8062                     name = "textCapWords",
8063                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8064                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
8065             @FlagEntry(
8066                     name = "textCapSentences",
8067                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8068                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
8069             @FlagEntry(
8070                     name = "textAutoCorrect",
8071                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8072                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
8073             @FlagEntry(
8074                     name = "textAutoComplete",
8075                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8076                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
8077             @FlagEntry(
8078                     name = "textMultiLine",
8079                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8080                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
8081             @FlagEntry(
8082                     name = "textImeMultiLine",
8083                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8084                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
8085             @FlagEntry(
8086                     name = "textNoSuggestions",
8087                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8088                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
8089             @FlagEntry(
8090                     name = "numberSigned",
8091                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8092                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
8093             @FlagEntry(
8094                     name = "numberDecimal",
8095                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
8096                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
8097     })
getInputType()8098     public int getInputType() {
8099         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
8100     }
8101 
8102     /**
8103      * Change the editor type integer associated with the text view, which
8104      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
8105      * when it has focus.
8106      * @see #getImeOptions
8107      * @see android.view.inputmethod.EditorInfo
8108      * @attr ref android.R.styleable#TextView_imeOptions
8109      */
setImeOptions(int imeOptions)8110     public void setImeOptions(int imeOptions) {
8111         createEditorIfNeeded();
8112         mEditor.createInputContentTypeIfNeeded();
8113         mEditor.mInputContentType.imeOptions = imeOptions;
8114     }
8115 
8116     /**
8117      * Get the type of the Input Method Editor (IME).
8118      * @return the type of the IME
8119      * @see #setImeOptions(int)
8120      * @see EditorInfo
8121      */
8122     @InspectableProperty(flagMapping = {
8123             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
8124             @FlagEntry(
8125                     name = "actionUnspecified",
8126                     mask = EditorInfo.IME_MASK_ACTION,
8127                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
8128             @FlagEntry(
8129                     name = "actionNone",
8130                     mask = EditorInfo.IME_MASK_ACTION,
8131                     target = EditorInfo.IME_ACTION_NONE),
8132             @FlagEntry(
8133                     name = "actionGo",
8134                     mask = EditorInfo.IME_MASK_ACTION,
8135                     target = EditorInfo.IME_ACTION_GO),
8136             @FlagEntry(
8137                     name = "actionSearch",
8138                     mask = EditorInfo.IME_MASK_ACTION,
8139                     target = EditorInfo.IME_ACTION_SEARCH),
8140             @FlagEntry(
8141                     name = "actionSend",
8142                     mask = EditorInfo.IME_MASK_ACTION,
8143                     target = EditorInfo.IME_ACTION_SEND),
8144             @FlagEntry(
8145                     name = "actionNext",
8146                     mask = EditorInfo.IME_MASK_ACTION,
8147                     target = EditorInfo.IME_ACTION_NEXT),
8148             @FlagEntry(
8149                     name = "actionDone",
8150                     mask = EditorInfo.IME_MASK_ACTION,
8151                     target = EditorInfo.IME_ACTION_DONE),
8152             @FlagEntry(
8153                     name = "actionPrevious",
8154                     mask = EditorInfo.IME_MASK_ACTION,
8155                     target = EditorInfo.IME_ACTION_PREVIOUS),
8156             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
8157             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
8158             @FlagEntry(
8159                     name = "flagNavigatePrevious",
8160                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
8161             @FlagEntry(
8162                     name = "flagNoAccessoryAction",
8163                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
8164             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
8165             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
8166             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
8167             @FlagEntry(
8168                     name = "flagNoPersonalizedLearning",
8169                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
8170     })
getImeOptions()8171     public int getImeOptions() {
8172         return mEditor != null && mEditor.mInputContentType != null
8173                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
8174     }
8175 
8176     /**
8177      * Change the custom IME action associated with the text view, which
8178      * will be reported to an IME with {@link EditorInfo#actionLabel}
8179      * and {@link EditorInfo#actionId} when it has focus.
8180      * @see #getImeActionLabel
8181      * @see #getImeActionId
8182      * @see android.view.inputmethod.EditorInfo
8183      * @attr ref android.R.styleable#TextView_imeActionLabel
8184      * @attr ref android.R.styleable#TextView_imeActionId
8185      */
setImeActionLabel(CharSequence label, int actionId)8186     public void setImeActionLabel(CharSequence label, int actionId) {
8187         createEditorIfNeeded();
8188         mEditor.createInputContentTypeIfNeeded();
8189         mEditor.mInputContentType.imeActionLabel = label;
8190         mEditor.mInputContentType.imeActionId = actionId;
8191     }
8192 
8193     /**
8194      * Get the IME action label previous set with {@link #setImeActionLabel}.
8195      *
8196      * @see #setImeActionLabel
8197      * @see android.view.inputmethod.EditorInfo
8198      */
8199     @InspectableProperty
getImeActionLabel()8200     public CharSequence getImeActionLabel() {
8201         return mEditor != null && mEditor.mInputContentType != null
8202                 ? mEditor.mInputContentType.imeActionLabel : null;
8203     }
8204 
8205     /**
8206      * Get the IME action ID previous set with {@link #setImeActionLabel}.
8207      *
8208      * @see #setImeActionLabel
8209      * @see android.view.inputmethod.EditorInfo
8210      */
8211     @InspectableProperty
getImeActionId()8212     public int getImeActionId() {
8213         return mEditor != null && mEditor.mInputContentType != null
8214                 ? mEditor.mInputContentType.imeActionId : 0;
8215     }
8216 
8217     /**
8218      * Set a special listener to be called when an action is performed
8219      * on the text view.  This will be called when the enter key is pressed,
8220      * or when an action supplied to the IME is selected by the user.  Setting
8221      * this means that the normal hard key event will not insert a newline
8222      * into the text view, even if it is multi-line; holding down the ALT
8223      * modifier will, however, allow the user to insert a newline character.
8224      */
setOnEditorActionListener(OnEditorActionListener l)8225     public void setOnEditorActionListener(OnEditorActionListener l) {
8226         createEditorIfNeeded();
8227         mEditor.createInputContentTypeIfNeeded();
8228         mEditor.mInputContentType.onEditorActionListener = l;
8229     }
8230 
8231     /**
8232      * Called when an attached input method calls
8233      * {@link InputConnection#performEditorAction(int)
8234      * InputConnection.performEditorAction()}
8235      * for this text view.  The default implementation will call your action
8236      * listener supplied to {@link #setOnEditorActionListener}, or perform
8237      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
8238      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
8239      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
8240      * EditorInfo.IME_ACTION_DONE}.
8241      *
8242      * <p>For backwards compatibility, if no IME options have been set and the
8243      * text view would not normally advance focus on enter, then
8244      * the NEXT and DONE actions received here will be turned into an enter
8245      * key down/up pair to go through the normal key handling.
8246      *
8247      * @param actionCode The code of the action being performed.
8248      *
8249      * @see #setOnEditorActionListener
8250      */
onEditorAction(int actionCode)8251     public void onEditorAction(int actionCode) {
8252         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
8253         if (ict != null) {
8254             if (ict.onEditorActionListener != null) {
8255                 if (ict.onEditorActionListener.onEditorAction(this,
8256                         actionCode, null)) {
8257                     return;
8258                 }
8259             }
8260 
8261             // This is the handling for some default action.
8262             // Note that for backwards compatibility we don't do this
8263             // default handling if explicit ime options have not been given,
8264             // instead turning this into the normal enter key codes that an
8265             // app may be expecting.
8266             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
8267                 View v = focusSearch(FOCUS_FORWARD);
8268                 if (v != null) {
8269                     if (!v.requestFocus(FOCUS_FORWARD)) {
8270                         throw new IllegalStateException("focus search returned a view "
8271                                 + "that wasn't able to take focus!");
8272                     }
8273                 }
8274                 return;
8275 
8276             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
8277                 View v = focusSearch(FOCUS_BACKWARD);
8278                 if (v != null) {
8279                     if (!v.requestFocus(FOCUS_BACKWARD)) {
8280                         throw new IllegalStateException("focus search returned a view "
8281                                 + "that wasn't able to take focus!");
8282                     }
8283                 }
8284                 return;
8285 
8286             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
8287                 InputMethodManager imm = getInputMethodManager();
8288                 if (imm != null) {
8289                     imm.hideSoftInputFromView(this, 0);
8290                 }
8291                 return;
8292             }
8293         }
8294 
8295         ViewRootImpl viewRootImpl = getViewRootImpl();
8296         if (viewRootImpl != null) {
8297             long eventTime = SystemClock.uptimeMillis();
8298             viewRootImpl.dispatchKeyFromIme(
8299                     new KeyEvent(eventTime, eventTime,
8300                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
8301                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
8302                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
8303                     | KeyEvent.FLAG_EDITOR_ACTION));
8304             viewRootImpl.dispatchKeyFromIme(
8305                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
8306                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
8307                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
8308                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
8309                     | KeyEvent.FLAG_EDITOR_ACTION));
8310         }
8311     }
8312 
8313     /**
8314      * Set the private content type of the text, which is the
8315      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
8316      * field that will be filled in when creating an input connection.
8317      *
8318      * @see #getPrivateImeOptions()
8319      * @see EditorInfo#privateImeOptions
8320      * @attr ref android.R.styleable#TextView_privateImeOptions
8321      */
setPrivateImeOptions(String type)8322     public void setPrivateImeOptions(String type) {
8323         createEditorIfNeeded();
8324         mEditor.createInputContentTypeIfNeeded();
8325         mEditor.mInputContentType.privateImeOptions = type;
8326     }
8327 
8328     /**
8329      * Get the private type of the content.
8330      *
8331      * @see #setPrivateImeOptions(String)
8332      * @see EditorInfo#privateImeOptions
8333      */
8334     @InspectableProperty
getPrivateImeOptions()8335     public String getPrivateImeOptions() {
8336         return mEditor != null && mEditor.mInputContentType != null
8337                 ? mEditor.mInputContentType.privateImeOptions : null;
8338     }
8339 
8340     /**
8341      * Set the extra input data of the text, which is the
8342      * {@link EditorInfo#extras TextBoxAttribute.extras}
8343      * Bundle that will be filled in when creating an input connection.  The
8344      * given integer is the resource identifier of an XML resource holding an
8345      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
8346      *
8347      * @see #getInputExtras(boolean)
8348      * @see EditorInfo#extras
8349      * @attr ref android.R.styleable#TextView_editorExtras
8350      */
setInputExtras(@mlRes int xmlResId)8351     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
8352         createEditorIfNeeded();
8353         XmlResourceParser parser = getResources().getXml(xmlResId);
8354         mEditor.createInputContentTypeIfNeeded();
8355         mEditor.mInputContentType.extras = new Bundle();
8356         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
8357     }
8358 
8359     /**
8360      * Retrieve the input extras currently associated with the text view, which
8361      * can be viewed as well as modified.
8362      *
8363      * @param create If true, the extras will be created if they don't already
8364      * exist.  Otherwise, null will be returned if none have been created.
8365      * @see #setInputExtras(int)
8366      * @see EditorInfo#extras
8367      * @attr ref android.R.styleable#TextView_editorExtras
8368      */
getInputExtras(boolean create)8369     public Bundle getInputExtras(boolean create) {
8370         if (mEditor == null && !create) return null;
8371         createEditorIfNeeded();
8372         if (mEditor.mInputContentType == null) {
8373             if (!create) return null;
8374             mEditor.createInputContentTypeIfNeeded();
8375         }
8376         if (mEditor.mInputContentType.extras == null) {
8377             if (!create) return null;
8378             mEditor.mInputContentType.extras = new Bundle();
8379         }
8380         return mEditor.mInputContentType.extras;
8381     }
8382 
8383     /**
8384      * Change "hint" locales associated with the text view, which will be reported to an IME with
8385      * {@link EditorInfo#hintLocales} when it has focus.
8386      *
8387      * Starting with Android O, this also causes internationalized listeners to be created (or
8388      * change locale) based on the first locale in the input locale list.
8389      *
8390      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
8391      * call {@link InputMethodManager#restartInput(View)}.</p>
8392      * @param hintLocales List of the languages that the user is supposed to switch to no matter
8393      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
8394      * @see #getImeHintLocales()
8395      * @see android.view.inputmethod.EditorInfo#hintLocales
8396      */
setImeHintLocales(@ullable LocaleList hintLocales)8397     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
8398         createEditorIfNeeded();
8399         mEditor.createInputContentTypeIfNeeded();
8400         mEditor.mInputContentType.imeHintLocales = hintLocales;
8401         if (mUseInternationalizedInput) {
8402             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
8403         }
8404     }
8405 
8406     /**
8407      * @return The current languages list "hint". {@code null} when no "hint" is available.
8408      * @see #setImeHintLocales(LocaleList)
8409      * @see android.view.inputmethod.EditorInfo#hintLocales
8410      */
8411     @Nullable
getImeHintLocales()8412     public LocaleList getImeHintLocales() {
8413         if (mEditor == null) {
8414             return null;
8415         }
8416         if (mEditor.mInputContentType == null) {
8417             return null;
8418         }
8419         return mEditor.mInputContentType.imeHintLocales;
8420     }
8421 
8422     /**
8423      * Returns the error message that was set to be displayed with
8424      * {@link #setError}, or <code>null</code> if no error was set
8425      * or if it the error was cleared by the widget after user input.
8426      */
getError()8427     public CharSequence getError() {
8428         return mEditor == null ? null : mEditor.mError;
8429     }
8430 
8431     /**
8432      * Sets the right-hand compound drawable of the TextView to the "error"
8433      * icon and sets an error message that will be displayed in a popup when
8434      * the TextView has focus.  The icon and error message will be reset to
8435      * null when any key events cause changes to the TextView's text.  If the
8436      * <code>error</code> is <code>null</code>, the error message and icon
8437      * will be cleared.
8438      */
8439     @android.view.RemotableViewMethod
setError(CharSequence error)8440     public void setError(CharSequence error) {
8441         if (error == null) {
8442             setError(null, null);
8443         } else {
8444             Drawable dr = getContext().getDrawable(
8445                     com.android.internal.R.drawable.indicator_input_error);
8446 
8447             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
8448             setError(error, dr);
8449         }
8450     }
8451 
8452     /**
8453      * Sets the right-hand compound drawable of the TextView to the specified
8454      * icon and sets an error message that will be displayed in a popup when
8455      * the TextView has focus.  The icon and error message will be reset to
8456      * null when any key events cause changes to the TextView's text.  The
8457      * drawable must already have had {@link Drawable#setBounds} set on it.
8458      * If the <code>error</code> is <code>null</code>, the error message will
8459      * be cleared (and you should provide a <code>null</code> icon as well).
8460      */
setError(CharSequence error, Drawable icon)8461     public void setError(CharSequence error, Drawable icon) {
8462         createEditorIfNeeded();
8463         mEditor.setError(error, icon);
8464         notifyViewAccessibilityStateChangedIfNeeded(
8465                 AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR
8466                         | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID);
8467     }
8468 
8469     @Override
setFrame(int l, int t, int r, int b)8470     protected boolean setFrame(int l, int t, int r, int b) {
8471         boolean result = super.setFrame(l, t, r, b);
8472 
8473         if (mEditor != null) mEditor.setFrame();
8474 
8475         restartMarqueeIfNeeded();
8476 
8477         return result;
8478     }
8479 
restartMarqueeIfNeeded()8480     private void restartMarqueeIfNeeded() {
8481         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8482             mRestartMarquee = false;
8483             startMarquee();
8484         }
8485     }
8486 
8487     /**
8488      * Sets the list of input filters that will be used if the buffer is
8489      * Editable. Has no effect otherwise.
8490      *
8491      * @attr ref android.R.styleable#TextView_maxLength
8492      */
setFilters(InputFilter[] filters)8493     public void setFilters(InputFilter[] filters) {
8494         if (filters == null) {
8495             throw new IllegalArgumentException();
8496         }
8497 
8498         mFilters = filters;
8499 
8500         if (mText instanceof Editable) {
8501             setFilters((Editable) mText, filters);
8502         }
8503     }
8504 
8505     /**
8506      * Sets the list of input filters on the specified Editable,
8507      * and includes mInput in the list if it is an InputFilter.
8508      */
setFilters(Editable e, InputFilter[] filters)8509     private void setFilters(Editable e, InputFilter[] filters) {
8510         if (mEditor != null) {
8511             final boolean undoFilter = mEditor.mUndoInputFilter != null;
8512             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
8513             int num = 0;
8514             if (undoFilter) num++;
8515             if (keyFilter) num++;
8516             if (num > 0) {
8517                 InputFilter[] nf = new InputFilter[filters.length + num];
8518 
8519                 System.arraycopy(filters, 0, nf, 0, filters.length);
8520                 num = 0;
8521                 if (undoFilter) {
8522                     nf[filters.length] = mEditor.mUndoInputFilter;
8523                     num++;
8524                 }
8525                 if (keyFilter) {
8526                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
8527                 }
8528 
8529                 e.setFilters(nf);
8530                 return;
8531             }
8532         }
8533         e.setFilters(filters);
8534     }
8535 
8536     /**
8537      * Returns the current list of input filters.
8538      *
8539      * @attr ref android.R.styleable#TextView_maxLength
8540      */
getFilters()8541     public InputFilter[] getFilters() {
8542         return mFilters;
8543     }
8544 
8545     /////////////////////////////////////////////////////////////////////////
8546 
getBoxHeight(Layout l)8547     private int getBoxHeight(Layout l) {
8548         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
8549         int padding = (l == mHintLayout)
8550                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
8551                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
8552         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
8553     }
8554 
8555     @UnsupportedAppUsage
getVerticalOffset(boolean forceNormal)8556     int getVerticalOffset(boolean forceNormal) {
8557         int voffset = 0;
8558         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
8559 
8560         Layout l = mLayout;
8561         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
8562             l = mHintLayout;
8563         }
8564 
8565         if (gravity != Gravity.TOP) {
8566             int boxht = getBoxHeight(l);
8567             int textht = l.getHeight();
8568 
8569             if (textht < boxht) {
8570                 if (gravity == Gravity.BOTTOM) {
8571                     voffset = boxht - textht;
8572                 } else { // (gravity == Gravity.CENTER_VERTICAL)
8573                     voffset = (boxht - textht) >> 1;
8574                 }
8575             }
8576         }
8577         return voffset;
8578     }
8579 
getBottomVerticalOffset(boolean forceNormal)8580     private int getBottomVerticalOffset(boolean forceNormal) {
8581         int voffset = 0;
8582         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
8583 
8584         Layout l = mLayout;
8585         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
8586             l = mHintLayout;
8587         }
8588 
8589         if (gravity != Gravity.BOTTOM) {
8590             int boxht = getBoxHeight(l);
8591             int textht = l.getHeight();
8592 
8593             if (textht < boxht) {
8594                 if (gravity == Gravity.TOP) {
8595                     voffset = boxht - textht;
8596                 } else { // (gravity == Gravity.CENTER_VERTICAL)
8597                     voffset = (boxht - textht) >> 1;
8598                 }
8599             }
8600         }
8601         return voffset;
8602     }
8603 
invalidateCursorPath()8604     void invalidateCursorPath() {
8605         if (mHighlightPathBogus) {
8606             invalidateCursor();
8607         } else {
8608             final int horizontalPadding = getCompoundPaddingLeft();
8609             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
8610 
8611             if (mEditor.mDrawableForCursor == null) {
8612                 synchronized (TEMP_RECTF) {
8613                     /*
8614                      * The reason for this concern about the thickness of the
8615                      * cursor and doing the floor/ceil on the coordinates is that
8616                      * some EditTexts (notably textfields in the Browser) have
8617                      * anti-aliased text where not all the characters are
8618                      * necessarily at integer-multiple locations.  This should
8619                      * make sure the entire cursor gets invalidated instead of
8620                      * sometimes missing half a pixel.
8621                      */
8622                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
8623                     if (thick < 1.0f) {
8624                         thick = 1.0f;
8625                     }
8626 
8627                     thick /= 2.0f;
8628 
8629                     // mHighlightPath is guaranteed to be non null at that point.
8630                     mHighlightPath.computeBounds(TEMP_RECTF, false);
8631 
8632                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
8633                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
8634                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
8635                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
8636                 }
8637             } else {
8638                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
8639                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
8640                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
8641             }
8642         }
8643     }
8644 
invalidateCursor()8645     void invalidateCursor() {
8646         int where = getSelectionEnd();
8647 
8648         invalidateCursor(where, where, where);
8649     }
8650 
invalidateCursor(int a, int b, int c)8651     private void invalidateCursor(int a, int b, int c) {
8652         if (a >= 0 || b >= 0 || c >= 0) {
8653             int start = Math.min(Math.min(a, b), c);
8654             int end = Math.max(Math.max(a, b), c);
8655             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
8656         }
8657     }
8658 
8659     /**
8660      * Invalidates the region of text enclosed between the start and end text offsets.
8661      */
invalidateRegion(int start, int end, boolean invalidateCursor)8662     void invalidateRegion(int start, int end, boolean invalidateCursor) {
8663         if (mLayout == null) {
8664             invalidate();
8665         } else {
8666             start = originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
8667             end = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
8668             int lineStart = mLayout.getLineForOffset(start);
8669             int top = mLayout.getLineTop(lineStart);
8670 
8671             // This is ridiculous, but the descent from the line above
8672             // can hang down into the line we really want to redraw,
8673             // so we have to invalidate part of the line above to make
8674             // sure everything that needs to be redrawn really is.
8675             // (But not the whole line above, because that would cause
8676             // the same problem with the descenders on the line above it!)
8677             if (lineStart > 0) {
8678                 top -= mLayout.getLineDescent(lineStart - 1);
8679             }
8680 
8681             int lineEnd;
8682 
8683             if (start == end) {
8684                 lineEnd = lineStart;
8685             } else {
8686                 lineEnd = mLayout.getLineForOffset(end);
8687             }
8688 
8689             int bottom = mLayout.getLineBottom(lineEnd);
8690 
8691             // mEditor can be null in case selection is set programmatically.
8692             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
8693                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
8694                 top = Math.min(top, bounds.top);
8695                 bottom = Math.max(bottom, bounds.bottom);
8696             }
8697 
8698             final int compoundPaddingLeft = getCompoundPaddingLeft();
8699             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
8700 
8701             int left, right;
8702             if (lineStart == lineEnd && !invalidateCursor) {
8703                 left = (int) mLayout.getPrimaryHorizontal(start);
8704                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
8705                 left += compoundPaddingLeft;
8706                 right += compoundPaddingLeft;
8707             } else {
8708                 // Rectangle bounding box when the region spans several lines
8709                 left = compoundPaddingLeft;
8710                 right = getWidth() - getCompoundPaddingRight();
8711             }
8712 
8713             invalidate(mScrollX + left, verticalPadding + top,
8714                     mScrollX + right, verticalPadding + bottom);
8715         }
8716     }
8717 
registerForPreDraw()8718     private void registerForPreDraw() {
8719         if (!mPreDrawRegistered) {
8720             getViewTreeObserver().addOnPreDrawListener(this);
8721             mPreDrawRegistered = true;
8722         }
8723     }
8724 
unregisterForPreDraw()8725     private void unregisterForPreDraw() {
8726         getViewTreeObserver().removeOnPreDrawListener(this);
8727         mPreDrawRegistered = false;
8728         mPreDrawListenerDetached = false;
8729     }
8730 
8731     /**
8732      * {@inheritDoc}
8733      */
8734     @Override
onPreDraw()8735     public boolean onPreDraw() {
8736         if (mLayout == null) {
8737             assumeLayout();
8738         }
8739 
8740         if (mMovement != null) {
8741             /* This code also provides auto-scrolling when a cursor is moved using a
8742              * CursorController (insertion point or selection limits).
8743              * For selection, ensure start or end is visible depending on controller's state.
8744              */
8745             int curs = getSelectionEnd();
8746             // Do not create the controller if it is not already created.
8747             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
8748                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
8749                 curs = getSelectionStart();
8750             }
8751 
8752             /*
8753              * TODO: This should really only keep the end in view if
8754              * it already was before the text changed.  I'm not sure
8755              * of a good way to tell from here if it was.
8756              */
8757             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8758                 curs = mText.length();
8759             }
8760 
8761             if (curs >= 0) {
8762                 bringPointIntoView(curs);
8763             }
8764         } else {
8765             bringTextIntoView();
8766         }
8767 
8768         // This has to be checked here since:
8769         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
8770         //   a screen rotation) since layout is not yet initialized at that point.
8771         if (mEditor != null && mEditor.mCreatedWithASelection) {
8772             mEditor.refreshTextActionMode();
8773             mEditor.mCreatedWithASelection = false;
8774         }
8775 
8776         unregisterForPreDraw();
8777 
8778         return true;
8779     }
8780 
8781     @Override
onAttachedToWindow()8782     protected void onAttachedToWindow() {
8783         super.onAttachedToWindow();
8784 
8785         if (mEditor != null) mEditor.onAttachedToWindow();
8786 
8787         if (mPreDrawListenerDetached) {
8788             getViewTreeObserver().addOnPreDrawListener(this);
8789             mPreDrawListenerDetached = false;
8790         }
8791     }
8792 
8793     /** @hide */
8794     @Override
onDetachedFromWindowInternal()8795     protected void onDetachedFromWindowInternal() {
8796         if (mPreDrawRegistered) {
8797             getViewTreeObserver().removeOnPreDrawListener(this);
8798             mPreDrawListenerDetached = true;
8799         }
8800 
8801         resetResolvedDrawables();
8802 
8803         if (mEditor != null) mEditor.onDetachedFromWindow();
8804 
8805         super.onDetachedFromWindowInternal();
8806     }
8807 
8808     @Override
onScreenStateChanged(int screenState)8809     public void onScreenStateChanged(int screenState) {
8810         super.onScreenStateChanged(screenState);
8811         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
8812     }
8813 
8814     @Override
isPaddingOffsetRequired()8815     protected boolean isPaddingOffsetRequired() {
8816         return mShadowRadius != 0 || mDrawables != null;
8817     }
8818 
8819     @Override
getLeftPaddingOffset()8820     protected int getLeftPaddingOffset() {
8821         return getCompoundPaddingLeft() - mPaddingLeft
8822                 + (int) Math.min(0, mShadowDx - mShadowRadius);
8823     }
8824 
8825     @Override
getTopPaddingOffset()8826     protected int getTopPaddingOffset() {
8827         return (int) Math.min(0, mShadowDy - mShadowRadius);
8828     }
8829 
8830     @Override
getBottomPaddingOffset()8831     protected int getBottomPaddingOffset() {
8832         return (int) Math.max(0, mShadowDy + mShadowRadius);
8833     }
8834 
8835     @Override
getRightPaddingOffset()8836     protected int getRightPaddingOffset() {
8837         return -(getCompoundPaddingRight() - mPaddingRight)
8838                 + (int) Math.max(0, mShadowDx + mShadowRadius);
8839     }
8840 
8841     @Override
verifyDrawable(@onNull Drawable who)8842     protected boolean verifyDrawable(@NonNull Drawable who) {
8843         final boolean verified = super.verifyDrawable(who);
8844         if (!verified && mDrawables != null) {
8845             for (Drawable dr : mDrawables.mShowing) {
8846                 if (who == dr) {
8847                     return true;
8848                 }
8849             }
8850         }
8851         return verified;
8852     }
8853 
8854     @Override
jumpDrawablesToCurrentState()8855     public void jumpDrawablesToCurrentState() {
8856         super.jumpDrawablesToCurrentState();
8857         if (mDrawables != null) {
8858             for (Drawable dr : mDrawables.mShowing) {
8859                 if (dr != null) {
8860                     dr.jumpToCurrentState();
8861                 }
8862             }
8863         }
8864     }
8865 
8866     @Override
invalidateDrawable(@onNull Drawable drawable)8867     public void invalidateDrawable(@NonNull Drawable drawable) {
8868         boolean handled = false;
8869 
8870         if (verifyDrawable(drawable)) {
8871             final Rect dirty = drawable.getBounds();
8872             int scrollX = mScrollX;
8873             int scrollY = mScrollY;
8874 
8875             // IMPORTANT: The coordinates below are based on the coordinates computed
8876             // for each compound drawable in onDraw(). Make sure to update each section
8877             // accordingly.
8878             final TextView.Drawables drawables = mDrawables;
8879             if (drawables != null) {
8880                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
8881                     final int compoundPaddingTop = getCompoundPaddingTop();
8882                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8883                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8884 
8885                     scrollX += mPaddingLeft;
8886                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
8887                     handled = true;
8888                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
8889                     final int compoundPaddingTop = getCompoundPaddingTop();
8890                     final int compoundPaddingBottom = getCompoundPaddingBottom();
8891                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
8892 
8893                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
8894                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
8895                     handled = true;
8896                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
8897                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8898                     final int compoundPaddingRight = getCompoundPaddingRight();
8899                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8900 
8901                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
8902                     scrollY += mPaddingTop;
8903                     handled = true;
8904                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
8905                     final int compoundPaddingLeft = getCompoundPaddingLeft();
8906                     final int compoundPaddingRight = getCompoundPaddingRight();
8907                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
8908 
8909                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
8910                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
8911                     handled = true;
8912                 }
8913             }
8914 
8915             if (handled) {
8916                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
8917                         dirty.right + scrollX, dirty.bottom + scrollY);
8918             }
8919         }
8920 
8921         if (!handled) {
8922             super.invalidateDrawable(drawable);
8923         }
8924     }
8925 
8926     @Override
hasOverlappingRendering()8927     public boolean hasOverlappingRendering() {
8928         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
8929         return ((getBackground() != null && getBackground().getCurrent() != null)
8930                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
8931                 || mShadowColor != 0);
8932     }
8933 
8934     /**
8935      *
8936      * Returns the state of the {@code textIsSelectable} flag (See
8937      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
8938      * to allow users to select and copy text in a non-editable TextView, the content of an
8939      * {@link EditText} can always be selected, independently of the value of this flag.
8940      * <p>
8941      *
8942      * @return True if the text displayed in this TextView can be selected by the user.
8943      *
8944      * @attr ref android.R.styleable#TextView_textIsSelectable
8945      */
8946     @InspectableProperty(name = "textIsSelectable")
isTextSelectable()8947     public boolean isTextSelectable() {
8948         return mEditor == null ? false : mEditor.mTextIsSelectable;
8949     }
8950 
8951     /**
8952      * Sets whether the content of this view is selectable by the user. The default is
8953      * {@code false}, meaning that the content is not selectable.
8954      * <p>
8955      * When you use a TextView to display a useful piece of information to the user (such as a
8956      * contact's address), make it selectable, so that the user can select and copy its
8957      * content. You can also use set the XML attribute
8958      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
8959      * <p>
8960      * When you call this method to set the value of {@code textIsSelectable}, it sets
8961      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
8962      * and {@code longClickable} to the same value. These flags correspond to the attributes
8963      * {@link android.R.styleable#View_focusable android:focusable},
8964      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
8965      * {@link android.R.styleable#View_clickable android:clickable}, and
8966      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
8967      * flags to a state you had set previously, call one or more of the following methods:
8968      * {@link #setFocusable(boolean) setFocusable()},
8969      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
8970      * {@link #setClickable(boolean) setClickable()} or
8971      * {@link #setLongClickable(boolean) setLongClickable()}.
8972      *
8973      * @param selectable Whether the content of this TextView should be selectable.
8974      */
setTextIsSelectable(boolean selectable)8975     public void setTextIsSelectable(boolean selectable) {
8976         if (!selectable && mEditor == null) return; // false is default value with no edit data
8977 
8978         createEditorIfNeeded();
8979         if (mEditor.mTextIsSelectable == selectable) return;
8980 
8981         mEditor.mTextIsSelectable = selectable;
8982         setFocusableInTouchMode(selectable);
8983         setFocusable(FOCUSABLE_AUTO);
8984         setClickable(selectable);
8985         setLongClickable(selectable);
8986 
8987         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
8988 
8989         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
8990         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
8991 
8992         // Called by setText above, but safer in case of future code changes
8993         mEditor.prepareCursorControllers();
8994     }
8995 
8996     @Override
onCreateDrawableState(int extraSpace)8997     protected int[] onCreateDrawableState(int extraSpace) {
8998         final int[] drawableState;
8999 
9000         if (mSingleLine) {
9001             drawableState = super.onCreateDrawableState(extraSpace);
9002         } else {
9003             drawableState = super.onCreateDrawableState(extraSpace + 1);
9004             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
9005         }
9006 
9007         if (isTextSelectable()) {
9008             // Disable pressed state, which was introduced when TextView was made clickable.
9009             // Prevents text color change.
9010             // setClickable(false) would have a similar effect, but it also disables focus changes
9011             // and long press actions, which are both needed by text selection.
9012             final int length = drawableState.length;
9013             for (int i = 0; i < length; i++) {
9014                 if (drawableState[i] == R.attr.state_pressed) {
9015                     final int[] nonPressedState = new int[length - 1];
9016                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
9017                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
9018                     return nonPressedState;
9019                 }
9020             }
9021         }
9022 
9023         return drawableState;
9024     }
9025 
maybeUpdateHighlightPaths()9026     private void maybeUpdateHighlightPaths() {
9027         if (!mHighlightPathsBogus) {
9028             return;
9029         }
9030 
9031         if (mHighlightPaths != null) {
9032             mPathRecyclePool.addAll(mHighlightPaths);
9033             mHighlightPaths.clear();
9034             mHighlightPaints.clear();
9035         } else {
9036             mHighlightPaths = new ArrayList<>();
9037             mHighlightPaints = new ArrayList<>();
9038         }
9039 
9040         if (mHighlights != null) {
9041             for (int i = 0; i < mHighlights.getSize(); ++i) {
9042                 final int[] ranges = mHighlights.getRanges(i);
9043                 final Paint paint = mHighlights.getPaint(i);
9044                 final Path path;
9045                 if (mPathRecyclePool.isEmpty()) {
9046                     path = new Path();
9047                 } else {
9048                     path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9049                     mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9050                     path.reset();
9051                 }
9052 
9053                 boolean atLeastOnePathAdded = false;
9054                 for (int j = 0; j < ranges.length / 2; ++j) {
9055                     final int start = ranges[2 * j];
9056                     final int end = ranges[2 * j + 1];
9057                     if (start < end) {
9058                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
9059                                 path.addRect(left, top, right, bottom, Path.Direction.CW)
9060                         );
9061                         atLeastOnePathAdded = true;
9062                     }
9063                 }
9064                 if (atLeastOnePathAdded) {
9065                     mHighlightPaths.add(path);
9066                     mHighlightPaints.add(paint);
9067                 }
9068             }
9069         }
9070 
9071         addSearchHighlightPaths();
9072 
9073         if (hasGesturePreviewHighlight()) {
9074             final Path path;
9075             if (mPathRecyclePool.isEmpty()) {
9076                 path = new Path();
9077             } else {
9078                 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9079                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9080                 path.reset();
9081             }
9082             mLayout.getSelectionPath(
9083                     mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path);
9084             mHighlightPaths.add(path);
9085             mHighlightPaints.add(mGesturePreviewHighlightPaint);
9086         }
9087 
9088         mHighlightPathsBogus = false;
9089     }
9090 
addSearchHighlightPaths()9091     private void addSearchHighlightPaths() {
9092         if (mSearchResultHighlights != null) {
9093             final Path searchResultPath;
9094             if (mPathRecyclePool.isEmpty()) {
9095                 searchResultPath = new Path();
9096             } else {
9097                 searchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9098                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9099                 searchResultPath.reset();
9100             }
9101             final Path focusedSearchResultPath;
9102             if (mFocusedSearchResultIndex == FOCUSED_SEARCH_RESULT_INDEX_NONE) {
9103                 focusedSearchResultPath = null;
9104             } else if (mPathRecyclePool.isEmpty()) {
9105                 focusedSearchResultPath = new Path();
9106             } else {
9107                 focusedSearchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
9108                 mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
9109                 focusedSearchResultPath.reset();
9110             }
9111 
9112             boolean atLeastOnePathAdded = false;
9113             for (int j = 0; j < mSearchResultHighlights.length / 2; ++j) {
9114                 final int start = mSearchResultHighlights[2 * j];
9115                 final int end = mSearchResultHighlights[2 * j + 1];
9116                 if (start < end) {
9117                     if (j == mFocusedSearchResultIndex) {
9118                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
9119                                 focusedSearchResultPath.addRect(left, top, right, bottom,
9120                                         Path.Direction.CW)
9121                         );
9122                     } else {
9123                         mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
9124                                 searchResultPath.addRect(left, top, right, bottom,
9125                                         Path.Direction.CW)
9126                         );
9127                         atLeastOnePathAdded = true;
9128                     }
9129                 }
9130             }
9131             if (atLeastOnePathAdded) {
9132                 if (mSearchResultHighlightPaint == null) {
9133                     mSearchResultHighlightPaint = new Paint();
9134                 }
9135                 mSearchResultHighlightPaint.setColor(mSearchResultHighlightColor);
9136                 mSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
9137                 mHighlightPaths.add(searchResultPath);
9138                 mHighlightPaints.add(mSearchResultHighlightPaint);
9139             }
9140             if (focusedSearchResultPath != null) {
9141                 if (mFocusedSearchResultHighlightPaint == null) {
9142                     mFocusedSearchResultHighlightPaint = new Paint();
9143                 }
9144                 mFocusedSearchResultHighlightPaint.setColor(mFocusedSearchResultHighlightColor);
9145                 mFocusedSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
9146                 mHighlightPaths.add(focusedSearchResultPath);
9147                 mHighlightPaints.add(mFocusedSearchResultHighlightPaint);
9148             }
9149         }
9150     }
9151 
9152     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getUpdatedHighlightPath()9153     private Path getUpdatedHighlightPath() {
9154         Path highlight = null;
9155         Paint highlightPaint = mHighlightPaint;
9156 
9157         final int selStart = getSelectionStartTransformed();
9158         final int selEnd = getSelectionEndTransformed();
9159         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
9160             if (selStart == selEnd) {
9161                 if (mEditor != null && mEditor.shouldRenderCursor()) {
9162                     if (mHighlightPathBogus) {
9163                         if (mHighlightPath == null) mHighlightPath = new Path();
9164                         mHighlightPath.reset();
9165                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
9166                         mEditor.updateCursorPosition();
9167                         mHighlightPathBogus = false;
9168                     }
9169 
9170                     // XXX should pass to skin instead of drawing directly
9171                     highlightPaint.setColor(mCurTextColor);
9172                     highlightPaint.setStyle(Paint.Style.STROKE);
9173                     highlight = mHighlightPath;
9174                 }
9175             } else {
9176                 if (mHighlightPathBogus) {
9177                     if (mHighlightPath == null) mHighlightPath = new Path();
9178                     mHighlightPath.reset();
9179                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
9180                     mHighlightPathBogus = false;
9181                 }
9182 
9183                 // XXX should pass to skin instead of drawing directly
9184                 highlightPaint.setColor(mHighlightColor);
9185                 highlightPaint.setStyle(Paint.Style.FILL);
9186 
9187                 highlight = mHighlightPath;
9188             }
9189         }
9190         return highlight;
9191     }
9192 
9193     /**
9194      * @hide
9195      */
getHorizontalOffsetForDrawables()9196     public int getHorizontalOffsetForDrawables() {
9197         return 0;
9198     }
9199 
9200     @Override
onDraw(Canvas canvas)9201     protected void onDraw(Canvas canvas) {
9202         restartMarqueeIfNeeded();
9203 
9204         // Draw the background for this view
9205         super.onDraw(canvas);
9206 
9207         final int compoundPaddingLeft = getCompoundPaddingLeft();
9208         final int compoundPaddingTop = getCompoundPaddingTop();
9209         final int compoundPaddingRight = getCompoundPaddingRight();
9210         final int compoundPaddingBottom = getCompoundPaddingBottom();
9211         final int scrollX = mScrollX;
9212         final int scrollY = mScrollY;
9213         final int right = mRight;
9214         final int left = mLeft;
9215         final int bottom = mBottom;
9216         final int top = mTop;
9217         final boolean isLayoutRtl = isLayoutRtl();
9218         final int offset = getHorizontalOffsetForDrawables();
9219         final int leftOffset = isLayoutRtl ? 0 : offset;
9220         final int rightOffset = isLayoutRtl ? offset : 0;
9221 
9222         final Drawables dr = mDrawables;
9223         if (dr != null) {
9224             /*
9225              * Compound, not extended, because the icon is not clipped
9226              * if the text height is smaller.
9227              */
9228 
9229             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
9230             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
9231 
9232             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9233             // Make sure to update invalidateDrawable() when changing this code.
9234             if (dr.mShowing[Drawables.LEFT] != null) {
9235                 canvas.save();
9236                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
9237                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
9238                 dr.mShowing[Drawables.LEFT].draw(canvas);
9239                 canvas.restore();
9240             }
9241 
9242             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9243             // Make sure to update invalidateDrawable() when changing this code.
9244             if (dr.mShowing[Drawables.RIGHT] != null) {
9245                 canvas.save();
9246                 canvas.translate(scrollX + right - left - mPaddingRight
9247                         - dr.mDrawableSizeRight - rightOffset,
9248                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
9249                 dr.mShowing[Drawables.RIGHT].draw(canvas);
9250                 canvas.restore();
9251             }
9252 
9253             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9254             // Make sure to update invalidateDrawable() when changing this code.
9255             if (dr.mShowing[Drawables.TOP] != null) {
9256                 canvas.save();
9257                 canvas.translate(scrollX + compoundPaddingLeft
9258                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
9259                 dr.mShowing[Drawables.TOP].draw(canvas);
9260                 canvas.restore();
9261             }
9262 
9263             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
9264             // Make sure to update invalidateDrawable() when changing this code.
9265             if (dr.mShowing[Drawables.BOTTOM] != null) {
9266                 canvas.save();
9267                 canvas.translate(scrollX + compoundPaddingLeft
9268                         + (hspace - dr.mDrawableWidthBottom) / 2,
9269                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
9270                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
9271                 canvas.restore();
9272             }
9273         }
9274 
9275         int color = mCurTextColor;
9276 
9277         if (mLayout == null) {
9278             assumeLayout();
9279         }
9280 
9281         Layout layout = mLayout;
9282 
9283         if (mHint != null && !mHideHint && mText.length() == 0) {
9284             if (mHintTextColor != null) {
9285                 color = mCurHintTextColor;
9286             }
9287 
9288             layout = mHintLayout;
9289         }
9290 
9291         mTextPaint.setColor(color);
9292         mTextPaint.drawableState = getDrawableState();
9293 
9294         canvas.save();
9295         /*  Would be faster if we didn't have to do this. Can we chop the
9296             (displayable) text so that we don't need to do this ever?
9297         */
9298 
9299         int extendedPaddingTop = getExtendedPaddingTop();
9300         int extendedPaddingBottom = getExtendedPaddingBottom();
9301 
9302         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
9303         final int maxScrollY = mLayout.getHeight() - vspace;
9304 
9305         float clipLeft = compoundPaddingLeft + scrollX;
9306         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
9307         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
9308         float clipBottom = bottom - top + scrollY
9309                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
9310 
9311         if (mShadowRadius != 0) {
9312             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
9313             clipRight += Math.max(0, mShadowDx + mShadowRadius);
9314 
9315             clipTop += Math.min(0, mShadowDy - mShadowRadius);
9316             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
9317         }
9318 
9319         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
9320 
9321         int voffsetText = 0;
9322         int voffsetCursor = 0;
9323 
9324         // translate in by our padding
9325         /* shortcircuit calling getVerticaOffset() */
9326         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9327             voffsetText = getVerticalOffset(false);
9328             voffsetCursor = getVerticalOffset(true);
9329         }
9330         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
9331 
9332         final int layoutDirection = getLayoutDirection();
9333         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
9334         if (isMarqueeFadeEnabled()) {
9335             if (!mSingleLine && getLineCount() == 1 && canMarquee()
9336                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
9337                 final int width = mRight - mLeft;
9338                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
9339                 final float dx = mLayout.getLineRight(0) - (width - padding);
9340                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9341             }
9342 
9343             if (mMarquee != null && mMarquee.isRunning()) {
9344                 final float dx = -mMarquee.getScroll();
9345                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9346             }
9347         }
9348 
9349         final int cursorOffsetVertical = voffsetCursor - voffsetText;
9350 
9351         maybeUpdateHighlightPaths();
9352         // If there is a gesture preview highlight, then the selection or cursor is not drawn.
9353         Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
9354         if (mEditor != null) {
9355             mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
9356                     mHighlightPaint, cursorOffsetVertical);
9357         } else {
9358             layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
9359                     cursorOffsetVertical);
9360         }
9361 
9362         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
9363             final float dx = mMarquee.getGhostOffset();
9364             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
9365             layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
9366                     cursorOffsetVertical);
9367         }
9368 
9369         canvas.restore();
9370     }
9371 
9372     @Override
getFocusedRect(Rect r)9373     public void getFocusedRect(Rect r) {
9374         if (mLayout == null) {
9375             super.getFocusedRect(r);
9376             return;
9377         }
9378 
9379         int selEnd = getSelectionEndTransformed();
9380         if (selEnd < 0) {
9381             super.getFocusedRect(r);
9382             return;
9383         }
9384 
9385         int selStart = getSelectionStartTransformed();
9386         if (selStart < 0 || selStart >= selEnd) {
9387             int line = mLayout.getLineForOffset(selEnd);
9388             r.top = mLayout.getLineTop(line);
9389             r.bottom = mLayout.getLineBottom(line);
9390             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
9391             r.right = r.left + 4;
9392         } else {
9393             int lineStart = mLayout.getLineForOffset(selStart);
9394             int lineEnd = mLayout.getLineForOffset(selEnd);
9395             r.top = mLayout.getLineTop(lineStart);
9396             r.bottom = mLayout.getLineBottom(lineEnd);
9397             if (lineStart == lineEnd) {
9398                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
9399                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
9400             } else {
9401                 // Selection extends across multiple lines -- make the focused
9402                 // rect cover the entire width.
9403                 if (mHighlightPathBogus) {
9404                     if (mHighlightPath == null) mHighlightPath = new Path();
9405                     mHighlightPath.reset();
9406                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
9407                     mHighlightPathBogus = false;
9408                 }
9409                 synchronized (TEMP_RECTF) {
9410                     mHighlightPath.computeBounds(TEMP_RECTF, true);
9411                     r.left = (int) TEMP_RECTF.left - 1;
9412                     r.right = (int) TEMP_RECTF.right + 1;
9413                 }
9414             }
9415         }
9416 
9417         // Adjust for padding and gravity.
9418         int paddingLeft = getCompoundPaddingLeft();
9419         int paddingTop = getExtendedPaddingTop();
9420         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9421             paddingTop += getVerticalOffset(false);
9422         }
9423         r.offset(paddingLeft, paddingTop);
9424         int paddingBottom = getExtendedPaddingBottom();
9425         r.bottom += paddingBottom;
9426     }
9427 
9428     /**
9429      * Return the number of lines of text, or 0 if the internal Layout has not
9430      * been built.
9431      */
getLineCount()9432     public int getLineCount() {
9433         return mLayout != null ? mLayout.getLineCount() : 0;
9434     }
9435 
9436     /**
9437      * Return the baseline for the specified line (0...getLineCount() - 1)
9438      * If bounds is not null, return the top, left, right, bottom extents
9439      * of the specified line in it. If the internal Layout has not been built,
9440      * return 0 and set bounds to (0, 0, 0, 0)
9441      * @param line which line to examine (0..getLineCount() - 1)
9442      * @param bounds Optional. If not null, it returns the extent of the line
9443      * @return the Y-coordinate of the baseline
9444      */
getLineBounds(int line, Rect bounds)9445     public int getLineBounds(int line, Rect bounds) {
9446         if (mLayout == null) {
9447             if (bounds != null) {
9448                 bounds.set(0, 0, 0, 0);
9449             }
9450             return 0;
9451         } else {
9452             int baseline = mLayout.getLineBounds(line, bounds);
9453 
9454             int voffset = getExtendedPaddingTop();
9455             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9456                 voffset += getVerticalOffset(true);
9457             }
9458             if (bounds != null) {
9459                 bounds.offset(getCompoundPaddingLeft(), voffset);
9460             }
9461             return baseline + voffset;
9462         }
9463     }
9464 
9465     @Override
getBaseline()9466     public int getBaseline() {
9467         if (mLayout == null) {
9468             return super.getBaseline();
9469         }
9470 
9471         return getBaselineOffset() + mLayout.getLineBaseline(0);
9472     }
9473 
getBaselineOffset()9474     int getBaselineOffset() {
9475         int voffset = 0;
9476         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9477             voffset = getVerticalOffset(true);
9478         }
9479 
9480         if (isLayoutModeOptical(mParent)) {
9481             voffset -= getOpticalInsets().top;
9482         }
9483 
9484         return getExtendedPaddingTop() + voffset;
9485     }
9486 
9487     /**
9488      * @hide
9489      */
9490     @Override
getFadeTop(boolean offsetRequired)9491     protected int getFadeTop(boolean offsetRequired) {
9492         if (mLayout == null) return 0;
9493 
9494         int voffset = 0;
9495         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9496             voffset = getVerticalOffset(true);
9497         }
9498 
9499         if (offsetRequired) voffset += getTopPaddingOffset();
9500 
9501         return getExtendedPaddingTop() + voffset;
9502     }
9503 
9504     /**
9505      * @hide
9506      */
9507     @Override
getFadeHeight(boolean offsetRequired)9508     protected int getFadeHeight(boolean offsetRequired) {
9509         return mLayout != null ? mLayout.getHeight() : 0;
9510     }
9511 
9512     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)9513     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
9514         if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
9515             if (mSpannable != null && mLinksClickable) {
9516                 final float x = event.getX(pointerIndex);
9517                 final float y = event.getY(pointerIndex);
9518                 final int offset = getOffsetForPosition(x, y);
9519                 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
9520                         ClickableSpan.class);
9521                 if (clickables.length > 0) {
9522                     return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
9523                 }
9524             }
9525             if (isTextSelectable() || isTextEditable()) {
9526                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
9527             }
9528         }
9529         return super.onResolvePointerIcon(event, pointerIndex);
9530     }
9531 
9532     @Override
onKeyPreIme(int keyCode, KeyEvent event)9533     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
9534         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
9535         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
9536         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
9537         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
9538             return true;
9539         }
9540         return super.onKeyPreIme(keyCode, event);
9541     }
9542 
9543     /**
9544      * @hide
9545      */
handleBackInTextActionModeIfNeeded(KeyEvent event)9546     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
9547         // Do nothing unless mEditor is in text action mode.
9548         if (mEditor == null || mEditor.getTextActionMode() == null) {
9549             return false;
9550         }
9551 
9552         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
9553             KeyEvent.DispatcherState state = getKeyDispatcherState();
9554             if (state != null) {
9555                 state.startTracking(event, this);
9556             }
9557             return true;
9558         } else if (event.getAction() == KeyEvent.ACTION_UP) {
9559             KeyEvent.DispatcherState state = getKeyDispatcherState();
9560             if (state != null) {
9561                 state.handleUpEvent(event);
9562             }
9563             if (event.isTracking() && !event.isCanceled()) {
9564                 stopTextActionMode();
9565                 return true;
9566             }
9567         }
9568         return false;
9569     }
9570 
9571     @Override
onKeyDown(int keyCode, KeyEvent event)9572     public boolean onKeyDown(int keyCode, KeyEvent event) {
9573         final int which = doKeyDown(keyCode, event, null);
9574         if (which == KEY_EVENT_NOT_HANDLED) {
9575             return super.onKeyDown(keyCode, event);
9576         }
9577 
9578         return true;
9579     }
9580 
9581     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)9582     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
9583         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
9584         final int which = doKeyDown(keyCode, down, event);
9585         if (which == KEY_EVENT_NOT_HANDLED) {
9586             // Go through default dispatching.
9587             return super.onKeyMultiple(keyCode, repeatCount, event);
9588         }
9589         if (which == KEY_EVENT_HANDLED) {
9590             // Consumed the whole thing.
9591             return true;
9592         }
9593 
9594         repeatCount--;
9595 
9596         // We are going to dispatch the remaining events to either the input
9597         // or movement method.  To do this, we will just send a repeated stream
9598         // of down and up events until we have done the complete repeatCount.
9599         // It would be nice if those interfaces had an onKeyMultiple() method,
9600         // but adding that is a more complicated change.
9601         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
9602         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
9603             // mEditor and mEditor.mInput are not null from doKeyDown
9604             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
9605             while (--repeatCount > 0) {
9606                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
9607                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
9608             }
9609             hideErrorIfUnchanged();
9610 
9611         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
9612             // mMovement is not null from doKeyDown
9613             mMovement.onKeyUp(this, mSpannable, keyCode, up);
9614             while (--repeatCount > 0) {
9615                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
9616                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
9617             }
9618         }
9619 
9620         return true;
9621     }
9622 
9623     /**
9624      * Returns true if pressing ENTER in this field advances focus instead
9625      * of inserting the character.  This is true mostly in single-line fields,
9626      * but also in mail addresses and subjects which will display on multiple
9627      * lines but where it doesn't make sense to insert newlines.
9628      */
shouldAdvanceFocusOnEnter()9629     private boolean shouldAdvanceFocusOnEnter() {
9630         if (getKeyListener() == null) {
9631             return false;
9632         }
9633 
9634         if (mSingleLine) {
9635             return true;
9636         }
9637 
9638         if (mEditor != null
9639                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
9640                         == EditorInfo.TYPE_CLASS_TEXT) {
9641             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9642             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
9643                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
9644                 return true;
9645             }
9646         }
9647 
9648         return false;
9649     }
9650 
isDirectionalNavigationKey(int keyCode)9651     private boolean isDirectionalNavigationKey(int keyCode) {
9652         switch(keyCode) {
9653             case KeyEvent.KEYCODE_DPAD_UP:
9654             case KeyEvent.KEYCODE_DPAD_DOWN:
9655             case KeyEvent.KEYCODE_DPAD_LEFT:
9656             case KeyEvent.KEYCODE_DPAD_RIGHT:
9657                 return true;
9658         }
9659         return false;
9660     }
9661 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)9662     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
9663         if (!isEnabled()) {
9664             return KEY_EVENT_NOT_HANDLED;
9665         }
9666 
9667         // If this is the initial keydown, we don't want to prevent a movement away from this view.
9668         // While this shouldn't be necessary because any time we're preventing default movement we
9669         // should be restricting the focus to remain within this view, thus we'll also receive
9670         // the key up event, occasionally key up events will get dropped and we don't want to
9671         // prevent the user from traversing out of this on the next key down.
9672         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
9673             mPreventDefaultMovement = false;
9674         }
9675 
9676         switch (keyCode) {
9677             case KeyEvent.KEYCODE_ENTER:
9678             case KeyEvent.KEYCODE_NUMPAD_ENTER:
9679                 if (event.hasNoModifiers()) {
9680                     // When mInputContentType is set, we know that we are
9681                     // running in a "modern" cupcake environment, so don't need
9682                     // to worry about the application trying to capture
9683                     // enter key events.
9684                     if (mEditor != null && mEditor.mInputContentType != null) {
9685                         // If there is an action listener, given them a
9686                         // chance to consume the event.
9687                         if (mEditor.mInputContentType.onEditorActionListener != null
9688                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
9689                                         this,
9690                                         getActionIdForEnterEvent(),
9691                                         event)) {
9692                             mEditor.mInputContentType.enterDown = true;
9693                             // We are consuming the enter key for them.
9694                             return KEY_EVENT_HANDLED;
9695                         }
9696                     }
9697 
9698                     // If our editor should move focus when enter is pressed, or
9699                     // this is a generated event from an IME action button, then
9700                     // don't let it be inserted into the text.
9701                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
9702                             || shouldAdvanceFocusOnEnter()) {
9703                         if (hasOnClickListeners()) {
9704                             return KEY_EVENT_NOT_HANDLED;
9705                         }
9706                         return KEY_EVENT_HANDLED;
9707                     }
9708                 }
9709                 break;
9710 
9711             case KeyEvent.KEYCODE_DPAD_CENTER:
9712                 if (event.hasNoModifiers()) {
9713                     if (shouldAdvanceFocusOnEnter()) {
9714                         return KEY_EVENT_NOT_HANDLED;
9715                     }
9716                 }
9717                 break;
9718 
9719             case KeyEvent.KEYCODE_TAB:
9720                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
9721                     // Tab is used to move focus.
9722                     return KEY_EVENT_NOT_HANDLED;
9723                 }
9724                 break;
9725 
9726                 // Has to be done on key down (and not on key up) to correctly be intercepted.
9727             case KeyEvent.KEYCODE_BACK:
9728                 if (mEditor != null && mEditor.getTextActionMode() != null) {
9729                     stopTextActionMode();
9730                     return KEY_EVENT_HANDLED;
9731                 }
9732                 break;
9733 
9734             case KeyEvent.KEYCODE_ESCAPE:
9735                 if (com.android.text.flags.Flags.escapeClearsFocus() && event.hasNoModifiers()) {
9736                     if (mEditor != null && mEditor.getTextActionMode() != null) {
9737                         stopTextActionMode();
9738                         return KEY_EVENT_HANDLED;
9739                     }
9740                     if (hasFocus()) {
9741                         clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
9742                         InputMethodManager imm = getInputMethodManager();
9743                         if (imm != null) {
9744                             imm.hideSoftInputFromView(this, 0);
9745                         }
9746                         return KEY_EVENT_HANDLED;
9747                     }
9748                 }
9749                 break;
9750 
9751             case KeyEvent.KEYCODE_CUT:
9752                 if (event.hasNoModifiers() && canCut()) {
9753                     if (onTextContextMenuItem(ID_CUT)) {
9754                         return KEY_EVENT_HANDLED;
9755                     }
9756                 }
9757                 break;
9758 
9759             case KeyEvent.KEYCODE_COPY:
9760                 if (event.hasNoModifiers() && canCopy()) {
9761                     if (onTextContextMenuItem(ID_COPY)) {
9762                         return KEY_EVENT_HANDLED;
9763                     }
9764                 }
9765                 break;
9766 
9767             case KeyEvent.KEYCODE_PASTE:
9768                 if (event.hasNoModifiers() && canPaste()) {
9769                     if (onTextContextMenuItem(ID_PASTE)) {
9770                         return KEY_EVENT_HANDLED;
9771                     }
9772                 }
9773                 break;
9774 
9775             case KeyEvent.KEYCODE_FORWARD_DEL:
9776                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
9777                     if (onTextContextMenuItem(ID_CUT)) {
9778                         return KEY_EVENT_HANDLED;
9779                     }
9780                 }
9781                 break;
9782 
9783             case KeyEvent.KEYCODE_INSERT:
9784                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
9785                     if (onTextContextMenuItem(ID_COPY)) {
9786                         return KEY_EVENT_HANDLED;
9787                     }
9788                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
9789                     if (onTextContextMenuItem(ID_PASTE)) {
9790                         return KEY_EVENT_HANDLED;
9791                     }
9792                 }
9793                 break;
9794         }
9795 
9796         if (mEditor != null && mEditor.mKeyListener != null) {
9797             boolean doDown = true;
9798             if (otherEvent != null) {
9799                 try {
9800                     beginBatchEdit();
9801                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
9802                             otherEvent);
9803                     hideErrorIfUnchanged();
9804                     doDown = false;
9805                     if (handled) {
9806                         return KEY_EVENT_HANDLED;
9807                     }
9808                 } catch (AbstractMethodError e) {
9809                     // onKeyOther was added after 1.0, so if it isn't
9810                     // implemented we need to try to dispatch as a regular down.
9811                 } finally {
9812                     endBatchEdit();
9813                 }
9814             }
9815 
9816             if (doDown) {
9817                 beginBatchEdit();
9818                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
9819                         keyCode, event);
9820                 endBatchEdit();
9821                 hideErrorIfUnchanged();
9822                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
9823             }
9824         }
9825 
9826         // bug 650865: sometimes we get a key event before a layout.
9827         // don't try to move around if we don't know the layout.
9828 
9829         if (mMovement != null && mLayout != null) {
9830             boolean doDown = true;
9831             if (otherEvent != null) {
9832                 try {
9833                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
9834                     doDown = false;
9835                     if (handled) {
9836                         return KEY_EVENT_HANDLED;
9837                     }
9838                 } catch (AbstractMethodError e) {
9839                     // onKeyOther was added after 1.0, so if it isn't
9840                     // implemented we need to try to dispatch as a regular down.
9841                 }
9842             }
9843             if (doDown) {
9844                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
9845                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
9846                         mPreventDefaultMovement = true;
9847                     }
9848                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
9849                 }
9850             }
9851             // Consume arrows from keyboard devices to prevent focus leaving the editor.
9852             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
9853             // to move focus with arrows.
9854             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
9855                     && isDirectionalNavigationKey(keyCode)) {
9856                 return KEY_EVENT_HANDLED;
9857             }
9858         }
9859 
9860         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
9861                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
9862     }
9863 
9864     /**
9865      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
9866      * can be recorded.
9867      * @hide
9868      */
resetErrorChangedFlag()9869     public void resetErrorChangedFlag() {
9870         /*
9871          * Keep track of what the error was before doing the input
9872          * so that if an input filter changed the error, we leave
9873          * that error showing.  Otherwise, we take down whatever
9874          * error was showing when the user types something.
9875          */
9876         if (mEditor != null) mEditor.mErrorWasChanged = false;
9877     }
9878 
9879     /**
9880      * @hide
9881      */
hideErrorIfUnchanged()9882     public void hideErrorIfUnchanged() {
9883         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
9884             setError(null, null);
9885         }
9886     }
9887 
9888     @Override
onKeyUp(int keyCode, KeyEvent event)9889     public boolean onKeyUp(int keyCode, KeyEvent event) {
9890         if (!isEnabled()) {
9891             return super.onKeyUp(keyCode, event);
9892         }
9893 
9894         if (!KeyEvent.isModifierKey(keyCode)) {
9895             mPreventDefaultMovement = false;
9896         }
9897 
9898         switch (keyCode) {
9899             case KeyEvent.KEYCODE_DPAD_CENTER:
9900                 if (event.hasNoModifiers()) {
9901                     /*
9902                      * If there is a click listener, just call through to
9903                      * super, which will invoke it.
9904                      *
9905                      * If there isn't a click listener, try to show the soft
9906                      * input method.  (It will also
9907                      * call performClick(), but that won't do anything in
9908                      * this case.)
9909                      */
9910                     if (!hasOnClickListeners()) {
9911                         if (mMovement != null && mText instanceof Editable
9912                                 && mLayout != null && onCheckIsTextEditor()) {
9913                             InputMethodManager imm = getInputMethodManager();
9914                             viewClicked(imm);
9915                             if (imm != null && getShowSoftInputOnFocus()) {
9916                                 imm.showSoftInput(this, 0);
9917                             }
9918                         }
9919                     }
9920                 }
9921                 return super.onKeyUp(keyCode, event);
9922 
9923             case KeyEvent.KEYCODE_ENTER:
9924             case KeyEvent.KEYCODE_NUMPAD_ENTER:
9925                 if (event.hasNoModifiers()) {
9926                     if (mEditor != null && mEditor.mInputContentType != null
9927                             && mEditor.mInputContentType.onEditorActionListener != null
9928                             && mEditor.mInputContentType.enterDown) {
9929                         mEditor.mInputContentType.enterDown = false;
9930                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
9931                                 this, getActionIdForEnterEvent(), event)) {
9932                             return true;
9933                         }
9934                     }
9935 
9936                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
9937                             || shouldAdvanceFocusOnEnter()) {
9938                         /*
9939                          * If there is a click listener, just call through to
9940                          * super, which will invoke it.
9941                          *
9942                          * If there isn't a click listener, try to advance focus,
9943                          * but still call through to super, which will reset the
9944                          * pressed state and longpress state.  (It will also
9945                          * call performClick(), but that won't do anything in
9946                          * this case.)
9947                          */
9948                         if (!hasOnClickListeners()) {
9949                             View v = focusSearch(FOCUS_DOWN);
9950 
9951                             if (v != null) {
9952                                 if (!v.requestFocus(FOCUS_DOWN)) {
9953                                     throw new IllegalStateException("focus search returned a view "
9954                                             + "that wasn't able to take focus!");
9955                                 }
9956 
9957                                 /*
9958                                  * Return true because we handled the key; super
9959                                  * will return false because there was no click
9960                                  * listener.
9961                                  */
9962                                 super.onKeyUp(keyCode, event);
9963                                 return true;
9964                             } else if ((event.getFlags()
9965                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
9966                                 // No target for next focus, but make sure the IME
9967                                 // if this came from it.
9968                                 InputMethodManager imm = getInputMethodManager();
9969                                 if (imm != null) {
9970                                     imm.hideSoftInputFromView(this, 0);
9971                                 }
9972                             }
9973                         }
9974                     }
9975                     return super.onKeyUp(keyCode, event);
9976                 }
9977                 break;
9978         }
9979 
9980         if (mEditor != null && mEditor.mKeyListener != null) {
9981             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
9982                 return true;
9983             }
9984         }
9985 
9986         if (mMovement != null && mLayout != null) {
9987             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
9988                 return true;
9989             }
9990         }
9991 
9992         return super.onKeyUp(keyCode, event);
9993     }
9994 
getActionIdForEnterEvent()9995     private int getActionIdForEnterEvent() {
9996         // If it's not single line, no action
9997         if (!isSingleLine()) {
9998             return EditorInfo.IME_NULL;
9999         }
10000         // Return the action that was specified for Enter
10001         return getImeOptions() & EditorInfo.IME_MASK_ACTION;
10002     }
10003 
10004     @Override
onCheckIsTextEditor()10005     public boolean onCheckIsTextEditor() {
10006         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
10007     }
10008 
hasEditorInFocusSearchDirection(@ocusRealDirection int direction)10009     private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) {
10010         final View nextView = focusSearch(direction);
10011         return nextView != null && nextView.onCheckIsTextEditor();
10012     }
10013 
10014     @Override
onCreateInputConnection(EditorInfo outAttrs)10015     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
10016         if (onCheckIsTextEditor() && isEnabled()) {
10017             mEditor.createInputMethodStateIfNeeded();
10018             mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = 0;
10019             mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = 0;
10020 
10021             outAttrs.inputType = getInputType();
10022             if (mEditor.mInputContentType != null) {
10023                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
10024                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
10025                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
10026                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
10027                 outAttrs.extras = mEditor.mInputContentType.extras;
10028                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
10029             } else {
10030                 outAttrs.imeOptions = EditorInfo.IME_NULL;
10031                 outAttrs.hintLocales = null;
10032             }
10033             if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) {
10034                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
10035             }
10036             if (hasEditorInFocusSearchDirection(FOCUS_UP)) {
10037                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
10038             }
10039             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
10040                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
10041                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
10042                     // An action has not been set, but the enter key will move to
10043                     // the next focus, so set the action to that.
10044                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
10045                 } else {
10046                     // An action has not been set, and there is no focus to move
10047                     // to, so let's just supply a "done" action.
10048                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
10049                 }
10050                 if (!shouldAdvanceFocusOnEnter()) {
10051                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
10052                 }
10053             }
10054             if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) {
10055                 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT;
10056             }
10057             if (isMultilineInputType(outAttrs.inputType)) {
10058                 // Multi-line text editors should always show an enter key.
10059                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
10060             }
10061             outAttrs.hintText = mHint;
10062             outAttrs.targetInputMethodUser = mTextOperationUser;
10063             if (mText instanceof Editable) {
10064                 InputConnection ic = new EditableInputConnection(this);
10065                 outAttrs.initialSelStart = getSelectionStart();
10066                 outAttrs.initialSelEnd = getSelectionEnd();
10067                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
10068                 outAttrs.setInitialSurroundingText(mText);
10069                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
10070                 if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) {
10071                     boolean handwritingEnabled = isAutoHandwritingEnabled();
10072                     outAttrs.setStylusHandwritingEnabled(handwritingEnabled);
10073                     // AndroidX Core library 1.13.0 introduced
10074                     // EditorInfoCompat#setStylusHandwritingEnabled and
10075                     // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the
10076                     // EditorInfo extras bundle. These methods do not set or check the Android V
10077                     // property since the Android V SDK was not yet available. In order for
10078                     // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for
10079                     // EditorInfo created by Android V TextView, the extras bundle value is also set
10080                     // here.
10081                     if (outAttrs.extras == null) {
10082                         outAttrs.extras = new Bundle();
10083                     }
10084                     outAttrs.extras.putBoolean(
10085                             STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled);
10086                 }
10087                 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
10088                 gestures.add(SelectGesture.class);
10089                 gestures.add(SelectRangeGesture.class);
10090                 gestures.add(DeleteGesture.class);
10091                 gestures.add(DeleteRangeGesture.class);
10092                 gestures.add(InsertGesture.class);
10093                 gestures.add(RemoveSpaceGesture.class);
10094                 gestures.add(JoinOrSplitGesture.class);
10095                 gestures.add(InsertModeGesture.class);
10096                 outAttrs.setSupportedHandwritingGestures(gestures);
10097 
10098                 Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>();
10099                 previews.add(SelectGesture.class);
10100                 previews.add(SelectRangeGesture.class);
10101                 previews.add(DeleteGesture.class);
10102                 previews.add(DeleteRangeGesture.class);
10103                 outAttrs.setSupportedHandwritingGesturePreviews(previews);
10104 
10105                 return ic;
10106             }
10107         }
10108         return null;
10109     }
10110 
10111     /**
10112      * Called back by the system to handle {@link InputConnection#requestCursorUpdates(int, int)}.
10113      *
10114      * @param cursorUpdateMode modes defined in {@link InputConnection.CursorUpdateMode}.
10115      * @param cursorUpdateFilter modes defined in {@link InputConnection.CursorUpdateFilter}.
10116      *
10117      * @hide
10118      */
onRequestCursorUpdatesInternal( @nputConnection.CursorUpdateMode int cursorUpdateMode, @InputConnection.CursorUpdateFilter int cursorUpdateFilter)10119     public void onRequestCursorUpdatesInternal(
10120             @InputConnection.CursorUpdateMode int cursorUpdateMode,
10121             @InputConnection.CursorUpdateFilter int cursorUpdateFilter) {
10122         mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = cursorUpdateMode;
10123         mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = cursorUpdateFilter;
10124         if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) == 0) {
10125             return;
10126         }
10127         if (isInLayout()) {
10128             // In this case, the view hierarchy is currently undergoing a layout pass.
10129             // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
10130             // pass is finished.
10131         } else {
10132             // This will schedule a layout pass of the view tree, and the layout event
10133             // eventually triggers IMM#updateCursorAnchorInfo.
10134             requestLayout();
10135         }
10136     }
10137 
10138     /**
10139      * If this TextView contains editable content, extract a portion of it
10140      * based on the information in <var>request</var> in to <var>outText</var>.
10141      * @return Returns true if the text was successfully extracted, else false.
10142      */
extractText(ExtractedTextRequest request, ExtractedText outText)10143     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
10144         createEditorIfNeeded();
10145         return mEditor.extractText(request, outText);
10146     }
10147 
10148     /**
10149      * This is used to remove all style-impacting spans from text before new
10150      * extracted text is being replaced into it, so that we don't have any
10151      * lingering spans applied during the replace.
10152      */
removeParcelableSpans(Spannable spannable, int start, int end)10153     static void removeParcelableSpans(Spannable spannable, int start, int end) {
10154         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
10155         int i = spans.length;
10156         while (i > 0) {
10157             i--;
10158             spannable.removeSpan(spans[i]);
10159         }
10160     }
10161 
10162     /**
10163      * Apply to this text view the given extracted text, as previously
10164      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
10165      */
setExtractedText(ExtractedText text)10166     public void setExtractedText(ExtractedText text) {
10167         Editable content = getEditableText();
10168         if (text.text != null) {
10169             if (content == null) {
10170                 setText(text.text, TextView.BufferType.EDITABLE);
10171             } else {
10172                 int start = 0;
10173                 int end = content.length();
10174 
10175                 if (text.partialStartOffset >= 0) {
10176                     final int N = content.length();
10177                     start = text.partialStartOffset;
10178                     if (start > N) start = N;
10179                     end = text.partialEndOffset;
10180                     if (end > N) end = N;
10181                 }
10182 
10183                 removeParcelableSpans(content, start, end);
10184                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
10185                     if (text.text instanceof Spanned) {
10186                         // OK to copy spans only.
10187                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
10188                                 Object.class, content, start);
10189                     }
10190                 } else {
10191                     content.replace(start, end, text.text);
10192                 }
10193             }
10194         }
10195 
10196         // Now set the selection position...  make sure it is in range, to
10197         // avoid crashes.  If this is a partial update, it is possible that
10198         // the underlying text may have changed, causing us problems here.
10199         // Also we just don't want to trust clients to do the right thing.
10200         Spannable sp = (Spannable) getText();
10201         final int N = sp.length();
10202         int start = text.selectionStart;
10203         if (start < 0) {
10204             start = 0;
10205         } else if (start > N) {
10206             start = N;
10207         }
10208         int end = text.selectionEnd;
10209         if (end < 0) {
10210             end = 0;
10211         } else if (end > N) {
10212             end = N;
10213         }
10214         Selection.setSelection(sp, start, end);
10215 
10216         // Finally, update the selection mode.
10217         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
10218             MetaKeyKeyListener.startSelecting(this, sp);
10219         } else {
10220             MetaKeyKeyListener.stopSelecting(this, sp);
10221         }
10222 
10223         setHintInternal(text.hint);
10224     }
10225 
10226     /**
10227      * @hide
10228      */
setExtracting(ExtractedTextRequest req)10229     public void setExtracting(ExtractedTextRequest req) {
10230         if (mEditor.mInputMethodState != null) {
10231             mEditor.mInputMethodState.mExtractedTextRequest = req;
10232         }
10233         // This would stop a possible selection mode, but no such mode is started in case
10234         // extracted mode will start. Some text is selected though, and will trigger an action mode
10235         // in the extracted view.
10236         mEditor.hideCursorAndSpanControllers();
10237         stopTextActionMode();
10238         if (mEditor.mSelectionModifierCursorController != null) {
10239             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
10240         }
10241     }
10242 
10243     /**
10244      * Called by the framework in response to a text completion from
10245      * the current input method, provided by it calling
10246      * {@link InputConnection#commitCompletion
10247      * InputConnection.commitCompletion()}.  The default implementation does
10248      * nothing; text views that are supporting auto-completion should override
10249      * this to do their desired behavior.
10250      *
10251      * @param text The auto complete text the user has selected.
10252      */
onCommitCompletion(CompletionInfo text)10253     public void onCommitCompletion(CompletionInfo text) {
10254         // intentionally empty
10255     }
10256 
10257     /**
10258      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
10259      * dictionary) from the current input method, provided by it calling
10260      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
10261      * The default implementation flashes the background of the corrected word to provide
10262      * feedback to the user.
10263      *
10264      * @param info The auto correct info about the text that was corrected.
10265      */
onCommitCorrection(CorrectionInfo info)10266     public void onCommitCorrection(CorrectionInfo info) {
10267         if (mEditor != null) mEditor.onCommitCorrection(info);
10268     }
10269 
beginBatchEdit()10270     public void beginBatchEdit() {
10271         if (mEditor != null) mEditor.beginBatchEdit();
10272     }
10273 
endBatchEdit()10274     public void endBatchEdit() {
10275         if (mEditor != null) mEditor.endBatchEdit();
10276     }
10277 
10278     /**
10279      * Called by the framework in response to a request to begin a batch
10280      * of edit operations through a call to link {@link #beginBatchEdit()}.
10281      */
onBeginBatchEdit()10282     public void onBeginBatchEdit() {
10283         // intentionally empty
10284     }
10285 
10286     /**
10287      * Called by the framework in response to a request to end a batch
10288      * of edit operations through a call to link {@link #endBatchEdit}.
10289      */
onEndBatchEdit()10290     public void onEndBatchEdit() {
10291         // intentionally empty
10292     }
10293 
10294     /** @hide */
onPerformSpellCheck()10295     public void onPerformSpellCheck() {
10296         if (mEditor != null && mEditor.mSpellChecker != null) {
10297             mEditor.mSpellChecker.onPerformSpellCheck();
10298         }
10299     }
10300 
10301     /**
10302      * Called by the framework in response to a private command from the
10303      * current method, provided by it calling
10304      * {@link InputConnection#performPrivateCommand
10305      * InputConnection.performPrivateCommand()}.
10306      *
10307      * @param action The action name of the command.
10308      * @param data Any additional data for the command.  This may be null.
10309      * @return Return true if you handled the command, else false.
10310      */
onPrivateIMECommand(String action, Bundle data)10311     public boolean onPrivateIMECommand(String action, Bundle data) {
10312         return false;
10313     }
10314 
10315     /**
10316      * Return whether the text is transformed and has {@link OffsetMapping}.
10317      * @hide
10318      */
isOffsetMappingAvailable()10319     public boolean isOffsetMappingAvailable() {
10320         return mTransformation != null && mTransformed instanceof OffsetMapping;
10321     }
10322 
10323     /** @hide */
previewHandwritingGesture( @onNull PreviewableHandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal)10324     public boolean previewHandwritingGesture(
10325             @NonNull PreviewableHandwritingGesture gesture,
10326             @Nullable CancellationSignal cancellationSignal) {
10327         if (gesture instanceof SelectGesture) {
10328             performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true);
10329         } else if (gesture instanceof SelectRangeGesture) {
10330             performHandwritingSelectRangeGesture(
10331                     (SelectRangeGesture) gesture, /* isPreview= */ true);
10332         } else if (gesture instanceof DeleteGesture) {
10333             performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true);
10334         } else if (gesture instanceof DeleteRangeGesture) {
10335             performHandwritingDeleteRangeGesture(
10336                     (DeleteRangeGesture) gesture, /* isPreview= */ true);
10337         } else {
10338             return false;
10339         }
10340         if (cancellationSignal != null) {
10341             cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight);
10342         }
10343         return true;
10344     }
10345 
10346     /** @hide */
performHandwritingSelectGesture(@onNull SelectGesture gesture)10347     public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
10348         return performHandwritingSelectGesture(gesture, /* isPreview= */ false);
10349     }
10350 
performHandwritingSelectGesture(@onNull SelectGesture gesture, boolean isPreview)10351     private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) {
10352         if (isOffsetMappingAvailable()) {
10353             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10354         }
10355         int[] range = getRangeForRect(
10356                 convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
10357                 gesture.getGranularity());
10358         if (range == null) {
10359             return handleGestureFailure(gesture, isPreview);
10360         }
10361         return performHandwritingSelectGesture(range, isPreview);
10362     }
10363 
performHandwritingSelectGesture(int[] range, boolean isPreview)10364     private int performHandwritingSelectGesture(int[] range, boolean isPreview) {
10365         if (isPreview) {
10366             setSelectGesturePreviewHighlight(range[0], range[1]);
10367         } else {
10368             Selection.setSelection(getEditableText(), range[0], range[1]);
10369             mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
10370         }
10371         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10372     }
10373 
10374     /** @hide */
performHandwritingSelectRangeGesture(@onNull SelectRangeGesture gesture)10375     public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) {
10376         return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false);
10377     }
10378 
performHandwritingSelectRangeGesture( @onNull SelectRangeGesture gesture, boolean isPreview)10379     private int performHandwritingSelectRangeGesture(
10380             @NonNull SelectRangeGesture gesture, boolean isPreview) {
10381         if (isOffsetMappingAvailable()) {
10382             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10383         }
10384         int[] startRange = getRangeForRect(
10385                 convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
10386                 gesture.getGranularity());
10387         if (startRange == null) {
10388             return handleGestureFailure(gesture, isPreview);
10389         }
10390         int[] endRange = getRangeForRect(
10391                 convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()),
10392                 gesture.getGranularity());
10393         if (endRange == null) {
10394             return handleGestureFailure(gesture, isPreview);
10395         }
10396         int[] range = new int[] {
10397                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
10398         };
10399         return performHandwritingSelectGesture(range, isPreview);
10400     }
10401 
10402     /** @hide */
performHandwritingDeleteGesture(@onNull DeleteGesture gesture)10403     public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
10404         return performHandwritingDeleteGesture(gesture, /* isPreview= */ false);
10405     }
10406 
performHandwritingDeleteGesture(@onNull DeleteGesture gesture, boolean isPreview)10407     private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) {
10408         if (isOffsetMappingAvailable()) {
10409             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10410         }
10411         int[] range = getRangeForRect(
10412                 convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
10413                 gesture.getGranularity());
10414         if (range == null) {
10415             return handleGestureFailure(gesture, isPreview);
10416         }
10417         return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
10418     }
10419 
performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview)10420     private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) {
10421         if (isPreview) {
10422             setDeleteGesturePreviewHighlight(range[0], range[1]);
10423         } else {
10424             if (granularity == HandwritingGesture.GRANULARITY_WORD) {
10425                 range = adjustHandwritingDeleteGestureRange(range);
10426             }
10427 
10428             Selection.setSelection(getEditableText(), range[0]);
10429             getEditableText().delete(range[0], range[1]);
10430         }
10431         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10432     }
10433 
10434     /** @hide */
performHandwritingDeleteRangeGesture(@onNull DeleteRangeGesture gesture)10435     public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) {
10436         return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false);
10437     }
10438 
performHandwritingDeleteRangeGesture( @onNull DeleteRangeGesture gesture, boolean isPreview)10439     private int performHandwritingDeleteRangeGesture(
10440             @NonNull DeleteRangeGesture gesture, boolean isPreview) {
10441         if (isOffsetMappingAvailable()) {
10442             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10443         }
10444         int[] startRange = getRangeForRect(
10445                 convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
10446                 gesture.getGranularity());
10447         if (startRange == null) {
10448             return handleGestureFailure(gesture, isPreview);
10449         }
10450         int[] endRange = getRangeForRect(
10451                 convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()),
10452                 gesture.getGranularity());
10453         if (endRange == null) {
10454             return handleGestureFailure(gesture, isPreview);
10455         }
10456         int[] range = new int[] {
10457                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
10458         };
10459         return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
10460     }
10461 
adjustHandwritingDeleteGestureRange(int[] range)10462     private int[] adjustHandwritingDeleteGestureRange(int[] range) {
10463         // For handwriting delete gestures with word granularity, adjust the start and end offsets
10464         // to remove extra whitespace around the deleted text.
10465 
10466         int start = range[0];
10467         int end = range[1];
10468 
10469         // If the deleted text is at the start of the text, the behavior is the same as the case
10470         // where the deleted text follows a new line character.
10471         int codePointBeforeStart = start > 0
10472                 ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT;
10473         // If the deleted text is at the end of the text, the behavior is the same as the case where
10474         // the deleted text precedes a new line character.
10475         int codePointAtEnd = end < mText.length()
10476                 ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT;
10477 
10478         if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)
10479                 && (TextUtils.isWhitespace(codePointAtEnd)
10480                         || TextUtils.isPunctuation(codePointAtEnd))) {
10481             // Remove whitespace (except new lines) before the deleted text, in these cases:
10482             // - There is whitespace following the deleted text
10483             //     e.g. "one [deleted] three" -> "one | three" -> "one| three"
10484             // - There is punctuation following the deleted text
10485             //     e.g. "one [deleted]!" -> "one |!" -> "one|!"
10486             // - There is a new line following the deleted text
10487             //     e.g. "one [deleted]\n" -> "one |\n" -> "one|\n"
10488             // - The deleted text is at the end of the text
10489             //     e.g. "one [deleted]" -> "one |" -> "one|"
10490             // (The pipe | indicates the cursor position.)
10491             do {
10492                 start -= Character.charCount(codePointBeforeStart);
10493                 if (start == 0) break;
10494                 codePointBeforeStart = Character.codePointBefore(mText, start);
10495             } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart));
10496             return new int[] {start, end};
10497         }
10498 
10499         if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)
10500                 && (TextUtils.isWhitespace(codePointBeforeStart)
10501                         || TextUtils.isPunctuation(codePointBeforeStart))) {
10502             // Remove whitespace (except new lines) after the deleted text, in these cases:
10503             // - There is punctuation preceding the deleted text
10504             //     e.g. "([deleted] two)" -> "(| two)" -> "(|two)"
10505             // - There is a new line preceding the deleted text
10506             //     e.g. "\n[deleted] two" -> "\n| two" -> "\n|two"
10507             // - The deleted text is at the start of the text
10508             //     e.g. "[deleted] two" -> "| two" -> "|two"
10509             // (The pipe | indicates the cursor position.)
10510             do {
10511                 end += Character.charCount(codePointAtEnd);
10512                 if (end == mText.length()) break;
10513                 codePointAtEnd = Character.codePointAt(mText, end);
10514             } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd));
10515             return new int[] {start, end};
10516         }
10517 
10518         // Return the original range.
10519         return range;
10520     }
10521 
10522     /** @hide */
10523     public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
10524         if (isOffsetMappingAvailable()) {
10525             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10526         }
10527         PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
10528         int line = getLineForHandwritingGesture(point);
10529         if (line == -1) {
10530             return handleGestureFailure(gesture);
10531         }
10532         int offset = mLayout.getOffsetForHorizontal(line, point.x);
10533         String textToInsert = gesture.getTextToInsert();
10534         return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture);
10535         // TODO(b/243980426): Insert extra spaces if necessary.
10536     }
10537 
10538     /** @hide */
10539     public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) {
10540         if (isOffsetMappingAvailable()) {
10541             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10542         }
10543         PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());
10544         PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint());
10545 
10546         // The operation should be applied to the first line of text containing one of the points.
10547         int startPointLine = getLineForHandwritingGesture(startPoint);
10548         int endPointLine = getLineForHandwritingGesture(endPoint);
10549         int line;
10550         if (startPointLine == -1) {
10551             if (endPointLine == -1) {
10552                 return handleGestureFailure(gesture);
10553             }
10554             line = endPointLine;
10555         } else {
10556             line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine);
10557         }
10558 
10559         // The operation should be applied to all characters touched by the line joining the points.
10560         float lineVerticalCenter = (mLayout.getLineTop(line)
10561                 + mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) / 2f;
10562         // Create a rectangle which is +/-0.1f around the line's vertical center, so that the
10563         // rectangle doesn't touch the line above or below. (The line height is at least 1f.)
10564         RectF area = new RectF(
10565                 Math.min(startPoint.x, endPoint.x),
10566                 lineVerticalCenter + 0.1f,
10567                 Math.max(startPoint.x, endPoint.x),
10568                 lineVerticalCenter - 0.1f);
10569         int[] range = mLayout.getRangeForRect(
10570                 area, new GraphemeClusterSegmentFinder(mText, mTextPaint),
10571                 Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
10572         if (range == null) {
10573             return handleGestureFailure(gesture);
10574         }
10575         int startOffset = range[0];
10576         int endOffset = range[1];
10577         // TODO(b/247557062): This doesn't handle bidirectional text correctly.
10578 
10579         Pattern whitespacePattern = getWhitespacePattern();
10580         Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
10581         int lastRemoveOffset = -1;
10582         while (matcher.find()) {
10583             lastRemoveOffset = startOffset + matcher.start();
10584             getEditableText().delete(lastRemoveOffset, startOffset + matcher.end());
10585             startOffset = lastRemoveOffset;
10586             endOffset -= matcher.end() - matcher.start();
10587             if (startOffset == endOffset) {
10588                 break;
10589             }
10590             matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
10591         }
10592         if (lastRemoveOffset == -1) {
10593             return handleGestureFailure(gesture);
10594         }
10595         Selection.setSelection(getEditableText(), lastRemoveOffset);
10596         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10597     }
10598 
10599     /** @hide */
10600     public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {
10601         if (isOffsetMappingAvailable()) {
10602             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10603         }
10604         PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint());
10605 
10606         int line = getLineForHandwritingGesture(point);
10607         if (line == -1) {
10608             return handleGestureFailure(gesture);
10609         }
10610 
10611         int startOffset = mLayout.getOffsetForHorizontal(line, point.x);
10612         if (mLayout.isLevelBoundary(startOffset)) {
10613             // TODO(b/247551937): Support gesture at level boundaries.
10614             return handleGestureFailure(gesture);
10615         }
10616 
10617         int endOffset = startOffset;
10618         while (startOffset > 0) {
10619             int codePointBeforeStart = Character.codePointBefore(mText, startOffset);
10620             if (!TextUtils.isWhitespace(codePointBeforeStart)) {
10621                 break;
10622             }
10623             startOffset -= Character.charCount(codePointBeforeStart);
10624         }
10625         while (endOffset < mText.length()) {
10626             int codePointAtEnd = Character.codePointAt(mText, endOffset);
10627             if (!TextUtils.isWhitespace(codePointAtEnd)) {
10628                 break;
10629             }
10630             endOffset += Character.charCount(codePointAtEnd);
10631         }
10632         if (startOffset < endOffset) {
10633             Selection.setSelection(getEditableText(), startOffset);
10634             getEditableText().delete(startOffset, endOffset);
10635             return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10636         } else {
10637             // No whitespace found, so insert a space.
10638             return tryInsertTextForHandwritingGesture(startOffset, " ", gesture);
10639         }
10640     }
10641 
10642     /** @hide */
10643     public int performHandwritingInsertModeGesture(@NonNull InsertModeGesture gesture) {
10644         final PointF insertPoint =
10645                 convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
10646         final int line = getLineForHandwritingGesture(insertPoint);
10647         final CancellationSignal cancellationSignal = gesture.getCancellationSignal();
10648 
10649         // If no cancellationSignal is provided, don't enter the insert mode.
10650         if (line == -1 || cancellationSignal == null) {
10651             return handleGestureFailure(gesture);
10652         }
10653 
10654         final int offset = mLayout.getOffsetForHorizontal(line, insertPoint.x);
10655 
10656         if (!mEditor.enterInsertMode(offset)) {
10657             return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10658         }
10659         cancellationSignal.setOnCancelListener(() -> mEditor.exitInsertMode());
10660         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10661     }
10662 
10663     private int handleGestureFailure(HandwritingGesture gesture) {
10664         return handleGestureFailure(gesture, /* isPreview= */ false);
10665     }
10666 
10667     private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) {
10668         clearGesturePreviewHighlight();
10669         if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) {
10670             getEditableText()
10671                     .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText());
10672             return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK;
10673         }
10674         return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
10675     }
10676 
10677     /**
10678      * Returns the closest line such that the point is either inside the line bounds or within
10679      * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns
10680      * -1 if the point is not within the margin of any line bounds.
10681      */
10682     private int getLineForHandwritingGesture(PointF point) {
10683         int line = mLayout.getLineForVertical((int) point.y);
10684         int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin();
10685         if (line < mLayout.getLineCount() - 1
10686                 && point.y > mLayout.getLineBottom(line) - lineMargin
10687                 && point.y
10688                         > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) {
10689             // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical
10690             // returns i. If the point is within lineMargin of line (i + 1), and closer to line
10691             // (i + 1) than line i, then the gesture operation should be applied to line (i + 1).
10692             line++;
10693         } else if (point.y < mLayout.getLineTop(line) - lineMargin
10694                 || point.y
10695                         > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)
10696                                 + lineMargin) {
10697             // The point is not within lineMargin of a line.
10698             return -1;
10699         }
10700         if (point.x < -lineMargin || point.x > mLayout.getWidth() + lineMargin) {
10701             // The point is not within lineMargin of a line.
10702             return -1;
10703         }
10704         return line;
10705     }
10706 
10707     @Nullable
10708     private int[] getRangeForRect(@NonNull RectF area, int granularity) {
10709         SegmentFinder segmentFinder;
10710         if (granularity == HandwritingGesture.GRANULARITY_WORD) {
10711             WordIterator wordIterator = getWordIterator();
10712             wordIterator.setCharSequence(mText, 0, mText.length());
10713             segmentFinder = new WordSegmentFinder(mText, wordIterator);
10714         } else {
10715             segmentFinder = new GraphemeClusterSegmentFinder(mText, mTextPaint);
10716         }
10717 
10718         return mLayout.getRangeForRect(
10719                 area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
10720     }
10721 
10722     private int tryInsertTextForHandwritingGesture(
10723             int offset, String textToInsert, HandwritingGesture gesture) {
10724         // A temporary cursor span is placed at the insertion offset. The span will be pushed
10725         // forward when text is inserted, then the real cursor can be placed after the inserted
10726         // text. A temporary cursor span is used in order to avoid modifying the real selection span
10727         // in the case that the text is filtered out.
10728         Editable editableText = getEditableText();
10729         if (mTempCursor == null) {
10730             mTempCursor = new NoCopySpan.Concrete();
10731         }
10732         editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT);
10733 
10734         editableText.insert(offset, textToInsert);
10735 
10736         int newOffset = editableText.getSpanStart(mTempCursor);
10737         editableText.removeSpan(mTempCursor);
10738         if (newOffset == offset) {
10739             // The inserted text was filtered out.
10740             return handleGestureFailure(gesture);
10741         } else {
10742             // Place the cursor after the inserted text.
10743             Selection.setSelection(editableText, newOffset);
10744             return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
10745         }
10746     }
10747 
10748     private Pattern getWhitespacePattern() {
10749         if (mWhitespacePattern == null) {
10750             mWhitespacePattern = Pattern.compile("\\s+");
10751         }
10752         return mWhitespacePattern;
10753     }
10754 
10755     /** @hide */
10756     @VisibleForTesting
10757     @UnsupportedAppUsage
10758     public void nullLayouts() {
10759         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
10760             mSavedLayout = (BoringLayout) mLayout;
10761         }
10762         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
10763             mSavedHintLayout = (BoringLayout) mHintLayout;
10764         }
10765 
10766         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
10767 
10768         mBoring = mHintBoring = null;
10769 
10770         // Since it depends on the value of mLayout
10771         if (mEditor != null) mEditor.prepareCursorControllers();
10772     }
10773 
10774     /**
10775      * Make a new Layout based on the already-measured size of the view,
10776      * on the assumption that it was measured correctly at some point.
10777      */
10778     @UnsupportedAppUsage
10779     private void assumeLayout() {
10780         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10781 
10782         if (width < 1) {
10783             width = 0;
10784         }
10785 
10786         int physicalWidth = width;
10787 
10788         if (mHorizontallyScrolling) {
10789             width = VERY_WIDE;
10790         }
10791 
10792         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
10793                       physicalWidth, false);
10794     }
10795 
10796     @UnsupportedAppUsage
10797     private Layout.Alignment getLayoutAlignment() {
10798         Layout.Alignment alignment;
10799         switch (getTextAlignment()) {
10800             case TEXT_ALIGNMENT_GRAVITY:
10801                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
10802                     case Gravity.START:
10803                         alignment = Layout.Alignment.ALIGN_NORMAL;
10804                         break;
10805                     case Gravity.END:
10806                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
10807                         break;
10808                     case Gravity.LEFT:
10809                         alignment = Layout.Alignment.ALIGN_LEFT;
10810                         break;
10811                     case Gravity.RIGHT:
10812                         alignment = Layout.Alignment.ALIGN_RIGHT;
10813                         break;
10814                     case Gravity.CENTER_HORIZONTAL:
10815                         alignment = Layout.Alignment.ALIGN_CENTER;
10816                         break;
10817                     default:
10818                         alignment = Layout.Alignment.ALIGN_NORMAL;
10819                         break;
10820                 }
10821                 break;
10822             case TEXT_ALIGNMENT_TEXT_START:
10823                 alignment = Layout.Alignment.ALIGN_NORMAL;
10824                 break;
10825             case TEXT_ALIGNMENT_TEXT_END:
10826                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
10827                 break;
10828             case TEXT_ALIGNMENT_CENTER:
10829                 alignment = Layout.Alignment.ALIGN_CENTER;
10830                 break;
10831             case TEXT_ALIGNMENT_VIEW_START:
10832                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
10833                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
10834                 break;
10835             case TEXT_ALIGNMENT_VIEW_END:
10836                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
10837                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
10838                 break;
10839             case TEXT_ALIGNMENT_INHERIT:
10840                 // This should never happen as we have already resolved the text alignment
10841                 // but better safe than sorry so we just fall through
10842             default:
10843                 alignment = Layout.Alignment.ALIGN_NORMAL;
10844                 break;
10845         }
10846         return alignment;
10847     }
10848 
10849     private Paint.FontMetrics getResolvedMinimumFontMetrics() {
10850         if (mMinimumFontMetrics != null) {
10851             return mMinimumFontMetrics;
10852         }
10853         if (!mUseLocalePreferredLineHeightForMinimum) {
10854             return null;
10855         }
10856 
10857         if (mLocalePreferredFontMetrics == null) {
10858             mLocalePreferredFontMetrics = new Paint.FontMetrics();
10859         }
10860         mTextPaint.getFontMetricsForLocale(mLocalePreferredFontMetrics);
10861         return mLocalePreferredFontMetrics;
10862     }
10863 
10864     /**
10865      * The width passed in is now the desired layout width,
10866      * not the full view width with padding.
10867      * {@hide}
10868      */
10869     @VisibleForTesting
10870     @UnsupportedAppUsage
10871     public void makeNewLayout(int wantWidth, int hintWidth,
10872                                  BoringLayout.Metrics boring,
10873                                  BoringLayout.Metrics hintBoring,
10874                                  int ellipsisWidth, boolean bringIntoView) {
10875         stopMarquee();
10876 
10877         // Update "old" cached values
10878         mOldMaximum = mMaximum;
10879         mOldMaxMode = mMaxMode;
10880 
10881         mHighlightPathBogus = true;
10882         mHighlightPathsBogus = true;
10883 
10884         if (wantWidth < 0) {
10885             wantWidth = 0;
10886         }
10887         if (hintWidth < 0) {
10888             hintWidth = 0;
10889         }
10890 
10891         Layout.Alignment alignment = getLayoutAlignment();
10892         final boolean testDirChange = mSingleLine && mLayout != null
10893                 && (alignment == Layout.Alignment.ALIGN_NORMAL
10894                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
10895         int oldDir = 0;
10896         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
10897         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
10898         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
10899                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
10900         TruncateAt effectiveEllipsize = mEllipsize;
10901         if (mEllipsize == TruncateAt.MARQUEE
10902                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10903             effectiveEllipsize = TruncateAt.END_SMALL;
10904         }
10905 
10906         if (mTextDir == null) {
10907             mTextDir = getTextDirectionHeuristic();
10908         }
10909 
10910         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
10911                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
10912         if (switchEllipsize) {
10913             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
10914                     ? TruncateAt.END : TruncateAt.MARQUEE;
10915             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
10916                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
10917         }
10918 
10919         shouldEllipsize = mEllipsize != null;
10920         mHintLayout = null;
10921 
10922         if (mHint != null) {
10923             if (shouldEllipsize) hintWidth = wantWidth;
10924 
10925             if (hintBoring == UNKNOWN_BORING) {
10926                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
10927                         isFallbackLineSpacingForBoringLayout(),
10928                         getResolvedMinimumFontMetrics(), mHintBoring);
10929 
10930                 if (hintBoring != null) {
10931                     mHintBoring = hintBoring;
10932                 }
10933             }
10934 
10935             if (hintBoring != null) {
10936                 if (hintBoring.width <= hintWidth
10937                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
10938                     if (mSavedHintLayout != null) {
10939                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
10940                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10941                                 hintBoring, mIncludePad);
10942                     } else {
10943                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
10944                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10945                                 hintBoring, mIncludePad);
10946                     }
10947 
10948                     mSavedHintLayout = (BoringLayout) mHintLayout;
10949                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
10950                     if (mSavedHintLayout != null) {
10951                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
10952                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10953                                 hintBoring, mIncludePad, mEllipsize,
10954                                 ellipsisWidth);
10955                     } else {
10956                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
10957                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
10958                                 hintBoring, mIncludePad, mEllipsize,
10959                                 ellipsisWidth);
10960                     }
10961                 }
10962             }
10963             // TODO: code duplication with makeSingleLayout()
10964             if (mHintLayout == null) {
10965                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
10966                         mHint.length(), mTextPaint, hintWidth)
10967                         .setAlignment(alignment)
10968                         .setTextDirection(mTextDir)
10969                         .setLineSpacing(mSpacingAdd, mSpacingMult)
10970                         .setIncludePad(mIncludePad)
10971                         .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
10972                         .setBreakStrategy(mBreakStrategy)
10973                         .setHyphenationFrequency(mHyphenationFrequency)
10974                         .setJustificationMode(mJustificationMode)
10975                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
10976                         .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
10977                                 mLineBreakStyle, mLineBreakWordStyle))
10978                         .setUseBoundsForWidth(mUseBoundsForWidth)
10979                         .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
10980 
10981                 if (shouldEllipsize) {
10982                     builder.setEllipsize(mEllipsize)
10983                             .setEllipsizedWidth(ellipsisWidth);
10984                 }
10985                 mHintLayout = builder.build();
10986             }
10987         }
10988 
10989         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
10990             registerForPreDraw();
10991         }
10992 
10993         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10994             if (!compressText(ellipsisWidth)) {
10995                 final int height = mLayoutParams.height;
10996                 // If the size of the view does not depend on the size of the text, try to
10997                 // start the marquee immediately
10998                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
10999                     startMarquee();
11000                 } else {
11001                     // Defer the start of the marquee until we know our width (see setFrame())
11002                     mRestartMarquee = true;
11003                 }
11004             }
11005         }
11006 
11007         // CursorControllers need a non-null mLayout
11008         if (mEditor != null) mEditor.prepareCursorControllers();
11009     }
11010 
11011     /**
11012      * Returns true if DynamicLayout is required
11013      *
11014      * @hide
11015      */
11016     @VisibleForTesting
11017     public boolean useDynamicLayout() {
11018         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
11019     }
11020 
11021     /**
11022      * @hide
11023      */
11024     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
11025             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
11026             boolean useSaved) {
11027         Layout result = null;
11028         if (useDynamicLayout()) {
11029             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
11030                     wantWidth)
11031                     .setDisplayText(mTransformed)
11032                     .setAlignment(alignment)
11033                     .setTextDirection(mTextDir)
11034                     .setLineSpacing(mSpacingAdd, mSpacingMult)
11035                     .setIncludePad(mIncludePad)
11036                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11037                     .setBreakStrategy(mBreakStrategy)
11038                     .setHyphenationFrequency(mHyphenationFrequency)
11039                     .setJustificationMode(mJustificationMode)
11040                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11041                             mLineBreakStyle, mLineBreakWordStyle))
11042                     .setUseBoundsForWidth(mUseBoundsForWidth)
11043                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
11044                     .setEllipsizedWidth(ellipsisWidth)
11045                     .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11046             result = builder.build();
11047         } else {
11048             if (boring == UNKNOWN_BORING) {
11049                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
11050                         isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
11051                         mBoring);
11052                 if (boring != null) {
11053                     mBoring = boring;
11054                 }
11055             }
11056 
11057             if (boring != null) {
11058                 if (boring.width <= wantWidth
11059                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
11060                     if (useSaved && mSavedLayout != null) {
11061                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
11062                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
11063                                 boring, mIncludePad, null, wantWidth,
11064                                 isFallbackLineSpacingForBoringLayout(),
11065                                 mUseBoundsForWidth, getResolvedMinimumFontMetrics());
11066                     } else {
11067                         result = new BoringLayout(
11068                                 mTransformed,
11069                                 mTextPaint,
11070                                 wantWidth,
11071                                 alignment,
11072                                 mSpacingMult,
11073                                 mSpacingAdd,
11074                                 mIncludePad,
11075                                 isFallbackLineSpacingForBoringLayout(),
11076                                 wantWidth,
11077                                 null,
11078                                 boring,
11079                                 mUseBoundsForWidth,
11080                                 mShiftDrawingOffsetForStartOverhang,
11081                                 getResolvedMinimumFontMetrics());
11082                     }
11083 
11084                     if (useSaved) {
11085                         mSavedLayout = (BoringLayout) result;
11086                     }
11087                 } else if (shouldEllipsize && boring.width <= wantWidth) {
11088                     if (useSaved && mSavedLayout != null) {
11089                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
11090                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
11091                                 boring, mIncludePad, effectiveEllipsize,
11092                                 ellipsisWidth, isFallbackLineSpacingForBoringLayout(),
11093                                 mUseBoundsForWidth, getResolvedMinimumFontMetrics());
11094                     } else {
11095                         result = new BoringLayout(
11096                                 mTransformed,
11097                                 mTextPaint,
11098                                 wantWidth,
11099                                 alignment,
11100                                 mSpacingMult,
11101                                 mSpacingAdd,
11102                                 mIncludePad,
11103                                 isFallbackLineSpacingForBoringLayout(),
11104                                 ellipsisWidth,
11105                                 effectiveEllipsize,
11106                                 boring,
11107                                 mUseBoundsForWidth,
11108                                 mShiftDrawingOffsetForStartOverhang,
11109                                 getResolvedMinimumFontMetrics());
11110                     }
11111                 }
11112             }
11113         }
11114         if (result == null) {
11115             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
11116                     0, mTransformed.length(), mTextPaint, wantWidth)
11117                     .setAlignment(alignment)
11118                     .setTextDirection(mTextDir)
11119                     .setLineSpacing(mSpacingAdd, mSpacingMult)
11120                     .setIncludePad(mIncludePad)
11121                     .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11122                     .setBreakStrategy(mBreakStrategy)
11123                     .setHyphenationFrequency(mHyphenationFrequency)
11124                     .setJustificationMode(mJustificationMode)
11125                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
11126                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11127                             mLineBreakStyle, mLineBreakWordStyle))
11128                     .setUseBoundsForWidth(mUseBoundsForWidth)
11129                     .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11130             if (shouldEllipsize) {
11131                 builder.setEllipsize(effectiveEllipsize)
11132                         .setEllipsizedWidth(ellipsisWidth);
11133             }
11134             result = builder.build();
11135         }
11136         return result;
11137     }
11138 
11139     @UnsupportedAppUsage
11140     private boolean compressText(float width) {
11141         if (isHardwareAccelerated()) return false;
11142 
11143         // Only compress the text if it hasn't been compressed by the previous pass
11144         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
11145                 && mTextPaint.getTextScaleX() == 1.0f) {
11146             final float textWidth = mLayout.getLineWidth(0);
11147             final float overflow = (textWidth + 1.0f - width) / width;
11148             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
11149                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
11150                 post(new Runnable() {
11151                     public void run() {
11152                         requestLayout();
11153                     }
11154                 });
11155                 return true;
11156             }
11157         }
11158 
11159         return false;
11160     }
11161 
11162     private static int desired(Layout layout, boolean useBoundsForWidth) {
11163         int n = layout.getLineCount();
11164         CharSequence text = layout.getText();
11165         float max = 0;
11166 
11167         // if any line was wrapped, we can't use it.
11168         // but it's ok for the last line not to have a newline
11169 
11170         for (int i = 0; i < n - 1; i++) {
11171             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
11172                 return -1;
11173             }
11174         }
11175 
11176         for (int i = 0; i < n; i++) {
11177             max = Math.max(max, layout.getLineMax(i));
11178         }
11179 
11180         if (useBoundsForWidth) {
11181             max = Math.max(max, layout.computeDrawingBoundingBox().width());
11182         }
11183 
11184         return (int) Math.ceil(max);
11185     }
11186 
11187     /**
11188      * Set whether the TextView includes extra top and bottom padding to make
11189      * room for accents that go above the normal ascent and descent.
11190      * The default is true.
11191      *
11192      * @see #getIncludeFontPadding()
11193      *
11194      * @attr ref android.R.styleable#TextView_includeFontPadding
11195      */
11196     public void setIncludeFontPadding(boolean includepad) {
11197         if (mIncludePad != includepad) {
11198             mIncludePad = includepad;
11199 
11200             if (mLayout != null) {
11201                 nullLayouts();
11202                 requestLayout();
11203                 invalidate();
11204             }
11205         }
11206     }
11207 
11208     /**
11209      * Gets whether the TextView includes extra top and bottom padding to make
11210      * room for accents that go above the normal ascent and descent.
11211      *
11212      * @see #setIncludeFontPadding(boolean)
11213      *
11214      * @attr ref android.R.styleable#TextView_includeFontPadding
11215      */
11216     @InspectableProperty
11217     public boolean getIncludeFontPadding() {
11218         return mIncludePad;
11219     }
11220 
11221     /** @hide */
11222     @VisibleForTesting
11223     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
11224 
11225     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)11226     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
11227         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
11228         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
11229         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
11230         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
11231 
11232         int width;
11233         int height;
11234 
11235         BoringLayout.Metrics boring = UNKNOWN_BORING;
11236         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
11237 
11238         if (mTextDir == null) {
11239             mTextDir = getTextDirectionHeuristic();
11240         }
11241 
11242         int des = -1;
11243         boolean fromexisting = false;
11244         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
11245                 ?  (float) widthSize : Float.MAX_VALUE;
11246 
11247         if (widthMode == MeasureSpec.EXACTLY) {
11248             // Parent has told us how big to be. So be it.
11249             width = widthSize;
11250         } else {
11251             if (mLayout != null && mEllipsize == null) {
11252                 des = desired(mLayout, mUseBoundsForWidth);
11253             }
11254 
11255             if (des < 0) {
11256                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
11257                         isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
11258                         mBoring);
11259                 if (boring != null) {
11260                     mBoring = boring;
11261                 }
11262             } else {
11263                 fromexisting = true;
11264             }
11265 
11266             if (boring == null || boring == UNKNOWN_BORING) {
11267                 if (des < 0) {
11268                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
11269                             mTransformed.length(), mTextPaint, mTextDir, widthLimit,
11270                             mUseBoundsForWidth));
11271                 }
11272                 width = des;
11273             } else {
11274                 if (mUseBoundsForWidth) {
11275                     RectF bbox = boring.getDrawingBoundingBox();
11276                     float rightMax = Math.max(bbox.right, boring.width);
11277                     float leftMin = Math.min(bbox.left, 0);
11278                     width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
11279                 } else {
11280                     width = boring.width;
11281                 }
11282             }
11283 
11284             final Drawables dr = mDrawables;
11285             if (dr != null) {
11286                 width = Math.max(width, dr.mDrawableWidthTop);
11287                 width = Math.max(width, dr.mDrawableWidthBottom);
11288             }
11289 
11290             if (mHint != null) {
11291                 int hintDes = -1;
11292                 int hintWidth;
11293 
11294                 if (mHintLayout != null && mEllipsize == null) {
11295                     hintDes = desired(mHintLayout, mUseBoundsForWidth);
11296                 }
11297 
11298                 if (hintDes < 0) {
11299                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
11300                             isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
11301                             mHintBoring);
11302                     if (hintBoring != null) {
11303                         mHintBoring = hintBoring;
11304                     }
11305                 }
11306 
11307                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
11308                     if (hintDes < 0) {
11309                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
11310                                 mHint.length(), mTextPaint, mTextDir, widthLimit,
11311                                 mUseBoundsForWidth));
11312                     }
11313                     hintWidth = hintDes;
11314                 } else {
11315                     hintWidth = hintBoring.width;
11316                 }
11317 
11318                 if (hintWidth > width) {
11319                     width = hintWidth;
11320                 }
11321             }
11322 
11323             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
11324 
11325             if (mMaxWidthMode == EMS) {
11326                 width = Math.min(width, mMaxWidth * getLineHeight());
11327             } else {
11328                 width = Math.min(width, mMaxWidth);
11329             }
11330 
11331             if (mMinWidthMode == EMS) {
11332                 width = Math.max(width, mMinWidth * getLineHeight());
11333             } else {
11334                 width = Math.max(width, mMinWidth);
11335             }
11336 
11337             // Check against our minimum width
11338             width = Math.max(width, getSuggestedMinimumWidth());
11339 
11340             if (widthMode == MeasureSpec.AT_MOST) {
11341                 width = Math.min(widthSize, width);
11342             }
11343         }
11344 
11345         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
11346         int unpaddedWidth = want;
11347 
11348         if (mHorizontallyScrolling) want = VERY_WIDE;
11349 
11350         int hintWant = want;
11351         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
11352 
11353         if (mLayout == null) {
11354             makeNewLayout(want, hintWant, boring, hintBoring,
11355                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
11356         } else {
11357             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
11358                     || (mLayout.getEllipsizedWidth()
11359                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
11360 
11361             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
11362                     && (want > mLayout.getWidth())
11363                     && (mLayout instanceof BoringLayout
11364                             || (fromexisting && des >= 0 && des <= want));
11365 
11366             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
11367 
11368             if (layoutChanged || maximumChanged) {
11369                 if (!maximumChanged && widthChanged) {
11370                     mLayout.increaseWidthTo(want);
11371                 } else {
11372                     makeNewLayout(want, hintWant, boring, hintBoring,
11373                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
11374                 }
11375             } else {
11376                 // Nothing has changed
11377             }
11378         }
11379 
11380         if (heightMode == MeasureSpec.EXACTLY) {
11381             // Parent has told us how big to be. So be it.
11382             height = heightSize;
11383             mDesiredHeightAtMeasure = -1;
11384         } else {
11385             int desired = getDesiredHeight();
11386 
11387             height = desired;
11388             mDesiredHeightAtMeasure = desired;
11389 
11390             if (heightMode == MeasureSpec.AT_MOST) {
11391                 height = Math.min(desired, heightSize);
11392             }
11393         }
11394 
11395         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
11396         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
11397             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
11398         }
11399 
11400         /*
11401          * We didn't let makeNewLayout() register to bring the cursor into view,
11402          * so do it here if there is any possibility that it is needed.
11403          */
11404         if (mMovement != null
11405                 || mLayout.getWidth() > unpaddedWidth
11406                 || mLayout.getHeight() > unpaddedHeight) {
11407             registerForPreDraw();
11408         } else {
11409             scrollTo(0, 0);
11410         }
11411 
11412         setMeasuredDimension(width, height);
11413     }
11414 
11415     /**
11416      * Automatically computes and sets the text size.
11417      */
autoSizeText()11418     private void autoSizeText() {
11419         if (!isAutoSizeEnabled()) {
11420             return;
11421         }
11422 
11423         if (mNeedsAutoSizeText) {
11424             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
11425                 return;
11426             }
11427 
11428             final int availableWidth = mHorizontallyScrolling
11429                     ? VERY_WIDE
11430                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
11431             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
11432                     - getExtendedPaddingTop();
11433 
11434             if (availableWidth <= 0 || availableHeight <= 0) {
11435                 return;
11436             }
11437 
11438             synchronized (TEMP_RECTF) {
11439                 TEMP_RECTF.setEmpty();
11440                 TEMP_RECTF.right = availableWidth;
11441                 TEMP_RECTF.bottom = availableHeight;
11442                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
11443 
11444                 if (optimalTextSize != getTextSize()) {
11445                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
11446                             false /* shouldRequestLayout */);
11447 
11448                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
11449                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
11450                             false /* bringIntoView */);
11451                 }
11452             }
11453         }
11454         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
11455         // after the next layout pass should set this to false.
11456         mNeedsAutoSizeText = true;
11457     }
11458 
11459     /**
11460      * Performs a binary search to find the largest text size that will still fit within the size
11461      * available to this view.
11462      */
findLargestTextSizeWhichFits(RectF availableSpace)11463     private int findLargestTextSizeWhichFits(RectF availableSpace) {
11464         final int sizesCount = mAutoSizeTextSizesInPx.length;
11465         if (sizesCount == 0) {
11466             throw new IllegalStateException("No available text sizes to choose from.");
11467         }
11468 
11469         int bestSizeIndex = 0;
11470         int lowIndex = bestSizeIndex + 1;
11471         int highIndex = sizesCount - 1;
11472         int sizeToTryIndex;
11473         while (lowIndex <= highIndex) {
11474             sizeToTryIndex = (lowIndex + highIndex) / 2;
11475             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
11476                 bestSizeIndex = lowIndex;
11477                 lowIndex = sizeToTryIndex + 1;
11478             } else {
11479                 highIndex = sizeToTryIndex - 1;
11480                 bestSizeIndex = highIndex;
11481             }
11482         }
11483 
11484         return mAutoSizeTextSizesInPx[bestSizeIndex];
11485     }
11486 
suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)11487     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
11488         final CharSequence text = mTransformed != null
11489                 ? mTransformed
11490                 : getText();
11491         final int maxLines = getMaxLines();
11492         if (mTempTextPaint == null) {
11493             mTempTextPaint = new TextPaint();
11494         } else {
11495             mTempTextPaint.reset();
11496         }
11497         mTempTextPaint.set(getPaint());
11498         mTempTextPaint.setTextSize(suggestedSizeInPx);
11499 
11500         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
11501                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
11502         layoutBuilder.setAlignment(getLayoutAlignment())
11503                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
11504                 .setIncludePad(getIncludeFontPadding())
11505                 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
11506                 .setBreakStrategy(getBreakStrategy())
11507                 .setHyphenationFrequency(getHyphenationFrequency())
11508                 .setJustificationMode(getJustificationMode())
11509                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
11510                 .setTextDirection(getTextDirectionHeuristic())
11511                 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
11512                         mLineBreakStyle, mLineBreakWordStyle))
11513                 .setUseBoundsForWidth(mUseBoundsForWidth)
11514                 .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
11515 
11516         final StaticLayout layout = layoutBuilder.build();
11517 
11518         // Lines overflow.
11519         if (maxLines != -1 && layout.getLineCount() > maxLines) {
11520             return false;
11521         }
11522 
11523         // Height overflow.
11524         if (layout.getHeight() > availableSpace.bottom) {
11525             return false;
11526         }
11527 
11528         return true;
11529     }
11530 
getDesiredHeight()11531     private int getDesiredHeight() {
11532         return Math.max(
11533                 getDesiredHeight(mLayout, true),
11534                 getDesiredHeight(mHintLayout, mEllipsize != null));
11535     }
11536 
getDesiredHeight(Layout layout, boolean cap)11537     private int getDesiredHeight(Layout layout, boolean cap) {
11538         if (layout == null) {
11539             return 0;
11540         }
11541 
11542         /*
11543         * Don't cap the hint to a certain number of lines.
11544         * (Do cap it, though, if we have a maximum pixel height.)
11545         */
11546         int desired = layout.getHeight(cap);
11547 
11548         final Drawables dr = mDrawables;
11549         if (dr != null) {
11550             desired = Math.max(desired, dr.mDrawableHeightLeft);
11551             desired = Math.max(desired, dr.mDrawableHeightRight);
11552         }
11553 
11554         int linecount = layout.getLineCount();
11555         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
11556         desired += padding;
11557 
11558         if (mMaxMode != LINES) {
11559             desired = Math.min(desired, mMaximum);
11560         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
11561                 || layout instanceof BoringLayout)) {
11562             desired = layout.getLineTop(mMaximum);
11563 
11564             if (dr != null) {
11565                 desired = Math.max(desired, dr.mDrawableHeightLeft);
11566                 desired = Math.max(desired, dr.mDrawableHeightRight);
11567             }
11568 
11569             desired += padding;
11570             linecount = mMaximum;
11571         }
11572 
11573         if (mMinMode == LINES) {
11574             if (linecount < mMinimum) {
11575                 desired += getLineHeight() * (mMinimum - linecount);
11576             }
11577         } else {
11578             desired = Math.max(desired, mMinimum);
11579         }
11580 
11581         // Check against our minimum height
11582         desired = Math.max(desired, getSuggestedMinimumHeight());
11583 
11584         return desired;
11585     }
11586 
11587     /**
11588      * Check whether a change to the existing text layout requires a
11589      * new view layout.
11590      */
checkForResize()11591     private void checkForResize() {
11592         boolean sizeChanged = false;
11593 
11594         if (mLayout != null) {
11595             // Check if our width changed
11596             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
11597                 sizeChanged = true;
11598                 invalidate();
11599             }
11600 
11601             // Check if our height changed
11602             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
11603                 int desiredHeight = getDesiredHeight();
11604 
11605                 if (desiredHeight != this.getHeight()) {
11606                     sizeChanged = true;
11607                 }
11608             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
11609                 if (mDesiredHeightAtMeasure >= 0) {
11610                     int desiredHeight = getDesiredHeight();
11611 
11612                     if (desiredHeight != mDesiredHeightAtMeasure) {
11613                         sizeChanged = true;
11614                     }
11615                 }
11616             }
11617         }
11618 
11619         if (sizeChanged) {
11620             requestLayout();
11621             // caller will have already invalidated
11622         }
11623     }
11624 
11625     /**
11626      * Check whether entirely new text requires a new view layout
11627      * or merely a new text layout.
11628      */
11629     @UnsupportedAppUsage
checkForRelayout()11630     private void checkForRelayout() {
11631         // If we have a fixed width, we can just swap in a new text layout
11632         // if the text height stays the same or if the view height is fixed.
11633 
11634         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
11635                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
11636                 && (mHint == null || mHintLayout != null)
11637                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
11638             // Static width, so try making a new text layout.
11639 
11640             int oldht = mLayout.getHeight();
11641             int want = mLayout.getWidth();
11642             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
11643 
11644             /*
11645              * No need to bring the text into view, since the size is not
11646              * changing (unless we do the requestLayout(), in which case it
11647              * will happen at measure).
11648              */
11649             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
11650                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
11651                           false);
11652 
11653             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
11654                 // In a fixed-height view, so use our new text layout.
11655                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
11656                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
11657                     autoSizeText();
11658                     invalidate();
11659                     return;
11660                 }
11661 
11662                 // Dynamic height, but height has stayed the same,
11663                 // so use our new text layout.
11664                 if (mLayout.getHeight() == oldht
11665                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
11666                     autoSizeText();
11667                     invalidate();
11668                     return;
11669                 }
11670             }
11671 
11672             // We lose: the height has changed and we have a dynamic height.
11673             // Request a new view layout using our new text layout.
11674             requestLayout();
11675             invalidate();
11676         } else {
11677             // Dynamic width, so we have no choice but to request a new
11678             // view layout with a new text layout.
11679             nullLayouts();
11680             requestLayout();
11681             invalidate();
11682         }
11683     }
11684 
11685     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)11686     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
11687         super.onLayout(changed, left, top, right, bottom);
11688         if (mDeferScroll >= 0) {
11689             int curs = mDeferScroll;
11690             mDeferScroll = -1;
11691             bringPointIntoView(Math.min(curs, mText.length()));
11692         }
11693         // Call auto-size after the width and height have been calculated.
11694         autoSizeText();
11695     }
11696 
isShowingHint()11697     private boolean isShowingHint() {
11698         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint) && !mHideHint;
11699     }
11700 
11701     /**
11702      * Returns true if anything changed.
11703      */
11704     @UnsupportedAppUsage
bringTextIntoView()11705     private boolean bringTextIntoView() {
11706         Layout layout = isShowingHint() ? mHintLayout : mLayout;
11707         int line = 0;
11708         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
11709             line = layout.getLineCount() - 1;
11710         }
11711 
11712         Layout.Alignment a = layout.getParagraphAlignment(line);
11713         int dir = layout.getParagraphDirection(line);
11714         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
11715         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
11716         int ht = layout.getHeight();
11717 
11718         int scrollx, scrolly;
11719 
11720         // Convert to left, center, or right alignment.
11721         if (a == Layout.Alignment.ALIGN_NORMAL) {
11722             a = dir == Layout.DIR_LEFT_TO_RIGHT
11723                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
11724         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
11725             a = dir == Layout.DIR_LEFT_TO_RIGHT
11726                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
11727         }
11728 
11729         if (a == Layout.Alignment.ALIGN_CENTER) {
11730             /*
11731              * Keep centered if possible, or, if it is too wide to fit,
11732              * keep leading edge in view.
11733              */
11734 
11735             int left = (int) Math.floor(layout.getLineLeft(line));
11736             int right = (int) Math.ceil(layout.getLineRight(line));
11737 
11738             if (right - left < hspace) {
11739                 scrollx = (right + left) / 2 - hspace / 2;
11740             } else {
11741                 if (dir < 0) {
11742                     scrollx = right - hspace;
11743                 } else {
11744                     scrollx = left;
11745                 }
11746             }
11747         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
11748             int right = (int) Math.ceil(layout.getLineRight(line));
11749             scrollx = right - hspace;
11750         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
11751             scrollx = (int) Math.floor(layout.getLineLeft(line));
11752         }
11753 
11754         if (ht < vspace) {
11755             scrolly = 0;
11756         } else {
11757             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
11758                 scrolly = ht - vspace;
11759             } else {
11760                 scrolly = 0;
11761             }
11762         }
11763 
11764         if (scrollx != mScrollX || scrolly != mScrollY) {
11765             scrollTo(scrollx, scrolly);
11766             return true;
11767         } else {
11768             return false;
11769         }
11770     }
11771 
11772     /**
11773      * Move the point, specified by the offset, into the view if it is needed.
11774      * This has to be called after layout. Returns true if anything changed.
11775      */
bringPointIntoView(int offset)11776     public boolean bringPointIntoView(int offset) {
11777         return bringPointIntoView(offset, false);
11778     }
11779 
11780     /**
11781      * Move the insertion position of the given offset into visible area of the View.
11782      *
11783      * If the View is focused or {@code requestRectWithoutFocus} is set to true, this API may call
11784      * {@link View#requestRectangleOnScreen(Rect)} to bring the point to the visible area if
11785      * necessary.
11786      *
11787      * @param offset an offset of the character.
11788      * @param requestRectWithoutFocus True for calling {@link View#requestRectangleOnScreen(Rect)}
11789      *                                in the unfocused state. False for calling it only the View has
11790      *                                the focus.
11791      * @return true if anything changed, otherwise false.
11792      *
11793      * @see #bringPointIntoView(int)
11794      */
bringPointIntoView(@ntRangefrom = 0) int offset, boolean requestRectWithoutFocus)11795     public boolean bringPointIntoView(@IntRange(from = 0) int offset,
11796             boolean requestRectWithoutFocus) {
11797         if (isLayoutRequested()) {
11798             mDeferScroll = offset;
11799             return false;
11800         }
11801         final int offsetTransformed =
11802                 originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
11803         boolean changed = false;
11804 
11805         Layout layout = isShowingHint() ? mHintLayout : mLayout;
11806 
11807         if (layout == null) return changed;
11808 
11809         int line = layout.getLineForOffset(offsetTransformed);
11810 
11811         int grav;
11812 
11813         switch (layout.getParagraphAlignment(line)) {
11814             case ALIGN_LEFT:
11815                 grav = 1;
11816                 break;
11817             case ALIGN_RIGHT:
11818                 grav = -1;
11819                 break;
11820             case ALIGN_NORMAL:
11821                 grav = layout.getParagraphDirection(line);
11822                 break;
11823             case ALIGN_OPPOSITE:
11824                 grav = -layout.getParagraphDirection(line);
11825                 break;
11826             case ALIGN_CENTER:
11827             default:
11828                 grav = 0;
11829                 break;
11830         }
11831 
11832         // We only want to clamp the cursor to fit within the layout width
11833         // in left-to-right modes, because in a right to left alignment,
11834         // we want to scroll to keep the line-right on the screen, as other
11835         // lines are likely to have text flush with the right margin, which
11836         // we want to keep visible.
11837         // A better long-term solution would probably be to measure both
11838         // the full line and a blank-trimmed version, and, for example, use
11839         // the latter measurement for centering and right alignment, but for
11840         // the time being we only implement the cursor clamping in left to
11841         // right where it is most likely to be annoying.
11842         final boolean clamped = grav > 0;
11843         // FIXME: Is it okay to truncate this, or should we round?
11844         final int x = (int) layout.getPrimaryHorizontal(offsetTransformed, clamped);
11845         final int top = layout.getLineTop(line);
11846         final int bottom = layout.getLineTop(line + 1);
11847 
11848         int left = (int) Math.floor(layout.getLineLeft(line));
11849         int right = (int) Math.ceil(layout.getLineRight(line));
11850         int ht = layout.getHeight();
11851 
11852         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
11853         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
11854         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
11855             // If cursor has been clamped, make sure we don't scroll.
11856             right = Math.max(x, left + hspace);
11857         }
11858 
11859         int hslack = (bottom - top) / 2;
11860         int vslack = hslack;
11861 
11862         if (vslack > vspace / 4) {
11863             vslack = vspace / 4;
11864         }
11865         if (hslack > hspace / 4) {
11866             hslack = hspace / 4;
11867         }
11868 
11869         int hs = mScrollX;
11870         int vs = mScrollY;
11871 
11872         if (top - vs < vslack) {
11873             vs = top - vslack;
11874         }
11875         if (bottom - vs > vspace - vslack) {
11876             vs = bottom - (vspace - vslack);
11877         }
11878         if (ht - vs < vspace) {
11879             vs = ht - vspace;
11880         }
11881         if (0 - vs > 0) {
11882             vs = 0;
11883         }
11884 
11885         if (grav != 0) {
11886             if (x - hs < hslack) {
11887                 hs = x - hslack;
11888             }
11889             if (x - hs > hspace - hslack) {
11890                 hs = x - (hspace - hslack);
11891             }
11892         }
11893 
11894         if (grav < 0) {
11895             if (left - hs > 0) {
11896                 hs = left;
11897             }
11898             if (right - hs < hspace) {
11899                 hs = right - hspace;
11900             }
11901         } else if (grav > 0) {
11902             if (right - hs < hspace) {
11903                 hs = right - hspace;
11904             }
11905             if (left - hs > 0) {
11906                 hs = left;
11907             }
11908         } else /* grav == 0 */ {
11909             if (right - left <= hspace) {
11910                 /*
11911                  * If the entire text fits, center it exactly.
11912                  */
11913                 hs = left - (hspace - (right - left)) / 2;
11914             } else if (x > right - hslack) {
11915                 /*
11916                  * If we are near the right edge, keep the right edge
11917                  * at the edge of the view.
11918                  */
11919                 hs = right - hspace;
11920             } else if (x < left + hslack) {
11921                 /*
11922                  * If we are near the left edge, keep the left edge
11923                  * at the edge of the view.
11924                  */
11925                 hs = left;
11926             } else if (left > hs) {
11927                 /*
11928                  * Is there whitespace visible at the left?  Fix it if so.
11929                  */
11930                 hs = left;
11931             } else if (right < hs + hspace) {
11932                 /*
11933                  * Is there whitespace visible at the right?  Fix it if so.
11934                  */
11935                 hs = right - hspace;
11936             } else {
11937                 /*
11938                  * Otherwise, float as needed.
11939                  */
11940                 if (x - hs < hslack) {
11941                     hs = x - hslack;
11942                 }
11943                 if (x - hs > hspace - hslack) {
11944                     hs = x - (hspace - hslack);
11945                 }
11946             }
11947         }
11948 
11949         if (hs != mScrollX || vs != mScrollY) {
11950             if (mScroller == null) {
11951                 scrollTo(hs, vs);
11952             } else {
11953                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
11954                 int dx = hs - mScrollX;
11955                 int dy = vs - mScrollY;
11956 
11957                 if (duration > ANIMATED_SCROLL_GAP) {
11958                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
11959                     awakenScrollBars(mScroller.getDuration());
11960                     invalidate();
11961                 } else {
11962                     if (!mScroller.isFinished()) {
11963                         mScroller.abortAnimation();
11964                     }
11965 
11966                     scrollBy(dx, dy);
11967                 }
11968 
11969                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
11970             }
11971 
11972             changed = true;
11973         }
11974 
11975         if (requestRectWithoutFocus || isFocused()) {
11976             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
11977             // requestRectangleOnScreen() is in terms of content coordinates.
11978 
11979             // The offsets here are to ensure the rectangle we are using is
11980             // within our view bounds, in case the cursor is on the far left
11981             // or right.  If it isn't withing the bounds, then this request
11982             // will be ignored.
11983             if (mTempRect == null) mTempRect = new Rect();
11984             mTempRect.set(x - 2, top, x + 2, bottom);
11985             getInterestingRect(mTempRect, line);
11986             mTempRect.offset(mScrollX, mScrollY);
11987 
11988             if (requestRectangleOnScreen(mTempRect)) {
11989                 changed = true;
11990             }
11991         }
11992 
11993         return changed;
11994     }
11995 
11996     /**
11997      * Move the cursor, if needed, so that it is at an offset that is visible
11998      * to the user.  This will not move the cursor if it represents more than
11999      * one character (a selection range).  This will only work if the
12000      * TextView contains spannable text; otherwise it will do nothing.
12001      *
12002      * @return True if the cursor was actually moved, false otherwise.
12003      */
moveCursorToVisibleOffset()12004     public boolean moveCursorToVisibleOffset() {
12005         if (!(mText instanceof Spannable)) {
12006             return false;
12007         }
12008         int start = getSelectionStartTransformed();
12009         int end = getSelectionEndTransformed();
12010         if (start != end) {
12011             return false;
12012         }
12013 
12014         // First: make sure the line is visible on screen:
12015 
12016         int line = mLayout.getLineForOffset(start);
12017 
12018         final int top = mLayout.getLineTop(line);
12019         final int bottom = mLayout.getLineTop(line + 1);
12020         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
12021         int vslack = (bottom - top) / 2;
12022         if (vslack > vspace / 4) {
12023             vslack = vspace / 4;
12024         }
12025         final int vs = mScrollY;
12026 
12027         if (top < (vs + vslack)) {
12028             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
12029         } else if (bottom > (vspace + vs - vslack)) {
12030             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
12031         }
12032 
12033         // Next: make sure the character is visible on screen:
12034 
12035         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
12036         final int hs = mScrollX;
12037         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
12038         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
12039 
12040         // line might contain bidirectional text
12041         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
12042         final int highChar = leftChar > rightChar ? leftChar : rightChar;
12043 
12044         int newStart = start;
12045         if (newStart < lowChar) {
12046             newStart = lowChar;
12047         } else if (newStart > highChar) {
12048             newStart = highChar;
12049         }
12050 
12051         if (newStart != start) {
12052             Selection.setSelection(mSpannable,
12053                     transformedToOriginal(newStart, OffsetMapping.MAP_STRATEGY_CURSOR));
12054             return true;
12055         }
12056 
12057         return false;
12058     }
12059 
12060     @Override
computeScroll()12061     public void computeScroll() {
12062         if (mScroller != null) {
12063             if (mScroller.computeScrollOffset()) {
12064                 mScrollX = mScroller.getCurrX();
12065                 mScrollY = mScroller.getCurrY();
12066                 invalidateParentCaches();
12067                 postInvalidate();  // So we draw again
12068             }
12069         }
12070     }
12071 
getInterestingRect(Rect r, int line)12072     private void getInterestingRect(Rect r, int line) {
12073         convertFromViewportToContentCoordinates(r);
12074 
12075         // Rectangle can can be expanded on first and last line to take
12076         // padding into account.
12077         // TODO Take left/right padding into account too?
12078         if (line == 0) r.top -= getExtendedPaddingTop();
12079         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
12080     }
12081 
convertFromViewportToContentCoordinates(Rect r)12082     private void convertFromViewportToContentCoordinates(Rect r) {
12083         final int horizontalOffset = viewportToContentHorizontalOffset();
12084         r.left += horizontalOffset;
12085         r.right += horizontalOffset;
12086 
12087         final int verticalOffset = viewportToContentVerticalOffset();
12088         r.top += verticalOffset;
12089         r.bottom += verticalOffset;
12090     }
12091 
convertFromScreenToContentCoordinates(PointF point)12092     private PointF convertFromScreenToContentCoordinates(PointF point) {
12093         int[] screenToViewport = getLocationOnScreen();
12094         PointF copy = new PointF(point);
12095         copy.offset(
12096                 -(screenToViewport[0] + viewportToContentHorizontalOffset()),
12097                 -(screenToViewport[1] + viewportToContentVerticalOffset()));
12098         return copy;
12099     }
12100 
convertFromScreenToContentCoordinates(RectF rect)12101     private RectF convertFromScreenToContentCoordinates(RectF rect) {
12102         int[] screenToViewport = getLocationOnScreen();
12103         RectF copy = new RectF(rect);
12104         copy.offset(
12105                 -(screenToViewport[0] + viewportToContentHorizontalOffset()),
12106                 -(screenToViewport[1] + viewportToContentVerticalOffset()));
12107         return copy;
12108     }
12109 
viewportToContentHorizontalOffset()12110     int viewportToContentHorizontalOffset() {
12111         return getCompoundPaddingLeft() - mScrollX;
12112     }
12113 
12114     @UnsupportedAppUsage
viewportToContentVerticalOffset()12115     int viewportToContentVerticalOffset() {
12116         int offset = getExtendedPaddingTop() - mScrollY;
12117         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
12118             offset += getVerticalOffset(false);
12119         }
12120         return offset;
12121     }
12122 
12123     @Override
debug(int depth)12124     public void debug(int depth) {
12125         super.debug(depth);
12126 
12127         String output = debugIndent(depth);
12128         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
12129                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
12130                 + "} ";
12131 
12132         if (mText != null) {
12133 
12134             output += "mText=\"" + mText + "\" ";
12135             if (mLayout != null) {
12136                 output += "mLayout width=" + mLayout.getWidth()
12137                         + " height=" + mLayout.getHeight();
12138             }
12139         } else {
12140             output += "mText=NULL";
12141         }
12142         Log.d(VIEW_LOG_TAG, output);
12143     }
12144 
12145     /**
12146      * Convenience for {@link Selection#getSelectionStart}.
12147      */
12148     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()12149     public int getSelectionStart() {
12150         return Selection.getSelectionStart(getText());
12151     }
12152 
12153     /**
12154      * Convenience for {@link Selection#getSelectionEnd}.
12155      */
12156     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()12157     public int getSelectionEnd() {
12158         return Selection.getSelectionEnd(getText());
12159     }
12160 
12161     /**
12162      * Calculates the rectangles which should be highlighted to indicate a selection between start
12163      * and end and feeds them into the given {@link Layout.SelectionRectangleConsumer}.
12164      *
12165      * @param start    the starting index of the selection
12166      * @param end      the ending index of the selection
12167      * @param consumer the {@link Layout.SelectionRectangleConsumer} which will receive the
12168      *                 generated rectangles. It will be called every time a rectangle is generated.
12169      * @hide
12170      */
getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer)12171     public void getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer) {
12172         final int transformedStart =
12173                 originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
12174         final int transformedEnd = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
12175         mLayout.getSelection(transformedStart, transformedEnd, consumer);
12176     }
12177 
getSelectionStartTransformed()12178     int getSelectionStartTransformed() {
12179         final int start = getSelectionStart();
12180         if (start < 0) return start;
12181         return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
12182     }
12183 
getSelectionEndTransformed()12184     int getSelectionEndTransformed() {
12185         final int end = getSelectionEnd();
12186         if (end < 0) return end;
12187         return originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
12188     }
12189 
12190     /**
12191      * Return true iff there is a selection of nonzero length inside this text view.
12192      */
hasSelection()12193     public boolean hasSelection() {
12194         final int selectionStart = getSelectionStart();
12195         final int selectionEnd = getSelectionEnd();
12196         final int selectionMin;
12197         final int selectionMax;
12198         if (selectionStart < selectionEnd) {
12199             selectionMin = selectionStart;
12200             selectionMax = selectionEnd;
12201         } else {
12202             selectionMin = selectionEnd;
12203             selectionMax = selectionStart;
12204         }
12205 
12206         return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax;
12207     }
12208 
getSelectedText()12209     String getSelectedText() {
12210         if (!hasSelection()) {
12211             return null;
12212         }
12213 
12214         final int start = getSelectionStart();
12215         final int end = getSelectionEnd();
12216         return String.valueOf(
12217                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
12218     }
12219 
12220     /**
12221      * Sets the properties of this field (lines, horizontally scrolling,
12222      * transformation method) to be for a single-line input.
12223      *
12224      * @attr ref android.R.styleable#TextView_singleLine
12225      */
setSingleLine()12226     public void setSingleLine() {
12227         setSingleLine(true);
12228     }
12229 
12230     /**
12231      * Sets the properties of this field to transform input to ALL CAPS
12232      * display. This may use a "small caps" formatting if available.
12233      * This setting will be ignored if this field is editable or selectable.
12234      *
12235      * This call replaces the current transformation method. Disabling this
12236      * will not necessarily restore the previous behavior from before this
12237      * was enabled.
12238      *
12239      * @see #setTransformationMethod(TransformationMethod)
12240      * @attr ref android.R.styleable#TextView_textAllCaps
12241      */
12242     @android.view.RemotableViewMethod
setAllCaps(boolean allCaps)12243     public void setAllCaps(boolean allCaps) {
12244         if (allCaps) {
12245             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
12246         } else {
12247             setTransformationMethod(null);
12248         }
12249     }
12250 
12251     /**
12252      *
12253      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
12254      * @return Whether the current transformation method is for ALL CAPS.
12255      *
12256      * @see #setAllCaps(boolean)
12257      * @see #setTransformationMethod(TransformationMethod)
12258      */
12259     @InspectableProperty(name = "textAllCaps")
isAllCaps()12260     public boolean isAllCaps() {
12261         final TransformationMethod method = getTransformationMethod();
12262         return method != null && method instanceof AllCapsTransformationMethod;
12263     }
12264 
12265     /**
12266      * If true, sets the properties of this field (number of lines, horizontally scrolling,
12267      * transformation method) to be for a single-line input; if false, restores these to the default
12268      * conditions.
12269      *
12270      * Note that the default conditions are not necessarily those that were in effect prior this
12271      * method, and you may want to reset these properties to your custom values.
12272      *
12273      * Note that due to performance reasons, by setting single line for the EditText, the maximum
12274      * text length is set to 5000 if no other character limitation are applied.
12275      *
12276      * @attr ref android.R.styleable#TextView_singleLine
12277      */
12278     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)12279     public void setSingleLine(boolean singleLine) {
12280         // Could be used, but may break backward compatibility.
12281         // if (mSingleLine == singleLine) return;
12282         setInputTypeSingleLine(singleLine);
12283         applySingleLine(singleLine, true, true, true);
12284     }
12285 
12286     /**
12287      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
12288      * @param singleLine
12289      */
setInputTypeSingleLine(boolean singleLine)12290     private void setInputTypeSingleLine(boolean singleLine) {
12291         if (mEditor != null
12292                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
12293                         == EditorInfo.TYPE_CLASS_TEXT) {
12294             if (singleLine) {
12295                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
12296             } else {
12297                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
12298             }
12299         }
12300     }
12301 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)12302     private void applySingleLine(boolean singleLine, boolean applyTransformation,
12303             boolean changeMaxLines, boolean changeMaxLength) {
12304         mSingleLine = singleLine;
12305 
12306         if (singleLine) {
12307             setLines(1);
12308             setHorizontallyScrolling(true);
12309             if (applyTransformation) {
12310                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
12311             }
12312 
12313             if (!changeMaxLength) return;
12314 
12315             // Single line length filter is only applicable editable text.
12316             if (mBufferType != BufferType.EDITABLE) return;
12317 
12318             final InputFilter[] prevFilters = getFilters();
12319             for (InputFilter filter: getFilters()) {
12320                 // We don't add LengthFilter if already there.
12321                 if (filter instanceof InputFilter.LengthFilter) return;
12322             }
12323 
12324             if (mSingleLineLengthFilter == null) {
12325                 mSingleLineLengthFilter = new InputFilter.LengthFilter(
12326                     MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
12327             }
12328 
12329             final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1];
12330             System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length);
12331             newFilters[prevFilters.length] = mSingleLineLengthFilter;
12332 
12333             setFilters(newFilters);
12334 
12335             // Since filter doesn't apply to existing text, trigger filter by setting text.
12336             setText(getText());
12337         } else {
12338             if (changeMaxLines) {
12339                 setMaxLines(Integer.MAX_VALUE);
12340             }
12341             setHorizontallyScrolling(false);
12342             if (applyTransformation) {
12343                 setTransformationMethod(null);
12344             }
12345 
12346             if (!changeMaxLength) return;
12347 
12348             // Single line length filter is only applicable editable text.
12349             if (mBufferType != BufferType.EDITABLE) return;
12350 
12351             final InputFilter[] prevFilters = getFilters();
12352             if (prevFilters.length == 0) return;
12353 
12354             // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated
12355             // single line char limit filter.
12356             if (mSingleLineLengthFilter == null) return;
12357 
12358             // If we need to remove mSingleLineLengthFilter, we need to allocate another array.
12359             // Since filter list is expected to be small and want to avoid unnecessary array
12360             // allocation, check if there is mSingleLengthFilter first.
12361             int targetIndex = -1;
12362             for (int i = 0; i < prevFilters.length; ++i) {
12363                 if (prevFilters[i] == mSingleLineLengthFilter) {
12364                     targetIndex = i;
12365                     break;
12366                 }
12367             }
12368             if (targetIndex == -1) return;  // not found. Do nothing.
12369 
12370             if (prevFilters.length == 1) {
12371                 setFilters(NO_FILTERS);
12372                 return;
12373             }
12374 
12375             // Create new array which doesn't include mSingleLengthFilter.
12376             final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1];
12377             System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex);
12378             System.arraycopy(
12379                     prevFilters,
12380                     targetIndex + 1,
12381                     newFilters,
12382                     targetIndex,
12383                     prevFilters.length - targetIndex - 1);
12384             setFilters(newFilters);
12385             mSingleLineLengthFilter = null;
12386         }
12387     }
12388 
12389     /**
12390      * Causes words in the text that are longer than the view's width
12391      * to be ellipsized instead of broken in the middle.  You may also
12392      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
12393      * to constrain the text to a single line.  Use <code>null</code>
12394      * to turn off ellipsizing.
12395      *
12396      * If {@link #setMaxLines} has been used to set two or more lines,
12397      * only {@link android.text.TextUtils.TruncateAt#END} and
12398      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
12399      * (other ellipsizing types will not do anything).
12400      *
12401      * @attr ref android.R.styleable#TextView_ellipsize
12402      */
setEllipsize(TextUtils.TruncateAt where)12403     public void setEllipsize(TextUtils.TruncateAt where) {
12404         // TruncateAt is an enum. != comparison is ok between these singleton objects.
12405         if (mEllipsize != where) {
12406             mEllipsize = where;
12407 
12408             if (mLayout != null) {
12409                 nullLayouts();
12410                 requestLayout();
12411                 invalidate();
12412             }
12413         }
12414     }
12415 
12416     /**
12417      * Sets how many times to repeat the marquee animation. Only applied if the
12418      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
12419      *
12420      * @see #getMarqueeRepeatLimit()
12421      *
12422      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
12423      */
setMarqueeRepeatLimit(int marqueeLimit)12424     public void setMarqueeRepeatLimit(int marqueeLimit) {
12425         mMarqueeRepeatLimit = marqueeLimit;
12426     }
12427 
12428     /**
12429      * Gets the number of times the marquee animation is repeated. Only meaningful if the
12430      * TextView has marquee enabled.
12431      *
12432      * @return the number of times the marquee animation is repeated. -1 if the animation
12433      * repeats indefinitely
12434      *
12435      * @see #setMarqueeRepeatLimit(int)
12436      *
12437      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
12438      */
12439     @InspectableProperty
getMarqueeRepeatLimit()12440     public int getMarqueeRepeatLimit() {
12441         return mMarqueeRepeatLimit;
12442     }
12443 
12444     /**
12445      * Returns where, if anywhere, words that are longer than the view
12446      * is wide should be ellipsized.
12447      */
12448     @InspectableProperty
12449     @ViewDebug.ExportedProperty
getEllipsize()12450     public TextUtils.TruncateAt getEllipsize() {
12451         return mEllipsize;
12452     }
12453 
12454     /**
12455      * Set the TextView so that when it takes focus, all the text is
12456      * selected.
12457      *
12458      * @attr ref android.R.styleable#TextView_selectAllOnFocus
12459      */
12460     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)12461     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
12462         createEditorIfNeeded();
12463         mEditor.mSelectAllOnFocus = selectAllOnFocus;
12464 
12465         if (selectAllOnFocus && !(mText instanceof Spannable)) {
12466             setText(mText, BufferType.SPANNABLE);
12467         }
12468     }
12469 
12470     /**
12471      * Set whether the cursor is visible. The default is true. Note that this property only
12472      * makes sense for editable TextView. If IME is consuming the input, the cursor will always be
12473      * invisible, visibility will be updated as the last state when IME does not consume
12474      * the input anymore.
12475      *
12476      * @see #isCursorVisible()
12477      *
12478      * @attr ref android.R.styleable#TextView_cursorVisible
12479      */
12480     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)12481     public void setCursorVisible(boolean visible) {
12482         mCursorVisibleFromAttr = visible;
12483         updateCursorVisibleInternal();
12484     }
12485 
12486     /**
12487      * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput}
12488      * is {@code true}. Otherwise, make the cursor visible.
12489      *
12490      * @param imeConsumesInput {@code true} if IME is consuming the input
12491      *
12492      * @hide
12493      */
setImeConsumesInput(boolean imeConsumesInput)12494     public void setImeConsumesInput(boolean imeConsumesInput) {
12495         mImeIsConsumingInput = imeConsumesInput;
12496         updateCursorVisibleInternal();
12497     }
12498 
updateCursorVisibleInternal()12499     private void updateCursorVisibleInternal()  {
12500         boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput;
12501         if (visible && mEditor == null) return; // visible is the default value with no edit data
12502         createEditorIfNeeded();
12503         if (mEditor.mCursorVisible != visible) {
12504             mEditor.mCursorVisible = visible;
12505             invalidate();
12506 
12507             mEditor.makeBlink();
12508 
12509             // InsertionPointCursorController depends on mCursorVisible
12510             mEditor.prepareCursorControllers();
12511         }
12512     }
12513 
12514     /**
12515      * @return whether or not the cursor is visible (assuming this TextView is editable). This
12516      * method may return {@code false} when the IME is consuming the input even if the
12517      * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)}
12518      * is called.
12519      *
12520      * @see #setCursorVisible(boolean)
12521      *
12522      * @attr ref android.R.styleable#TextView_cursorVisible
12523      */
12524     @InspectableProperty
isCursorVisible()12525     public boolean isCursorVisible() {
12526         // true is the default value
12527         return mEditor == null ? true : mEditor.mCursorVisible;
12528     }
12529 
12530     /**
12531      * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}.
12532      * {@code true} is the default value.
12533      *
12534      * @see #setCursorVisible(boolean)
12535      * @hide
12536      */
isCursorVisibleFromAttr()12537     public boolean isCursorVisibleFromAttr() {
12538         return mCursorVisibleFromAttr;
12539     }
12540 
canMarquee()12541     private boolean canMarquee() {
12542         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
12543         return width > 0 && (mLayout.getLineWidth(0) > width
12544                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
12545                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
12546     }
12547 
12548     /**
12549      * @hide
12550      */
12551     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startMarquee()12552     protected void startMarquee() {
12553         // Do not ellipsize EditText
12554         if (getKeyListener() != null) return;
12555 
12556         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
12557             return;
12558         }
12559 
12560         if ((mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible()
12561                 && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) {
12562 
12563             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
12564                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
12565                 final Layout tmp = mLayout;
12566                 mLayout = mSavedMarqueeModeLayout;
12567                 mSavedMarqueeModeLayout = tmp;
12568                 setHorizontalFadingEdgeEnabled(true);
12569                 requestLayout();
12570                 invalidate();
12571             }
12572 
12573             if (mMarquee == null) mMarquee = new Marquee(this);
12574             mMarquee.start(mMarqueeRepeatLimit);
12575         }
12576     }
12577 
12578     /**
12579      * @hide
12580      */
stopMarquee()12581     protected void stopMarquee() {
12582         if (mMarquee != null && !mMarquee.isStopped()) {
12583             mMarquee.stop();
12584         }
12585 
12586         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
12587             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
12588             final Layout tmp = mSavedMarqueeModeLayout;
12589             mSavedMarqueeModeLayout = mLayout;
12590             mLayout = tmp;
12591             setHorizontalFadingEdgeEnabled(false);
12592             requestLayout();
12593             invalidate();
12594         }
12595     }
12596 
12597     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
startStopMarquee(boolean start)12598     private void startStopMarquee(boolean start) {
12599         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
12600             if (start) {
12601                 startMarquee();
12602             } else {
12603                 stopMarquee();
12604             }
12605         }
12606     }
12607 
12608     /**
12609      * This method is called when the text is changed, in case any subclasses
12610      * would like to know.
12611      *
12612      * Within <code>text</code>, the <code>lengthAfter</code> characters
12613      * beginning at <code>start</code> have just replaced old text that had
12614      * length <code>lengthBefore</code>. It is an error to attempt to make
12615      * changes to <code>text</code> from this callback.
12616      *
12617      * @param text The text the TextView is displaying
12618      * @param start The offset of the start of the range of the text that was
12619      * modified
12620      * @param lengthBefore The length of the former text that has been replaced
12621      * @param lengthAfter The length of the replacement modified text
12622      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)12623     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
12624         // intentionally empty, template pattern method can be overridden by subclasses
12625     }
12626 
12627     /**
12628      * This method is called when the selection has changed, in case any
12629      * subclasses would like to know.
12630      * </p>
12631      * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs
12632      * the accessibility subsystem about the selection change.
12633      * </p>
12634      *
12635      * @param selStart The new selection start location.
12636      * @param selEnd The new selection end location.
12637      */
12638     @CallSuper
onSelectionChanged(int selStart, int selEnd)12639     protected void onSelectionChanged(int selStart, int selEnd) {
12640         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
12641     }
12642 
12643     /**
12644      * Adds a TextWatcher to the list of those whose methods are called
12645      * whenever this TextView's text changes.
12646      * <p>
12647      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
12648      * not called after {@link #setText} calls.  Now, doing {@link #setText}
12649      * if there are any text changed listeners forces the buffer type to
12650      * Editable if it would not otherwise be and does call this method.
12651      */
addTextChangedListener(TextWatcher watcher)12652     public void addTextChangedListener(TextWatcher watcher) {
12653         if (mListeners == null) {
12654             mListeners = new ArrayList<TextWatcher>();
12655         }
12656 
12657         mListeners.add(watcher);
12658     }
12659 
12660     /**
12661      * Removes the specified TextWatcher from the list of those whose
12662      * methods are called
12663      * whenever this TextView's text changes.
12664      */
removeTextChangedListener(TextWatcher watcher)12665     public void removeTextChangedListener(TextWatcher watcher) {
12666         if (mListeners != null) {
12667             int i = mListeners.indexOf(watcher);
12668 
12669             if (i >= 0) {
12670                 mListeners.remove(i);
12671             }
12672         }
12673     }
12674 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)12675     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
12676         if (mListeners != null) {
12677             final ArrayList<TextWatcher> list = mListeners;
12678             final int count = list.size();
12679             for (int i = 0; i < count; i++) {
12680                 list.get(i).beforeTextChanged(text, start, before, after);
12681             }
12682         }
12683 
12684         // The spans that are inside or intersect the modified region no longer make sense
12685         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
12686         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
12687     }
12688 
12689     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)12690     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
12691         if (!(mText instanceof Editable)) return;
12692         Editable text = (Editable) mText;
12693 
12694         T[] spans = text.getSpans(start, end, type);
12695         ArrayList<T> spansToRemove = new ArrayList<>();
12696         for (T span : spans) {
12697             final int spanStart = text.getSpanStart(span);
12698             final int spanEnd = text.getSpanEnd(span);
12699             if (spanEnd == start || spanStart == end) continue;
12700             spansToRemove.add(span);
12701         }
12702         for (T span : spansToRemove) {
12703             text.removeSpan(span);
12704         }
12705     }
12706 
removeAdjacentSuggestionSpans(final int pos)12707     void removeAdjacentSuggestionSpans(final int pos) {
12708         if (!(mText instanceof Editable)) return;
12709         final Editable text = (Editable) mText;
12710 
12711         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
12712         final int length = spans.length;
12713         for (int i = 0; i < length; i++) {
12714             final int spanStart = text.getSpanStart(spans[i]);
12715             final int spanEnd = text.getSpanEnd(spans[i]);
12716             if (spanEnd == pos || spanStart == pos) {
12717                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
12718                     text.removeSpan(spans[i]);
12719                 }
12720             }
12721         }
12722     }
12723 
12724     /**
12725      * Not private so it can be called from an inner class without going
12726      * through a thunk.
12727      */
sendOnTextChanged(CharSequence text, int start, int before, int after)12728     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
12729         if (mListeners != null) {
12730             final ArrayList<TextWatcher> list = mListeners;
12731             final int count = list.size();
12732             for (int i = 0; i < count; i++) {
12733                 list.get(i).onTextChanged(text, start, before, after);
12734             }
12735         }
12736 
12737         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
12738     }
12739 
12740     /**
12741      * Not private so it can be called from an inner class without going
12742      * through a thunk.
12743      */
sendAfterTextChanged(Editable text)12744     void sendAfterTextChanged(Editable text) {
12745         if (mListeners != null) {
12746             final ArrayList<TextWatcher> list = mListeners;
12747             final int count = list.size();
12748             for (int i = 0; i < count; i++) {
12749                 list.get(i).afterTextChanged(text);
12750             }
12751         }
12752 
12753         notifyListeningManagersAfterTextChanged();
12754 
12755         hideErrorIfUnchanged();
12756     }
12757 
12758     /**
12759      * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
12760      * interested on text changes.
12761      */
notifyListeningManagersAfterTextChanged()12762     private void notifyListeningManagersAfterTextChanged() {
12763 
12764         // Autofill
12765         if (isAutofillable()) {
12766             // It is important to not check whether the view is important for autofill
12767             // since the user can trigger autofill manually on not important views.
12768             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
12769             if (afm != null) {
12770                 if (android.view.autofill.Helper.sVerbose) {
12771                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
12772                 }
12773                 afm.notifyValueChanged(TextView.this);
12774             }
12775         }
12776 
12777         notifyContentCaptureTextChanged();
12778     }
12779 
12780     /**
12781      * Notifies the ContentCapture service that the text of the view has changed (only if
12782      * ContentCapture has been notified of this view's existence already).
12783      *
12784      * @hide
12785      */
notifyContentCaptureTextChanged()12786     public void notifyContentCaptureTextChanged() {
12787         // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
12788         // of using isLaidout(), so it's not called in cases where it's laid out but a
12789         // notifyAppeared was not sent.
12790         if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) {
12791             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
12792             if (cm != null && cm.isContentCaptureEnabled()) {
12793                 final ContentCaptureSession session = getContentCaptureSession();
12794                 if (session != null) {
12795                     // TODO(b/111276913): pass flags when edited by user / add CTS test
12796                     session.notifyViewTextChanged(getAutofillId(), getText());
12797                 }
12798             }
12799         }
12800     }
12801 
isAutofillable()12802     private boolean isAutofillable() {
12803         // It is important to not check whether the view is important for autofill
12804         // since the user can trigger autofill manually on not important views.
12805         return getAutofillType() != AUTOFILL_TYPE_NONE;
12806     }
12807 
updateAfterEdit()12808     void updateAfterEdit() {
12809         invalidate();
12810         int curs = getSelectionStart();
12811 
12812         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
12813             registerForPreDraw();
12814         }
12815 
12816         checkForResize();
12817 
12818         if (curs >= 0) {
12819             mHighlightPathBogus = true;
12820             if (mEditor != null) mEditor.makeBlink();
12821             bringPointIntoView(curs);
12822         }
12823     }
12824 
12825     /**
12826      * Not private so it can be called from an inner class without going
12827      * through a thunk.
12828      */
handleTextChanged(CharSequence buffer, int start, int before, int after)12829     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
12830         sLastCutCopyOrTextChangedTime = 0;
12831 
12832         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
12833         if (ims == null || ims.mBatchEditNesting == 0) {
12834             updateAfterEdit();
12835         }
12836         if (ims != null) {
12837             ims.mContentChanged = true;
12838             if (ims.mChangedStart < 0) {
12839                 ims.mChangedStart = start;
12840                 ims.mChangedEnd = start + before;
12841             } else {
12842                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
12843                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
12844             }
12845             ims.mChangedDelta += after - before;
12846         }
12847         resetErrorChangedFlag();
12848         sendOnTextChanged(buffer, start, before, after);
12849         onTextChanged(buffer, start, before, after);
12850 
12851         mHideHint = false;
12852         clearGesturePreviewHighlight();
12853     }
12854 
12855     /**
12856      * Not private so it can be called from an inner class without going
12857      * through a thunk.
12858      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)12859     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
12860         // XXX Make the start and end move together if this ends up
12861         // spending too much time invalidating.
12862 
12863         boolean selChanged = false;
12864         int newSelStart = -1, newSelEnd = -1;
12865 
12866         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
12867 
12868         if (what == Selection.SELECTION_END) {
12869             selChanged = true;
12870             newSelEnd = newStart;
12871 
12872             if (oldStart >= 0 || newStart >= 0) {
12873                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
12874                 checkForResize();
12875                 registerForPreDraw();
12876                 if (mEditor != null) mEditor.makeBlink();
12877             }
12878         }
12879 
12880         if (what == Selection.SELECTION_START) {
12881             selChanged = true;
12882             newSelStart = newStart;
12883 
12884             if (oldStart >= 0 || newStart >= 0) {
12885                 int end = Selection.getSelectionEnd(buf);
12886                 invalidateCursor(end, oldStart, newStart);
12887             }
12888         }
12889 
12890         if (selChanged) {
12891             clearGesturePreviewHighlight();
12892             mHighlightPathBogus = true;
12893             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
12894 
12895             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
12896                 if (newSelStart < 0) {
12897                     newSelStart = Selection.getSelectionStart(buf);
12898                 }
12899                 if (newSelEnd < 0) {
12900                     newSelEnd = Selection.getSelectionEnd(buf);
12901                 }
12902 
12903                 if (mEditor != null) {
12904                     mEditor.refreshTextActionMode();
12905                     if (!hasSelection()
12906                             && mEditor.getTextActionMode() == null && hasTransientState()) {
12907                         // User generated selection has been removed.
12908                         setHasTransientState(false);
12909                     }
12910                 }
12911                 onSelectionChanged(newSelStart, newSelEnd);
12912             }
12913         }
12914 
12915         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
12916                 || what instanceof CharacterStyle) {
12917             if (ims == null || ims.mBatchEditNesting == 0) {
12918                 invalidate();
12919                 mHighlightPathBogus = true;
12920                 checkForResize();
12921             } else {
12922                 ims.mContentChanged = true;
12923             }
12924             if (mEditor != null) {
12925                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
12926                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
12927                 mEditor.invalidateHandlesAndActionMode();
12928             }
12929         }
12930 
12931         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
12932             mHighlightPathBogus = true;
12933             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
12934                 ims.mSelectionModeChanged = true;
12935             }
12936 
12937             if (Selection.getSelectionStart(buf) >= 0) {
12938                 if (ims == null || ims.mBatchEditNesting == 0) {
12939                     invalidateCursor();
12940                 } else {
12941                     ims.mCursorChanged = true;
12942                 }
12943             }
12944         }
12945 
12946         if (what instanceof ParcelableSpan) {
12947             // If this is a span that can be sent to a remote process,
12948             // the current extract editor would be interested in it.
12949             if (ims != null && ims.mExtractedTextRequest != null) {
12950                 if (ims.mBatchEditNesting != 0) {
12951                     if (oldStart >= 0) {
12952                         if (ims.mChangedStart > oldStart) {
12953                             ims.mChangedStart = oldStart;
12954                         }
12955                         if (ims.mChangedStart > oldEnd) {
12956                             ims.mChangedStart = oldEnd;
12957                         }
12958                     }
12959                     if (newStart >= 0) {
12960                         if (ims.mChangedStart > newStart) {
12961                             ims.mChangedStart = newStart;
12962                         }
12963                         if (ims.mChangedStart > newEnd) {
12964                             ims.mChangedStart = newEnd;
12965                         }
12966                     }
12967                 } else {
12968                     if (DEBUG_EXTRACT) {
12969                         Log.v(LOG_TAG, "Span change outside of batch: "
12970                                 + oldStart + "-" + oldEnd + ","
12971                                 + newStart + "-" + newEnd + " " + what);
12972                     }
12973                     ims.mContentChanged = true;
12974                 }
12975             }
12976         }
12977 
12978         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
12979                 && what instanceof SpellCheckSpan) {
12980             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
12981         }
12982     }
12983 
12984     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)12985     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
12986         if (isTemporarilyDetached()) {
12987             // If we are temporarily in the detach state, then do nothing.
12988             super.onFocusChanged(focused, direction, previouslyFocusedRect);
12989             return;
12990         }
12991 
12992         mHideHint = false;
12993 
12994         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
12995 
12996         if (focused) {
12997             if (mSpannable != null) {
12998                 MetaKeyKeyListener.resetMetaState(mSpannable);
12999             }
13000         }
13001 
13002         startStopMarquee(focused);
13003 
13004         if (mTransformation != null) {
13005             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
13006         }
13007 
13008         super.onFocusChanged(focused, direction, previouslyFocusedRect);
13009     }
13010 
13011     @Override
onWindowFocusChanged(boolean hasWindowFocus)13012     public void onWindowFocusChanged(boolean hasWindowFocus) {
13013         super.onWindowFocusChanged(hasWindowFocus);
13014 
13015         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
13016 
13017         startStopMarquee(hasWindowFocus);
13018     }
13019 
13020     @Override
onVisibilityChanged(View changedView, int visibility)13021     protected void onVisibilityChanged(View changedView, int visibility) {
13022         super.onVisibilityChanged(changedView, visibility);
13023         if (mEditor != null && visibility != VISIBLE) {
13024             mEditor.hideCursorAndSpanControllers();
13025             stopTextActionMode();
13026         }
13027     }
13028 
13029     @Override
onVisibilityAggregated(boolean isVisible)13030     public void onVisibilityAggregated(boolean isVisible) {
13031         super.onVisibilityAggregated(isVisible);
13032         startStopMarquee(isVisible);
13033     }
13034 
13035     /**
13036      * Use {@link BaseInputConnection#removeComposingSpans
13037      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
13038      * state from this text view.
13039      */
clearComposingText()13040     public void clearComposingText() {
13041         if (mText instanceof Spannable) {
13042             BaseInputConnection.removeComposingSpans(mSpannable);
13043         }
13044     }
13045 
13046     @Override
setSelected(boolean selected)13047     public void setSelected(boolean selected) {
13048         boolean wasSelected = isSelected();
13049 
13050         super.setSelected(selected);
13051 
13052         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
13053             if (selected) {
13054                 startMarquee();
13055             } else {
13056                 stopMarquee();
13057             }
13058         }
13059     }
13060 
13061     /**
13062      * Called from onTouchEvent() to prevent the touches by secondary fingers.
13063      * Dragging on handles can revise cursor/selection, so can dragging on the text view.
13064      * This method is a lock to avoid processing multiple fingers on both text view and handles.
13065      * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work.
13066      *
13067      * @param event The motion event that is being handled and carries the pointer info.
13068      * @param fromHandleView true if the event is delivered to selection handle or insertion
13069      * handle; false if this event is delivered to TextView.
13070      * @return Returns true to indicate that onTouchEvent() can continue processing the motion
13071      * event, otherwise false.
13072      *  - Always returns true for the first finger.
13073      *  - For secondary fingers, if the first or current finger is from TextView, returns false.
13074      *    This is to make touch mutually exclusive between the TextView and the handles, but
13075      *    not among the handles.
13076      */
isFromPrimePointer(MotionEvent event, boolean fromHandleView)13077     boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) {
13078         boolean res = true;
13079         if (mPrimePointerId == NO_POINTER_ID)  {
13080             mPrimePointerId = event.getPointerId(0);
13081             mIsPrimePointerFromHandleView = fromHandleView;
13082         } else if (mPrimePointerId != event.getPointerId(0)) {
13083             res = mIsPrimePointerFromHandleView && fromHandleView;
13084         }
13085         if (event.getActionMasked() == MotionEvent.ACTION_UP
13086             || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
13087             mPrimePointerId = -1;
13088         }
13089         return res;
13090     }
13091 
13092     @Override
onTouchEvent(MotionEvent event)13093     public boolean onTouchEvent(MotionEvent event) {
13094         if (DEBUG_CURSOR) {
13095             logCursor("onTouchEvent", "%d: %s (%f,%f)",
13096                     event.getSequenceNumber(),
13097                     MotionEvent.actionToString(event.getActionMasked()),
13098                     event.getX(), event.getY());
13099         }
13100         mLastInputSource = event.getSource();
13101         final int action = event.getActionMasked();
13102         if (mEditor != null) {
13103             if (!isFromPrimePointer(event, false)) {
13104                 return true;
13105             }
13106 
13107             mEditor.onTouchEvent(event);
13108 
13109             if (mEditor.mInsertionPointCursorController != null
13110                     && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
13111                 return true;
13112             }
13113             if (mEditor.mSelectionModifierCursorController != null
13114                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
13115                 return true;
13116             }
13117         }
13118 
13119         final boolean superResult = super.onTouchEvent(event);
13120         if (DEBUG_CURSOR) {
13121             logCursor("onTouchEvent", "superResult=%s", superResult);
13122         }
13123 
13124         /*
13125          * Don't handle the release after a long press, because it will move the selection away from
13126          * whatever the menu action was trying to affect. If the long press should have triggered an
13127          * insertion action mode, we can now actually show it.
13128          */
13129         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
13130             mEditor.mDiscardNextActionUp = false;
13131             if (DEBUG_CURSOR) {
13132                 logCursor("onTouchEvent", "release after long press detected");
13133             }
13134             if (mEditor.mIsInsertionActionModeStartPending) {
13135                 mEditor.startInsertionActionMode();
13136                 mEditor.mIsInsertionActionModeStartPending = false;
13137             }
13138             return superResult;
13139         }
13140 
13141         // At this point, the event is not a long press, otherwise it would be handled above.
13142         if (Flags.handwritingEndOfLineTap() && action == MotionEvent.ACTION_UP
13143                 && shouldStartHandwritingForEndOfLineTap(event)) {
13144             InputMethodManager imm = getInputMethodManager();
13145             if (imm != null) {
13146                 imm.startStylusHandwriting(this);
13147                 return true;
13148             }
13149         }
13150 
13151         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
13152                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
13153 
13154         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
13155                 && mText instanceof Spannable && mLayout != null) {
13156             boolean handled = false;
13157 
13158             if (mMovement != null) {
13159                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
13160             }
13161 
13162             final boolean textIsSelectable = isTextSelectable();
13163             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
13164                 // The LinkMovementMethod which should handle taps on links has not been installed
13165                 // on non editable text that support text selection.
13166                 // We reproduce its behavior here to open links for these.
13167                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
13168                     getSelectionEnd(), ClickableSpan.class);
13169 
13170                 if (links.length > 0) {
13171                     links[0].onClick(this);
13172                     handled = true;
13173                 }
13174             }
13175 
13176             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
13177                 // Show the IME, except when selecting in read-only text.
13178                 final InputMethodManager imm = getInputMethodManager();
13179                 viewClicked(imm);
13180                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null
13181                         && !showAutofillDialog()) {
13182                     imm.showSoftInput(this, 0);
13183                 }
13184 
13185                 // The above condition ensures that the mEditor is not null
13186                 mEditor.onTouchUpEvent(event);
13187 
13188                 handled = true;
13189             }
13190 
13191             if (handled) {
13192                 return true;
13193             }
13194         }
13195 
13196         return superResult;
13197     }
13198 
13199     /**
13200      * If handwriting is supported, the TextView is already focused and not empty, and the cursor is
13201      * at the end of a line, a stylus tap after the end of the line will trigger handwriting.
13202      */
shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent)13203     private boolean shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent) {
13204         if (!onCheckIsTextEditor()
13205                 || !isEnabled()
13206                 || !isAutoHandwritingEnabled()
13207                 || TextUtils.isEmpty(mText)
13208                 || didTouchFocusSelect()
13209                 || mLayout == null
13210                 || !actionUpEvent.isStylusPointer()) {
13211             return false;
13212         }
13213         int cursorOffset = getSelectionStart();
13214         if (cursorOffset < 0 || getSelectionEnd() != cursorOffset) {
13215             return false;
13216         }
13217         int cursorLine = mLayout.getLineForOffset(cursorOffset);
13218         int cursorLineEnd = mLayout.getLineEnd(cursorLine);
13219         if (cursorLine != mLayout.getLineCount() - 1) {
13220             cursorLineEnd--;
13221         }
13222         if (cursorLineEnd != cursorOffset) {
13223             return false;
13224         }
13225         // Check that the stylus down point is within the same line as the cursor.
13226         if (getLineAtCoordinate(actionUpEvent.getY()) != cursorLine) {
13227             return false;
13228         }
13229         // Check that the stylus down point is after the end of the line.
13230         float localX = convertToLocalHorizontalCoordinate(actionUpEvent.getX());
13231         if (mLayout.getParagraphDirection(cursorLine) == Layout.DIR_RIGHT_TO_LEFT
13232                 ? localX >= mLayout.getLineLeft(cursorLine)
13233                 : localX <= mLayout.getLineRight(cursorLine)) {
13234             return false;
13235         }
13236         return isStylusHandwritingAvailable();
13237     }
13238 
13239     /**
13240      * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
13241      *
13242      * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
13243      * @hide
13244      */
showUIForTouchScreen()13245     public final boolean showUIForTouchScreen() {
13246         return (mLastInputSource & InputDevice.SOURCE_TOUCHSCREEN)
13247                 == InputDevice.SOURCE_TOUCHSCREEN;
13248     }
13249 
13250     /**
13251      * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI.
13252      * If autofill suggestions are available when the user clicks on a field that supports filling
13253      * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area
13254      * to display the datasets, so it is easy for users to pay attention to the datasets and
13255      * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better
13256      * experience is not to show the IME if there is a fill dialog.
13257      */
showAutofillDialog()13258     private boolean showAutofillDialog() {
13259         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
13260         if (afm != null) {
13261             return afm.showAutofillDialog(this);
13262         }
13263         return false;
13264     }
13265 
13266     @Override
onGenericMotionEvent(MotionEvent event)13267     public boolean onGenericMotionEvent(MotionEvent event) {
13268         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
13269             try {
13270                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
13271                     return true;
13272                 }
13273             } catch (AbstractMethodError ex) {
13274                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
13275                 // Ignore its absence in case third party applications implemented the
13276                 // interface directly.
13277             }
13278         }
13279         return super.onGenericMotionEvent(event);
13280     }
13281 
13282     @Override
onCreateContextMenu(ContextMenu menu)13283     protected void onCreateContextMenu(ContextMenu menu) {
13284         if (mEditor != null) {
13285             mEditor.onCreateContextMenu(menu);
13286         }
13287     }
13288 
13289     @Override
showContextMenu()13290     public boolean showContextMenu() {
13291         if (mEditor != null) {
13292             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
13293         }
13294         return super.showContextMenu();
13295     }
13296 
13297     @Override
showContextMenu(float x, float y)13298     public boolean showContextMenu(float x, float y) {
13299         if (mEditor != null) {
13300             mEditor.setContextMenuAnchor(x, y);
13301         }
13302         return super.showContextMenu(x, y);
13303     }
13304 
13305     /**
13306      * @return True iff this TextView contains a text that can be edited, or if this is
13307      * a selectable TextView.
13308      */
13309     @UnsupportedAppUsage
isTextEditable()13310     boolean isTextEditable() {
13311         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
13312     }
13313 
13314     /**
13315      * @return true if this TextView could be filled by an Autofill service. Note that disabled
13316      * fields can still be filled.
13317      */
13318     @UnsupportedAppUsage
isTextAutofillable()13319     boolean isTextAutofillable() {
13320         return mText instanceof Editable && onCheckIsTextEditor();
13321     }
13322 
13323     /**
13324      * Returns true, only while processing a touch gesture, if the initial
13325      * touch down event caused focus to move to the text view and as a result
13326      * its selection changed.  Only valid while processing the touch gesture
13327      * of interest, in an editable text view.
13328      */
didTouchFocusSelect()13329     public boolean didTouchFocusSelect() {
13330         return mEditor != null && mEditor.mTouchFocusSelected;
13331     }
13332 
13333     @Override
cancelLongPress()13334     public void cancelLongPress() {
13335         super.cancelLongPress();
13336         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
13337     }
13338 
13339     @Override
onTrackballEvent(MotionEvent event)13340     public boolean onTrackballEvent(MotionEvent event) {
13341         if (mMovement != null && mSpannable != null && mLayout != null) {
13342             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
13343                 return true;
13344             }
13345         }
13346 
13347         return super.onTrackballEvent(event);
13348     }
13349 
13350     /**
13351      * Sets the Scroller used for producing a scrolling animation
13352      *
13353      * @param s A Scroller instance
13354      */
setScroller(Scroller s)13355     public void setScroller(Scroller s) {
13356         mScroller = s;
13357     }
13358 
13359     @Override
getLeftFadingEdgeStrength()13360     protected float getLeftFadingEdgeStrength() {
13361         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
13362             final Marquee marquee = mMarquee;
13363             if (marquee.shouldDrawLeftFade()) {
13364                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
13365             } else {
13366                 return 0.0f;
13367             }
13368         } else if (getLineCount() == 1) {
13369             final float lineLeft = getLayout().getLineLeft(0);
13370             if (lineLeft > mScrollX) return 0.0f;
13371             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
13372         }
13373         return super.getLeftFadingEdgeStrength();
13374     }
13375 
13376     @Override
getRightFadingEdgeStrength()13377     protected float getRightFadingEdgeStrength() {
13378         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
13379             final Marquee marquee = mMarquee;
13380             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
13381         } else if (getLineCount() == 1) {
13382             final float rightEdge = mScrollX +
13383                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
13384             final float lineRight = getLayout().getLineRight(0);
13385             if (lineRight < rightEdge) return 0.0f;
13386             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
13387         }
13388         return super.getRightFadingEdgeStrength();
13389     }
13390 
13391     /**
13392      * Calculates the fading edge strength as the ratio of the distance between two
13393      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
13394      * value for the distance calculation.
13395      *
13396      * @param position1 A horizontal position.
13397      * @param position2 A horizontal position.
13398      * @return Fading edge strength between [0.0f, 1.0f].
13399      */
13400     @FloatRange(from = 0.0, to = 1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)13401     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
13402         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
13403         if (horizontalFadingEdgeLength == 0) return 0.0f;
13404         final float diff = Math.abs(position1 - position2);
13405         if (diff > horizontalFadingEdgeLength) return 1.0f;
13406         return diff / horizontalFadingEdgeLength;
13407     }
13408 
isMarqueeFadeEnabled()13409     private boolean isMarqueeFadeEnabled() {
13410         return mEllipsize == TextUtils.TruncateAt.MARQUEE
13411                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
13412     }
13413 
13414     @Override
computeHorizontalScrollRange()13415     protected int computeHorizontalScrollRange() {
13416         if (mLayout != null) {
13417             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
13418                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
13419         }
13420 
13421         return super.computeHorizontalScrollRange();
13422     }
13423 
13424     @Override
computeVerticalScrollRange()13425     protected int computeVerticalScrollRange() {
13426         if (mLayout != null) {
13427             return mLayout.getHeight();
13428         }
13429         return super.computeVerticalScrollRange();
13430     }
13431 
13432     @Override
computeVerticalScrollExtent()13433     protected int computeVerticalScrollExtent() {
13434         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
13435     }
13436 
13437     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)13438     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
13439         super.findViewsWithText(outViews, searched, flags);
13440         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
13441                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
13442             String searchedLowerCase = searched.toString().toLowerCase();
13443             String textLowerCase = mText.toString().toLowerCase();
13444             if (textLowerCase.contains(searchedLowerCase)) {
13445                 outViews.add(this);
13446             }
13447         }
13448     }
13449 
13450     /**
13451      * Type of the text buffer that defines the characteristics of the text such as static,
13452      * styleable, or editable.
13453      */
13454     public enum BufferType {
13455         NORMAL, SPANNABLE, EDITABLE
13456     }
13457 
13458     /**
13459      * Returns the TextView_textColor attribute from the TypedArray, if set, or
13460      * the TextAppearance_textColor from the TextView_textAppearance attribute,
13461      * if TextView_textColor was not set directly.
13462      *
13463      * @removed
13464      */
getTextColors(Context context, TypedArray attrs)13465     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
13466         if (attrs == null) {
13467             // Preserve behavior prior to removal of this API.
13468             throw new NullPointerException();
13469         }
13470 
13471         // It's not safe to use this method from apps. The parameter 'attrs'
13472         // must have been obtained using the TextView filter array which is not
13473         // available to the SDK. As such, we grab a default TypedArray with the
13474         // right filter instead here.
13475         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
13476         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
13477         if (colors == null) {
13478             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
13479             if (ap != 0) {
13480                 final TypedArray appearance = context.obtainStyledAttributes(
13481                         ap, R.styleable.TextAppearance);
13482                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
13483                 appearance.recycle();
13484             }
13485         }
13486         a.recycle();
13487 
13488         return colors;
13489     }
13490 
13491     /**
13492      * Returns the default color from the TextView_textColor attribute from the
13493      * AttributeSet, if set, or the default color from the
13494      * TextAppearance_textColor from the TextView_textAppearance attribute, if
13495      * TextView_textColor was not set directly.
13496      *
13497      * @removed
13498      */
getTextColor(Context context, TypedArray attrs, int def)13499     public static int getTextColor(Context context, TypedArray attrs, int def) {
13500         final ColorStateList colors = getTextColors(context, attrs);
13501         if (colors == null) {
13502             return def;
13503         } else {
13504             return colors.getDefaultColor();
13505         }
13506     }
13507 
13508     @Override
onKeyShortcut(int keyCode, KeyEvent event)13509     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
13510         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
13511             // Handle Ctrl-only shortcuts.
13512             switch (keyCode) {
13513                 case KeyEvent.KEYCODE_A:
13514                     if (canSelectText()) {
13515                         return onTextContextMenuItem(ID_SELECT_ALL);
13516                     }
13517                     break;
13518                 case KeyEvent.KEYCODE_Z:
13519                     if (canUndo()) {
13520                         return onTextContextMenuItem(ID_UNDO);
13521                     }
13522                     break;
13523                 case KeyEvent.KEYCODE_X:
13524                     if (canCut()) {
13525                         return onTextContextMenuItem(ID_CUT);
13526                     }
13527                     break;
13528                 case KeyEvent.KEYCODE_C:
13529                     if (canCopy()) {
13530                         return onTextContextMenuItem(ID_COPY);
13531                     }
13532                     break;
13533                 case KeyEvent.KEYCODE_V:
13534                     if (canPaste()) {
13535                         return onTextContextMenuItem(ID_PASTE);
13536                     }
13537                     break;
13538                 case KeyEvent.KEYCODE_Y:
13539                     if (canRedo()) {
13540                         return onTextContextMenuItem(ID_REDO);
13541                     }
13542                     break;
13543             }
13544         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
13545             // Handle Ctrl-Shift shortcuts.
13546             switch (keyCode) {
13547                 case KeyEvent.KEYCODE_Z:
13548                     if (canRedo()) {
13549                         return onTextContextMenuItem(ID_REDO);
13550                     }
13551                     break;
13552                 case KeyEvent.KEYCODE_V:
13553                     if (canPaste()) {
13554                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
13555                     }
13556             }
13557         }
13558         return super.onKeyShortcut(keyCode, event);
13559     }
13560 
13561     /**
13562      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
13563      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
13564      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
13565      * sufficient.
13566      */
canSelectText()13567     boolean canSelectText() {
13568         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
13569     }
13570 
13571     /**
13572      * Test based on the <i>intrinsic</i> charateristics of the TextView.
13573      * The text must be spannable and the movement method must allow for arbitary selection.
13574      *
13575      * See also {@link #canSelectText()}.
13576      */
textCanBeSelected()13577     boolean textCanBeSelected() {
13578         // prepareCursorController() relies on this method.
13579         // If you change this condition, make sure prepareCursorController is called anywhere
13580         // the value of this condition might be changed.
13581         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
13582         return isTextEditable()
13583                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
13584     }
13585 
13586     @UnsupportedAppUsage
getTextServicesLocale(boolean allowNullLocale)13587     private Locale getTextServicesLocale(boolean allowNullLocale) {
13588         // Start fetching the text services locale asynchronously.
13589         updateTextServicesLocaleAsync();
13590         // If !allowNullLocale and there is no cached text services locale, just return the default
13591         // locale.
13592         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
13593                 : mCurrentSpellCheckerLocaleCache;
13594     }
13595 
13596     /**
13597      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
13598      * this {@link TextView}.
13599      *
13600      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
13601      * other apps may need to set this so that the system can user right user's resources and
13602      * services such as input methods and spell checkers.</p>
13603      *
13604      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
13605      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
13606      * @hide
13607      */
13608     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
setTextOperationUser(@ullable UserHandle user)13609     public final void setTextOperationUser(@Nullable UserHandle user) {
13610         if (Objects.equals(mTextOperationUser, user)) {
13611             return;
13612         }
13613         if (user != null && !Process.myUserHandle().equals(user)) {
13614             // Just for preventing people from accidentally using this hidden API without
13615             // the required permission.  The same permission is also checked in the system server.
13616             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
13617                     != PackageManager.PERMISSION_GRANTED) {
13618                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
13619                         + " userId=" + user.getIdentifier()
13620                         + " callingUserId" + UserHandle.myUserId());
13621             }
13622         }
13623         mTextOperationUser = user;
13624         // Invalidate some resources
13625         mCurrentSpellCheckerLocaleCache = null;
13626         if (mEditor != null) {
13627             mEditor.onTextOperationUserChanged();
13628         }
13629     }
13630 
13631     @Override
isAutoHandwritingEnabled()13632     public boolean isAutoHandwritingEnabled() {
13633         return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType();
13634     }
13635 
13636     /** @hide */
13637     @Override
shouldTrackHandwritingArea()13638     public boolean shouldTrackHandwritingArea() {
13639         // The handwriting initiator tracks all editable TextViews regardless of whether handwriting
13640         // is supported, so that it can show an error message for unsupported editable TextViews.
13641         return super.shouldTrackHandwritingArea()
13642                 || (Flags.handwritingUnsupportedMessage() && onCheckIsTextEditor());
13643     }
13644 
13645     /** @hide */
13646     @Override
isStylusHandwritingAvailable()13647     public boolean isStylusHandwritingAvailable() {
13648         if (mTextOperationUser == null) {
13649             return super.isStylusHandwritingAvailable();
13650         }
13651         final InputMethodManager imm = getInputMethodManager();
13652         return imm.isStylusHandwritingAvailableAsUser(mTextOperationUser);
13653     }
13654 
13655     @Nullable
getTextServicesManagerForUser()13656     final TextServicesManager getTextServicesManagerForUser() {
13657         return getServiceManagerForUser("android", TextServicesManager.class);
13658     }
13659 
13660     @Nullable
getClipboardManagerForUser()13661     final ClipboardManager getClipboardManagerForUser() {
13662         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
13663     }
13664 
13665     @Nullable
getTextClassificationManagerForUser()13666     final TextClassificationManager getTextClassificationManagerForUser() {
13667         return getServiceManagerForUser(
13668                 getContext().getPackageName(), TextClassificationManager.class);
13669     }
13670 
13671     @Nullable
getServiceManagerForUser(String packageName, Class<T> managerClazz)13672     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
13673         if (mTextOperationUser == null) {
13674             return getContext().getSystemService(managerClazz);
13675         }
13676         try {
13677             Context context = getContext().createPackageContextAsUser(
13678                     packageName, 0 /* flags */, mTextOperationUser);
13679             return context.getSystemService(managerClazz);
13680         } catch (PackageManager.NameNotFoundException e) {
13681             return null;
13682         }
13683     }
13684 
13685     /**
13686      * Starts {@link Activity} as a text-operation user if it is specified with
13687      * {@link #setTextOperationUser(UserHandle)}.
13688      *
13689      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
13690      *
13691      * @param intent The description of the activity to start.
13692      */
startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)13693     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
13694         if (mTextOperationUser != null) {
13695             getContext().startActivityAsUser(intent, mTextOperationUser);
13696         } else {
13697             getContext().startActivity(intent);
13698         }
13699     }
13700 
13701     /**
13702      * This is a temporary method. Future versions may support multi-locale text.
13703      * Caveat: This method may not return the latest text services locale, but this should be
13704      * acceptable and it's more important to make this method asynchronous.
13705      *
13706      * @return The locale that should be used for a word iterator
13707      * in this TextView, based on the current spell checker settings,
13708      * the current IME's locale, or the system default locale.
13709      * Please note that a word iterator in this TextView is different from another word iterator
13710      * used by SpellChecker.java of TextView. This method should be used for the former.
13711      * @hide
13712      */
13713     // TODO: Support multi-locale
13714     // TODO: Update the text services locale immediately after the keyboard locale is switched
13715     // by catching intent of keyboard switch event
getTextServicesLocale()13716     public Locale getTextServicesLocale() {
13717         return getTextServicesLocale(false /* allowNullLocale */);
13718     }
13719 
13720     /**
13721      * @return {@code true} if this TextView is specialized for showing and interacting with the
13722      * extracted text in a full-screen input method.
13723      * @hide
13724      */
isInExtractedMode()13725     public boolean isInExtractedMode() {
13726         return false;
13727     }
13728 
13729     /**
13730      * @return {@code true} if this widget supports auto-sizing text and has been configured to
13731      * auto-size.
13732      */
isAutoSizeEnabled()13733     private boolean isAutoSizeEnabled() {
13734         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
13735     }
13736 
13737     /**
13738      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
13739      * @hide
13740      */
supportsAutoSizeText()13741     protected boolean supportsAutoSizeText() {
13742         return true;
13743     }
13744 
13745     /**
13746      * This is a temporary method. Future versions may support multi-locale text.
13747      * Caveat: This method may not return the latest spell checker locale, but this should be
13748      * acceptable and it's more important to make this method asynchronous.
13749      *
13750      * @return The locale that should be used for a spell checker in this TextView,
13751      * based on the current spell checker settings, the current IME's locale, or the system default
13752      * locale.
13753      * @hide
13754      */
getSpellCheckerLocale()13755     public Locale getSpellCheckerLocale() {
13756         return getTextServicesLocale(true /* allowNullLocale */);
13757     }
13758 
updateTextServicesLocaleAsync()13759     private void updateTextServicesLocaleAsync() {
13760         // AsyncTask.execute() uses a serial executor which means we don't have
13761         // to lock around updateTextServicesLocaleLocked() to prevent it from
13762         // being executed n times in parallel.
13763         AsyncTask.execute(new Runnable() {
13764             @Override
13765             public void run() {
13766                 updateTextServicesLocaleLocked();
13767             }
13768         });
13769     }
13770 
13771     @UnsupportedAppUsage
updateTextServicesLocaleLocked()13772     private void updateTextServicesLocaleLocked() {
13773         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
13774         if (textServicesManager == null) {
13775             return;
13776         }
13777         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
13778         final Locale locale;
13779         if (subtype != null) {
13780             locale = subtype.getLocaleObject();
13781         } else {
13782             locale = null;
13783         }
13784         mCurrentSpellCheckerLocaleCache = locale;
13785     }
13786 
onLocaleChanged()13787     void onLocaleChanged() {
13788         mEditor.onLocaleChanged();
13789     }
13790 
13791     /**
13792      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
13793      * Made available to achieve a consistent behavior.
13794      * @hide
13795      */
getWordIterator()13796     public WordIterator getWordIterator() {
13797         if (mEditor != null) {
13798             return mEditor.getWordIterator();
13799         } else {
13800             return null;
13801         }
13802     }
13803 
13804     /** @hide */
13805     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)13806     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
13807         super.onPopulateAccessibilityEventInternal(event);
13808 
13809         if (this.isAccessibilityDataSensitive() && !event.isAccessibilityDataSensitive()) {
13810             // This view's accessibility data is sensitive, but another view that generated this
13811             // event is not, so don't append this view's text to the event in order to prevent
13812             // sharing this view's contents with non-accessibility-tool services.
13813             return;
13814         }
13815 
13816         final CharSequence text = getTextForAccessibility();
13817         if (!TextUtils.isEmpty(text)) {
13818             event.getText().add(text);
13819         }
13820     }
13821 
13822     @Override
getAccessibilityClassName()13823     public CharSequence getAccessibilityClassName() {
13824         return TextView.class.getName();
13825     }
13826 
13827     /** @hide */
13828     @Override
onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)13829     protected void onProvideStructure(@NonNull ViewStructure structure,
13830             @ViewStructureType int viewFor, int flags) {
13831         super.onProvideStructure(structure, viewFor, flags);
13832 
13833         final boolean isPassword = hasPasswordTransformationMethod()
13834                 || isPasswordInputType(getInputType());
13835         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
13836                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13837             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13838                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
13839             }
13840             if (mTextId != Resources.ID_NULL) {
13841                 try {
13842                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
13843                 } catch (Resources.NotFoundException e) {
13844                     if (android.view.autofill.Helper.sVerbose) {
13845                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
13846                                 + mTextId + ": " + e.getMessage());
13847                     }
13848                 }
13849             }
13850             String[] mimeTypes = getReceiveContentMimeTypes();
13851             if (mimeTypes == null && mEditor != null) {
13852                 // If the app hasn't set a listener for receiving content on this view (ie,
13853                 // getReceiveContentMimeTypes() returns null), check if it implements the
13854                 // keyboard image API and, if possible, use those MIME types as fallback.
13855                 // This fallback is only in place for autofill, not other mechanisms for
13856                 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER
13857                 // in TextViewOnReceiveContentListener for more info.
13858                 mimeTypes = mEditor.getDefaultOnReceiveContentListener()
13859                         .getFallbackMimeTypesForAutofill(this);
13860             }
13861             structure.setReceiveContentMimeTypes(mimeTypes);
13862         }
13863 
13864         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
13865                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13866             if (mLayout == null) {
13867                 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13868                     Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
13869                 }
13870                 assumeLayout();
13871             }
13872             Layout layout = mLayout;
13873             final int lineCount = layout.getLineCount();
13874             if (lineCount <= 1) {
13875                 // Simple case: this is a single line.
13876                 final CharSequence text = getText();
13877                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13878                     structure.setText(text);
13879                 } else {
13880                     structure.setText(text, getSelectionStart(), getSelectionEnd());
13881                 }
13882             } else {
13883                 // Complex case: multi-line, could be scrolled or within a scroll container
13884                 // so some lines are not visible.
13885                 final int[] tmpCords = new int[2];
13886                 getLocationInWindow(tmpCords);
13887                 final int topWindowLocation = tmpCords[1];
13888                 View root = this;
13889                 ViewParent viewParent = getParent();
13890                 while (viewParent instanceof View) {
13891                     root = (View) viewParent;
13892                     viewParent = root.getParent();
13893                 }
13894                 final int windowHeight = root.getHeight();
13895                 final int topLine;
13896                 final int bottomLine;
13897                 if (topWindowLocation >= 0) {
13898                     // The top of the view is fully within its window; start text at line 0.
13899                     topLine = getLineAtCoordinateUnclamped(0);
13900                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
13901                 } else {
13902                     // The top of hte window has scrolled off the top of the window; figure out
13903                     // the starting line for this.
13904                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
13905                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
13906                 }
13907                 // We want to return some contextual lines above/below the lines that are
13908                 // actually visible.
13909                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
13910                 if (expandedTopLine < 0) {
13911                     expandedTopLine = 0;
13912                 }
13913                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
13914                 if (expandedBottomLine >= lineCount) {
13915                     expandedBottomLine = lineCount - 1;
13916                 }
13917 
13918                 // Convert lines into character offsets.
13919                 int expandedTopChar = transformedToOriginal(
13920                         layout.getLineStart(expandedTopLine),
13921                         OffsetMapping.MAP_STRATEGY_CHARACTER);
13922                 int expandedBottomChar = transformedToOriginal(
13923                         layout.getLineEnd(expandedBottomLine),
13924                         OffsetMapping.MAP_STRATEGY_CHARACTER);
13925 
13926                 // Take into account selection -- if there is a selection, we need to expand
13927                 // the text we are returning to include that selection.
13928                 final int selStart = getSelectionStart();
13929                 final int selEnd = getSelectionEnd();
13930                 if (selStart < selEnd) {
13931                     if (selStart < expandedTopChar) {
13932                         expandedTopChar = selStart;
13933                     }
13934                     if (selEnd > expandedBottomChar) {
13935                         expandedBottomChar = selEnd;
13936                     }
13937                 }
13938 
13939                 // Get the text and trim it to the range we are reporting.
13940                 CharSequence text = getText();
13941 
13942                 if (text != null) {
13943                     if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
13944                         // Cap the offsets to avoid an OOB exception. That can happen if the
13945                         // displayed/layout text, on which these offsets are calculated, is longer
13946                         // than the original text (such as when the view is translated by the
13947                         // platform intelligence).
13948                         // TODO(b/196433694): Figure out how to better handle the offset
13949                         // calculations for this case (so we don't unnecessarily cutoff the original
13950                         // text, for example).
13951                         expandedTopChar = Math.min(expandedTopChar, text.length());
13952                         expandedBottomChar = Math.min(expandedBottomChar, text.length());
13953                         text = text.subSequence(expandedTopChar, expandedBottomChar);
13954                     }
13955 
13956                     if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
13957                         structure.setText(text);
13958                     } else {
13959                         structure.setText(text,
13960                                 selStart - expandedTopChar,
13961                                 selEnd - expandedTopChar);
13962 
13963                         final int[] lineOffsets = new int[bottomLine - topLine + 1];
13964                         final int[] lineBaselines = new int[bottomLine - topLine + 1];
13965                         final int baselineOffset = getBaselineOffset();
13966                         for (int i = topLine; i <= bottomLine; i++) {
13967                             lineOffsets[i - topLine] = transformedToOriginal(layout.getLineStart(i),
13968                                     OffsetMapping.MAP_STRATEGY_CHARACTER);
13969                             lineBaselines[i - topLine] =
13970                                     layout.getLineBaseline(i) + baselineOffset;
13971                         }
13972                         structure.setTextLines(lineOffsets, lineBaselines);
13973                     }
13974                 }
13975             }
13976 
13977             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
13978                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
13979                 // Extract style information that applies to the TextView as a whole.
13980                 int style = 0;
13981                 int typefaceStyle = getTypefaceStyle();
13982                 if ((typefaceStyle & Typeface.BOLD) != 0) {
13983                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
13984                 }
13985                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
13986                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
13987                 }
13988 
13989                 // Global styles can also be set via TextView.setPaintFlags().
13990                 int paintFlags = mTextPaint.getFlags();
13991                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
13992                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
13993                 }
13994                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
13995                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
13996                 }
13997                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
13998                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
13999                 }
14000 
14001                 // TextView does not have its own text background color. A background is either part
14002                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
14003                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
14004                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
14005             }
14006             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
14007                     || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
14008                 structure.setMinTextEms(getMinEms());
14009                 structure.setMaxTextEms(getMaxEms());
14010                 int maxLength = -1;
14011                 for (InputFilter filter: getFilters()) {
14012                     if (filter instanceof InputFilter.LengthFilter) {
14013                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
14014                         break;
14015                     }
14016                 }
14017                 structure.setMaxTextLength(maxLength);
14018             }
14019         }
14020         if (mHintId != Resources.ID_NULL) {
14021             try {
14022                 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId));
14023             } catch (Resources.NotFoundException e) {
14024                 if (android.view.autofill.Helper.sVerbose) {
14025                     Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id "
14026                             + mHintId + ": " + e.getMessage());
14027                 }
14028             }
14029         }
14030         structure.setHint(getHint());
14031         structure.setInputType(getInputType());
14032     }
14033 
canRequestAutofill()14034     boolean canRequestAutofill() {
14035         if (!isAutofillable()) {
14036             return false;
14037         }
14038         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
14039         if (afm != null) {
14040             return afm.isEnabled();
14041         }
14042         return false;
14043     }
14044 
requestAutofill()14045     private void requestAutofill() {
14046         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
14047         if (afm != null) {
14048             afm.requestAutofill(this);
14049         }
14050     }
14051 
14052     @Override
autofill(AutofillValue value)14053     public void autofill(AutofillValue value) {
14054         if (android.view.autofill.Helper.sVerbose) {
14055             Log.v(LOG_TAG, "autofill() called on textview for id:" + getAutofillId());
14056         }
14057         if (!isTextAutofillable()) {
14058             Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
14059             return;
14060         }
14061         if (!value.isText()) {
14062             Log.w(LOG_TAG, "value of type " + value.describeContents()
14063                     + " cannot be autofilled into " + this);
14064             return;
14065         }
14066         final ClipData clip = ClipData.newPlainText("", value.getTextValue());
14067         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build();
14068         performReceiveContent(payload);
14069     }
14070 
14071     @Override
getAutofillType()14072     public @AutofillType int getAutofillType() {
14073         return isTextAutofillable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
14074     }
14075 
14076     /**
14077      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
14078      * {@code char}s if longer.
14079      *
14080      * @return current text, {@code null} if the text is not editable
14081      *
14082      * @see View#getAutofillValue()
14083      */
14084     @Override
14085     @Nullable
getAutofillValue()14086     public AutofillValue getAutofillValue() {
14087         if (isTextAutofillable()) {
14088             final CharSequence text = TextUtils.trimToParcelableSize(getText());
14089             return AutofillValue.forText(text);
14090         }
14091         return null;
14092     }
14093 
14094     /** @hide */
14095     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)14096     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
14097         super.onInitializeAccessibilityEventInternal(event);
14098 
14099         final boolean isPassword = hasPasswordTransformationMethod();
14100         event.setPassword(isPassword);
14101 
14102         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
14103             event.setFromIndex(Selection.getSelectionStart(mText));
14104             event.setToIndex(Selection.getSelectionEnd(mText));
14105             event.setItemCount(mText.length());
14106         }
14107     }
14108 
14109     /** @hide */
14110     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)14111     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
14112         super.onInitializeAccessibilityNodeInfoInternal(info);
14113 
14114         final boolean isPassword = hasPasswordTransformationMethod();
14115         info.setPassword(isPassword);
14116         info.setText(getTextForAccessibility());
14117         info.setHintText(mHint);
14118         info.setShowingHintText(isShowingHint());
14119 
14120         if (mBufferType == BufferType.EDITABLE) {
14121             info.setEditable(true);
14122             if (isEnabled()) {
14123                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
14124             }
14125         }
14126 
14127         if (mEditor != null) {
14128             info.setInputType(mEditor.mInputType);
14129 
14130             if (mEditor.mError != null) {
14131                 info.setContentInvalid(true);
14132                 info.setError(mEditor.mError);
14133             }
14134             // TextView will expose this action if it is editable and has focus.
14135             if (isTextEditable() && isFocused()) {
14136                 CharSequence imeActionLabel = mContext.getResources().getString(
14137                         com.android.internal.R.string.keyboardview_keycode_enter);
14138                 if (getImeActionLabel() != null) {
14139                     imeActionLabel = getImeActionLabel();
14140                 }
14141                 AccessibilityNodeInfo.AccessibilityAction action =
14142                         new AccessibilityNodeInfo.AccessibilityAction(
14143                                 R.id.accessibilityActionImeEnter, imeActionLabel);
14144                 info.addAction(action);
14145             }
14146         }
14147 
14148         if (!TextUtils.isEmpty(mText)) {
14149             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
14150             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
14151             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
14152                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
14153                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
14154                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
14155                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
14156             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
14157             info.setAvailableExtraData(Arrays.asList(
14158                     EXTRA_DATA_RENDERING_INFO_KEY,
14159                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
14160             ));
14161             info.setTextSelectable(isTextSelectable() || isTextEditable());
14162         } else {
14163             info.setAvailableExtraData(Arrays.asList(
14164                     EXTRA_DATA_RENDERING_INFO_KEY
14165             ));
14166         }
14167 
14168         if (isFocused()) {
14169             if (canCopy()) {
14170                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
14171             }
14172             if (canPaste()) {
14173                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
14174             }
14175             if (canCut()) {
14176                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
14177             }
14178             if (canReplace()) {
14179                 info.addAction(
14180                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS);
14181             }
14182             if (canShare()) {
14183                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
14184                         ACCESSIBILITY_ACTION_SHARE,
14185                         getResources().getString(com.android.internal.R.string.share)));
14186             }
14187             if (canProcessText()) {  // also implies mEditor is not null.
14188                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
14189                 mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info);
14190             }
14191         }
14192 
14193         // Check for known input filter types.
14194         final int numFilters = mFilters.length;
14195         for (int i = 0; i < numFilters; i++) {
14196             final InputFilter filter = mFilters[i];
14197             if (filter instanceof InputFilter.LengthFilter) {
14198                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
14199             }
14200         }
14201 
14202         if (!isSingleLine()) {
14203             info.setMultiLine(true);
14204         }
14205 
14206         // A view should not be exposed as clickable/long-clickable to a service because of a
14207         // LinkMovementMethod or because it has selectable and non-editable text.
14208         if ((info.isClickable() || info.isLongClickable())
14209                 && (mMovement instanceof LinkMovementMethod
14210                 || (isTextSelectable() && !isTextEditable()))) {
14211             if (!hasOnClickListeners()) {
14212                 info.setClickable(false);
14213                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
14214             }
14215             if (!hasOnLongClickListeners()) {
14216                 info.setLongClickable(false);
14217                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
14218             }
14219         }
14220     }
14221 
14222     @Override
addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)14223     public void addExtraDataToAccessibilityNodeInfo(
14224             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
14225         if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
14226             int positionInfoStartIndex = arguments.getInt(
14227                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
14228             int positionInfoLength = arguments.getInt(
14229                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
14230             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
14231                     || (positionInfoStartIndex >= mText.length())) {
14232                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
14233                 return;
14234             }
14235             RectF[] boundingRects = new RectF[positionInfoLength];
14236             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
14237             populateCharacterBounds(builder, positionInfoStartIndex,
14238                     Math.min(positionInfoStartIndex + positionInfoLength, length()),
14239                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
14240             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
14241             for (int i = 0; i < positionInfoLength; i++) {
14242                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
14243                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
14244                     RectF bounds = cursorAnchorInfo
14245                             .getCharacterBounds(positionInfoStartIndex + i);
14246                     if (bounds != null) {
14247                         mapRectFromViewToScreenCoords(bounds, true);
14248                         boundingRects[i] = bounds;
14249                     }
14250                 }
14251             }
14252             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
14253             return;
14254         }
14255         if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) {
14256             final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo =
14257                     AccessibilityNodeInfo.ExtraRenderingInfo.obtain();
14258             extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height);
14259             extraRenderingInfo.setTextSizeInPx(getTextSize());
14260             extraRenderingInfo.setTextSizeUnit(getTextSizeUnit());
14261             info.setExtraRenderingInfo(extraRenderingInfo);
14262         }
14263     }
14264 
14265     /**
14266      * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
14267      * This method obtains the view's visible rectangle whereas the method
14268      * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
14269      *
14270      * @return true if at least part of the text content is visible; false if the text content is
14271      * completely clipped or translated out of the visible area.
14272      */
getViewVisibleRect(Rect rect)14273     private boolean getViewVisibleRect(Rect rect) {
14274         if (!getLocalVisibleRect(rect)) {
14275             return false;
14276         }
14277         // getLocalVisibleRect returns a rect relative to the unscrolled left top corner of the
14278         // view. In other words, the returned rectangle's origin point is (-scrollX, -scrollY) in
14279         // view's coordinates. So we need to offset it with the negative scrolled amount to convert
14280         // it to view's coordinate.
14281         rect.offset(-getScrollX(), -getScrollY());
14282         return true;
14283     }
14284 
14285     /**
14286      * Helper method to set {@code rect} to the text content's non-clipped area in the view's
14287      * coordinates.
14288      *
14289      * @return true if at least part of the text content is visible; false if the text content is
14290      * completely clipped or translated out of the visible area.
14291      */
getContentVisibleRect(Rect rect)14292     private boolean getContentVisibleRect(Rect rect) {
14293         if (!getViewVisibleRect(rect)) {
14294             return false;
14295         }
14296         // Clip the view's visible rect with the text layout's visible rect.
14297         return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
14298                 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
14299     }
14300 
14301     /**
14302      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
14303      *
14304      * @param builder The builder to populate
14305      * @param startIndex The starting character index to populate
14306      * @param endIndex The ending character index to populate
14307      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
14308      * content
14309      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
14310      * @hide
14311      */
populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)14312     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
14313             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
14314             float viewportToContentVerticalOffset) {
14315         if (isOffsetMappingAvailable()) {
14316             // The text is transformed, and has different length, we don't support
14317             // character bounds in this case yet.
14318             return;
14319         }
14320         final Rect rect = new Rect();
14321         getContentVisibleRect(rect);
14322         final RectF visibleRect = new RectF(rect);
14323 
14324         final float[] characterBounds = getCharacterBounds(startIndex, endIndex,
14325                 viewportToContentHorizontalOffset, viewportToContentVerticalOffset);
14326         final int limit = endIndex - startIndex;
14327         for (int offset = 0; offset < limit; ++offset) {
14328             final float left = characterBounds[offset * 4];
14329             final float top = characterBounds[offset * 4 + 1];
14330             final float right = characterBounds[offset * 4 + 2];
14331             final float bottom = characterBounds[offset * 4 + 3];
14332 
14333             final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom);
14334             final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom);
14335             int characterBoundsFlags = 0;
14336             if (hasVisibleRegion) {
14337                 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
14338             }
14339             if (hasInVisibleRegion) {
14340                 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
14341             }
14342 
14343             if (mLayout.isRtlCharAt(offset)) {
14344                 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
14345             }
14346             builder.addCharacterBounds(offset + startIndex, left, top, right, bottom,
14347                     characterBoundsFlags);
14348         }
14349     }
14350 
14351     /**
14352      * Return the bounds of the characters in the given range, in TextView's coordinates.
14353      *
14354      * @param start the start index of the interested text range, inclusive.
14355      * @param end the end index of the interested text range, exclusive.
14356      * @param layoutLeft the left of the given {@code layout} in the editor view's coordinates.
14357      * @param layoutTop  the top of the given {@code layout} in the editor view's coordinates.
14358      * @return the character bounds stored in a flattened array, in the editor view's coordinates.
14359      */
getCharacterBounds(int start, int end, float layoutLeft, float layoutTop)14360     private float[] getCharacterBounds(int start, int end, float layoutLeft, float layoutTop) {
14361         final float[] characterBounds = new float[4 * (end - start)];
14362         mLayout.fillCharacterBounds(start, end, characterBounds, 0);
14363         for (int offset = 0; offset < end - start; ++offset) {
14364             characterBounds[4 * offset] += layoutLeft;
14365             characterBounds[4 * offset + 1] += layoutTop;
14366             characterBounds[4 * offset + 2] += layoutLeft;
14367             characterBounds[4 * offset + 3] += layoutTop;
14368         }
14369         return characterBounds;
14370     }
14371 
14372     /**
14373      * Compute {@link CursorAnchorInfo} from this {@link TextView}.
14374      *
14375      * @param filter the {@link CursorAnchorInfo} update filter which specified the needed
14376      *               information from IME.
14377      * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build
14378      *                                the result {@link CursorAnchorInfo}.
14379      * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen
14380      *                           matrix.
14381      * @return the result {@link CursorAnchorInfo} to be passed to IME.
14382      * @hide
14383      */
14384     @VisibleForTesting
14385     @Nullable
getCursorAnchorInfo(@nputConnection.CursorUpdateFilter int filter, @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, @NonNull Matrix viewToScreenMatrix)14386     public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter,
14387             @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder,
14388             @NonNull Matrix viewToScreenMatrix) {
14389         Layout layout = getLayout();
14390         if (layout == null) {
14391             return null;
14392         }
14393         boolean includeEditorBounds =
14394                 (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0;
14395         boolean includeCharacterBounds =
14396                 (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0;
14397         boolean includeInsertionMarker =
14398                 (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
14399         boolean includeVisibleLineBounds =
14400                 (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0;
14401         boolean includeTextAppearance =
14402                 (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0;
14403         boolean includeAll =
14404                 (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker
14405                         && !includeVisibleLineBounds && !includeTextAppearance);
14406 
14407         includeEditorBounds |= includeAll;
14408         includeCharacterBounds |= includeAll;
14409         includeInsertionMarker |= includeAll;
14410         includeVisibleLineBounds |= includeAll;
14411         includeTextAppearance |= includeAll;
14412 
14413         final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder;
14414         builder.reset();
14415 
14416         final int selectionStart = getSelectionStart();
14417         builder.setSelectionRange(selectionStart, getSelectionEnd());
14418 
14419         // Construct transformation matrix from view local coordinates to screen coordinates.
14420         viewToScreenMatrix.reset();
14421         transformMatrixToGlobal(viewToScreenMatrix);
14422         builder.setMatrix(viewToScreenMatrix);
14423 
14424         if (includeEditorBounds) {
14425             if (mTempRect == null) {
14426                 mTempRect = new Rect();
14427             }
14428             final Rect bounds = mTempRect;
14429             final RectF editorBounds;
14430             final RectF handwritingBounds;
14431             if (getViewVisibleRect(bounds)) {
14432                 editorBounds = new RectF(bounds);
14433                 handwritingBounds = new RectF(editorBounds);
14434                 handwritingBounds.top -= getHandwritingBoundsOffsetTop();
14435                 handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
14436                 handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
14437                 handwritingBounds.right += getHandwritingBoundsOffsetRight();
14438             } else {
14439                 // The editor is not visible at all, return empty rectangles. We still need to
14440                 // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
14441                 editorBounds = new RectF();
14442                 handwritingBounds = new RectF();
14443             }
14444             EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
14445             EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
14446                     .setHandwritingBounds(handwritingBounds).build();
14447             builder.setEditorBoundsInfo(editorBoundsInfo);
14448         }
14449 
14450         if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) {
14451             final float viewportToContentHorizontalOffset =
14452                     viewportToContentHorizontalOffset();
14453             final float viewportToContentVerticalOffset =
14454                     viewportToContentVerticalOffset();
14455             final boolean isTextTransformed = (getTransformationMethod() != null
14456                     && getTransformed() instanceof OffsetMapping);
14457             if (includeCharacterBounds && !isTextTransformed) {
14458                 final CharSequence text = getText();
14459                 if (text instanceof Spannable) {
14460                     final Spannable sp = (Spannable) text;
14461                     int composingTextStart = EditableInputConnection.getComposingSpanStart(sp);
14462                     int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp);
14463                     if (composingTextEnd < composingTextStart) {
14464                         final int temp = composingTextEnd;
14465                         composingTextEnd = composingTextStart;
14466                         composingTextStart = temp;
14467                     }
14468                     final boolean hasComposingText =
14469                             (0 <= composingTextStart) && (composingTextStart
14470                                     < composingTextEnd);
14471                     if (hasComposingText) {
14472                         final CharSequence composingText = text.subSequence(composingTextStart,
14473                                 composingTextEnd);
14474                         builder.setComposingText(composingTextStart, composingText);
14475                         populateCharacterBounds(builder, composingTextStart,
14476                                 composingTextEnd, viewportToContentHorizontalOffset,
14477                                 viewportToContentVerticalOffset);
14478                     }
14479                 }
14480             }
14481 
14482             if (includeInsertionMarker) {
14483                 // Treat selectionStart as the insertion point.
14484                 if (0 <= selectionStart) {
14485                     final int offsetTransformed = originalToTransformed(
14486                             selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR);
14487                     final int line = layout.getLineForOffset(offsetTransformed);
14488                     final float insertionMarkerX =
14489                             layout.getPrimaryHorizontal(
14490                                             offsetTransformed, layout.shouldClampCursor(line))
14491                                     + viewportToContentHorizontalOffset;
14492                     final float insertionMarkerTop = layout.getLineTop(line)
14493                             + viewportToContentVerticalOffset;
14494                     final float insertionMarkerBaseline = layout.getLineBaseline(line)
14495                             + viewportToContentVerticalOffset;
14496                     final float insertionMarkerBottom =
14497                             layout.getLineBottom(line, /* includeLineSpacing= */ false)
14498                                     + viewportToContentVerticalOffset;
14499                     final boolean isTopVisible =
14500                             isPositionVisible(insertionMarkerX, insertionMarkerTop);
14501                     final boolean isBottomVisible =
14502                             isPositionVisible(insertionMarkerX, insertionMarkerBottom);
14503                     int insertionMarkerFlags = 0;
14504                     if (isTopVisible || isBottomVisible) {
14505                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
14506                     }
14507                     if (!isTopVisible || !isBottomVisible) {
14508                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
14509                     }
14510                     if (layout.isRtlCharAt(offsetTransformed)) {
14511                         insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
14512                     }
14513                     builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
14514                             insertionMarkerBaseline, insertionMarkerBottom,
14515                             insertionMarkerFlags);
14516                 }
14517             }
14518 
14519             if (includeVisibleLineBounds) {
14520                 final Rect visibleRect = new Rect();
14521                 if (getContentVisibleRect(visibleRect)) {
14522                     // Subtract the viewportToContentVerticalOffset to convert the view
14523                     // coordinates to layout coordinates.
14524                     final float visibleTop =
14525                             visibleRect.top - viewportToContentVerticalOffset;
14526                     final float visibleBottom =
14527                             visibleRect.bottom - viewportToContentVerticalOffset;
14528                     final int firstLine =
14529                             layout.getLineForVertical((int) Math.floor(visibleTop));
14530                     final int lastLine =
14531                             layout.getLineForVertical((int) Math.ceil(visibleBottom));
14532 
14533                     for (int line = firstLine; line <= lastLine; ++line) {
14534                         final float left = layout.getLineLeft(line)
14535                                 + viewportToContentHorizontalOffset;
14536                         final float top = layout.getLineTop(line)
14537                                 + viewportToContentVerticalOffset;
14538                         final float right = layout.getLineRight(line)
14539                                 + viewportToContentHorizontalOffset;
14540                         final float bottom = layout.getLineBottom(line, false)
14541                                 + viewportToContentVerticalOffset;
14542                         builder.addVisibleLineBounds(left, top, right, bottom);
14543                     }
14544                 }
14545             }
14546         }
14547 
14548         if (includeTextAppearance) {
14549             builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this));
14550         }
14551         return builder.build();
14552     }
14553 
14554     /**
14555      * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}.
14556      * @hide
14557      */
getTextBoundsInfo(@onNull RectF bounds)14558     public TextBoundsInfo getTextBoundsInfo(@NonNull RectF bounds) {
14559         final Layout layout = getLayout();
14560         if (layout == null) {
14561             // No valid text layout, return null.
14562             return null;
14563         }
14564         final CharSequence text = layout.getText();
14565         if (text == null || isOffsetMappingAvailable()) {
14566             // The text is Null or the text has been transformed. Can't provide TextBoundsInfo.
14567             return null;
14568         }
14569 
14570         final Matrix localToGlobalMatrix = new Matrix();
14571         transformMatrixToGlobal(localToGlobalMatrix);
14572         final Matrix globalToLocalMatrix = new Matrix();
14573         if (!localToGlobalMatrix.invert(globalToLocalMatrix)) {
14574             // Can't map global rectF to local coordinates, this is almost impossible in practice.
14575             return null;
14576         }
14577 
14578         final float layoutLeft = viewportToContentHorizontalOffset();
14579         final float layoutTop = viewportToContentVerticalOffset();
14580 
14581         final RectF localBounds = new RectF(bounds);
14582         globalToLocalMatrix.mapRect(localBounds);
14583         localBounds.offset(-layoutLeft, -layoutTop);
14584 
14585         // Text length is 0. There is no character bounds, return empty TextBoundsInfo.
14586         // rectF doesn't intersect with the layout, return empty TextBoundsInfo.
14587         if (!localBounds.intersects(0f, 0f, layout.getWidth(), layout.getHeight())
14588                 || text.length() == 0) {
14589             final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 0);
14590             final SegmentFinder emptySegmentFinder =
14591                     new SegmentFinder.PrescribedSegmentFinder(new int[0]);
14592             builder.setMatrix(localToGlobalMatrix)
14593                     .setCharacterBounds(new float[0])
14594                     .setCharacterBidiLevel(new int[0])
14595                     .setCharacterFlags(new int[0])
14596                     .setGraphemeSegmentFinder(emptySegmentFinder)
14597                     .setLineSegmentFinder(emptySegmentFinder)
14598                     .setWordSegmentFinder(emptySegmentFinder);
14599             return  builder.build();
14600         }
14601 
14602         final int startLine = layout.getLineForVertical((int) Math.floor(localBounds.top));
14603         final int endLine = layout.getLineForVertical((int) Math.floor(localBounds.bottom));
14604         final int start = layout.getLineStart(startLine);
14605         final int end = layout.getLineEnd(endLine);
14606 
14607         // Compute character bounds.
14608         final float[] characterBounds = getCharacterBounds(start, end, layoutLeft, layoutTop);
14609 
14610         // Compute character flags and BiDi levels.
14611         final int[] characterFlags = new int[end - start];
14612         final int[] characterBidiLevels = new int[end - start];
14613         for (int line = startLine; line <= endLine; ++line) {
14614             final int lineStart = layout.getLineStart(line);
14615             final int lineEnd = layout.getLineEnd(line);
14616             final Layout.Directions directions = layout.getLineDirections(line);
14617             for (int i = 0; i < directions.getRunCount(); ++i) {
14618                 final int runStart = directions.getRunStart(i) + lineStart;
14619                 final int runEnd = Math.min(runStart + directions.getRunLength(i), lineEnd);
14620                 final int runLevel = directions.getRunLevel(i);
14621                 Arrays.fill(characterBidiLevels, runStart - start, runEnd - start, runLevel);
14622             }
14623 
14624             final boolean lineIsRtl =
14625                     layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT;
14626             for (int index = lineStart; index < lineEnd; ++index) {
14627                 int flags = 0;
14628                 if (TextUtils.isWhitespace(text.charAt(index))) {
14629                     flags |= TextBoundsInfo.FLAG_CHARACTER_WHITESPACE;
14630                 }
14631                 if (TextUtils.isPunctuation(Character.codePointAt(text, index))) {
14632                     flags |= TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION;
14633                 }
14634                 if (TextUtils.isNewline(Character.codePointAt(text, index))) {
14635                     flags |= TextBoundsInfo.FLAG_CHARACTER_LINEFEED;
14636                 }
14637                 if (lineIsRtl) {
14638                     flags |= TextBoundsInfo.FLAG_LINE_IS_RTL;
14639                 }
14640                 characterFlags[index - start] = flags;
14641             }
14642         }
14643 
14644         // Create grapheme SegmentFinder.
14645         final SegmentFinder graphemeSegmentFinder =
14646                 new GraphemeClusterSegmentFinder(text, layout.getPaint());
14647 
14648         // Create word SegmentFinder.
14649         final WordIterator wordIterator = getWordIterator();
14650         wordIterator.setCharSequence(text, 0, text.length());
14651         final SegmentFinder wordSegmentFinder = new WordSegmentFinder(text, wordIterator);
14652 
14653         // Create line SegmentFinder.
14654         final int lineCount = endLine - startLine + 1;
14655         final int[] lineRanges = new int[2 * lineCount];
14656         for (int line = startLine; line <= endLine; ++line) {
14657             final int offset = line - startLine;
14658             lineRanges[2 * offset] = layout.getLineStart(line);
14659             lineRanges[2 * offset + 1] = layout.getLineEnd(line);
14660         }
14661         final SegmentFinder lineSegmentFinder =
14662                 new SegmentFinder.PrescribedSegmentFinder(lineRanges);
14663 
14664         return new TextBoundsInfo.Builder(start, end)
14665                 .setMatrix(localToGlobalMatrix)
14666                 .setCharacterBounds(characterBounds)
14667                 .setCharacterBidiLevel(characterBidiLevels)
14668                 .setCharacterFlags(characterFlags)
14669                 .setGraphemeSegmentFinder(graphemeSegmentFinder)
14670                 .setLineSegmentFinder(lineSegmentFinder)
14671                 .setWordSegmentFinder(wordSegmentFinder)
14672                 .build();
14673     }
14674 
14675     /**
14676      * @hide
14677      */
isPositionVisible(final float positionX, final float positionY)14678     public boolean isPositionVisible(final float positionX, final float positionY) {
14679         synchronized (TEMP_POSITION) {
14680             final float[] position = TEMP_POSITION;
14681             position[0] = positionX;
14682             position[1] = positionY;
14683             View view = this;
14684 
14685             while (view != null) {
14686                 if (view != this) {
14687                     // Local scroll is already taken into account in positionX/Y
14688                     position[0] -= view.getScrollX();
14689                     position[1] -= view.getScrollY();
14690                 }
14691 
14692                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
14693                         || position[1] > view.getHeight()) {
14694                     return false;
14695                 }
14696 
14697                 if (!view.getMatrix().isIdentity()) {
14698                     view.getMatrix().mapPoints(position);
14699                 }
14700 
14701                 position[0] += view.getLeft();
14702                 position[1] += view.getTop();
14703 
14704                 final ViewParent parent = view.getParent();
14705                 if (parent instanceof View) {
14706                     view = (View) parent;
14707                 } else {
14708                     // We've reached the ViewRoot, stop iterating
14709                     view = null;
14710                 }
14711             }
14712         }
14713 
14714         // We've been able to walk up the view hierarchy and the position was never clipped
14715         return true;
14716     }
14717 
14718     /**
14719      * Performs an accessibility action after it has been offered to the
14720      * delegate.
14721      *
14722      * @hide
14723      */
14724     @Override
performAccessibilityActionInternal(int action, Bundle arguments)14725     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
14726         if (mEditor != null) {
14727             if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)
14728                     || mEditor.performSmartActionsAccessibilityAction(action)) {
14729                 return true;
14730             }
14731         }
14732         switch (action) {
14733             case AccessibilityNodeInfo.ACTION_CLICK: {
14734                 return performAccessibilityActionClick(arguments);
14735             }
14736             case AccessibilityNodeInfo.ACTION_COPY: {
14737                 if (isFocused() && canCopy()) {
14738                     if (onTextContextMenuItem(ID_COPY)) {
14739                         return true;
14740                     }
14741                 }
14742             } return false;
14743             case AccessibilityNodeInfo.ACTION_PASTE: {
14744                 if (isFocused() && canPaste()) {
14745                     if (onTextContextMenuItem(ID_PASTE)) {
14746                         return true;
14747                     }
14748                 }
14749             } return false;
14750             case AccessibilityNodeInfo.ACTION_CUT: {
14751                 if (isFocused() && canCut()) {
14752                     if (onTextContextMenuItem(ID_CUT)) {
14753                         return true;
14754                     }
14755                 }
14756             } return false;
14757             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
14758                 ensureIterableTextForAccessibilitySelectable();
14759                 CharSequence text = getIterableTextForAccessibility();
14760                 if (text == null) {
14761                     return false;
14762                 }
14763                 final int start = (arguments != null) ? arguments.getInt(
14764                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
14765                 final int end = (arguments != null) ? arguments.getInt(
14766                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
14767                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
14768                     // No arguments clears the selection.
14769                     if (start == end && end == -1) {
14770                         Selection.removeSelection((Spannable) text);
14771                         return true;
14772                     }
14773                     if (start >= 0 && start <= end && end <= text.length()) {
14774                         requestFocusOnNonEditableSelectableText();
14775                         Selection.setSelection((Spannable) text, start, end);
14776                         // Make sure selection mode is engaged.
14777                         if (mEditor != null) {
14778                             mEditor.startSelectionActionModeAsync(false);
14779                         }
14780                         return true;
14781                     }
14782                 }
14783             } return false;
14784             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
14785             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
14786                 ensureIterableTextForAccessibilitySelectable();
14787                 return super.performAccessibilityActionInternal(action, arguments);
14788             }
14789             case ACCESSIBILITY_ACTION_SHARE: {
14790                 if (isFocused() && canShare()) {
14791                     if (onTextContextMenuItem(ID_SHARE)) {
14792                         return true;
14793                     }
14794                 }
14795             } return false;
14796             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
14797                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
14798                     return false;
14799                 }
14800                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
14801                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
14802                 setText(text);
14803                 if (mText != null) {
14804                     int updatedTextLength = mText.length();
14805                     if (updatedTextLength > 0) {
14806                         Selection.setSelection(mSpannable, updatedTextLength);
14807                     }
14808                 }
14809             } return true;
14810             case R.id.accessibilityActionImeEnter: {
14811                 if (isFocused() && isTextEditable()) {
14812                     onEditorAction(getImeActionId());
14813                 }
14814             } return true;
14815             case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
14816                 if (isLongClickable()) {
14817                     boolean handled;
14818                     if (isEnabled() && (mBufferType == BufferType.EDITABLE)) {
14819                         mEditor.mIsBeingLongClickedByAccessibility = true;
14820                         try {
14821                             handled = performLongClick();
14822                         } finally {
14823                             mEditor.mIsBeingLongClickedByAccessibility = false;
14824                         }
14825                     } else {
14826                         handled = performLongClick();
14827                     }
14828                     return handled;
14829                 }
14830             }
14831             return false;
14832             default: {
14833                 // New ids have static blocks to assign values, so they can't be used in a case
14834                 // block.
14835                 if (action == R.id.accessibilityActionShowTextSuggestions) {
14836                     return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE);
14837                 }
14838                 return super.performAccessibilityActionInternal(action, arguments);
14839             }
14840         }
14841     }
14842 
performAccessibilityActionClick(Bundle arguments)14843     private boolean performAccessibilityActionClick(Bundle arguments) {
14844         boolean handled = false;
14845 
14846         if (!isEnabled()) {
14847             return false;
14848         }
14849 
14850         if (isClickable() || isLongClickable()) {
14851             // Simulate View.onTouchEvent for an ACTION_UP event
14852             if (isFocusable() && !isFocused()) {
14853                 requestFocus();
14854             }
14855 
14856             performClick();
14857             handled = true;
14858         }
14859 
14860         // Show the IME, except when selecting in read-only text.
14861         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
14862                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
14863             final InputMethodManager imm = getInputMethodManager();
14864             viewClicked(imm);
14865             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
14866                 handled |= imm.showSoftInput(this, 0);
14867             }
14868         }
14869 
14870         return handled;
14871     }
14872 
requestFocusOnNonEditableSelectableText()14873     private void requestFocusOnNonEditableSelectableText() {
14874         if (!isTextEditable() && isTextSelectable()) {
14875             if (!isEnabled()) {
14876                 return;
14877             }
14878 
14879             if (isFocusable() && !isFocused()) {
14880                 requestFocus();
14881             }
14882         }
14883     }
14884 
hasSpannableText()14885     private boolean hasSpannableText() {
14886         return mText != null && mText instanceof Spannable;
14887     }
14888 
14889     /** @hide */
14890     @Override
sendAccessibilityEventInternal(int eventType)14891     public void sendAccessibilityEventInternal(int eventType) {
14892         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
14893             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
14894         }
14895 
14896         super.sendAccessibilityEventInternal(eventType);
14897     }
14898 
14899     @Override
sendAccessibilityEventUnchecked(AccessibilityEvent event)14900     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
14901         // Do not send scroll events since first they are not interesting for
14902         // accessibility and second such events a generated too frequently.
14903         // For details see the implementation of bringTextIntoView().
14904         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
14905             return;
14906         }
14907         super.sendAccessibilityEventUnchecked(event);
14908     }
14909 
14910     /**
14911      * Returns the text that should be exposed to accessibility services.
14912      * <p>
14913      * This approximates what is displayed visually.
14914      *
14915      * @return the text that should be exposed to accessibility services, may
14916      *         be {@code null} if no text is set
14917      */
14918     @Nullable
14919     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTextForAccessibility()14920     private CharSequence getTextForAccessibility() {
14921         // If the text is empty, we must be showing the hint text.
14922         if (TextUtils.isEmpty(mText)) {
14923             return mHint;
14924         }
14925 
14926         // Otherwise, return whatever text is being displayed.
14927         return TextUtils.trimToParcelableSize(mTransformed);
14928     }
14929 
isVisibleToAccessibility()14930     boolean isVisibleToAccessibility() {
14931         return AccessibilityManager.getInstance(mContext).isEnabled()
14932                 && (isFocused() || (isSelected() && isShown()));
14933     }
14934 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)14935     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
14936             int fromIndex, int removedCount, int addedCount) {
14937         AccessibilityEvent event =
14938                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
14939         event.setFromIndex(fromIndex);
14940         event.setRemovedCount(removedCount);
14941         event.setAddedCount(addedCount);
14942         event.setBeforeText(beforeText);
14943         sendAccessibilityEventUnchecked(event);
14944     }
14945 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int toIndex)14946     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
14947             int fromIndex, int toIndex) {
14948         AccessibilityEvent event =
14949                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
14950         event.setFromIndex(fromIndex);
14951         event.setToIndex(toIndex);
14952         event.setBeforeText(beforeText);
14953         sendAccessibilityEventUnchecked(event);
14954     }
14955 
getInputMethodManager()14956     private InputMethodManager getInputMethodManager() {
14957         return getContext().getSystemService(InputMethodManager.class);
14958     }
14959 
14960     /**
14961      * Returns whether this text view is a current input method target.  The
14962      * default implementation just checks with {@link InputMethodManager}.
14963      * @return True if the TextView is a current input method target; false otherwise.
14964      */
isInputMethodTarget()14965     public boolean isInputMethodTarget() {
14966         InputMethodManager imm = getInputMethodManager();
14967         return imm != null && imm.isActive(this);
14968     }
14969 
14970     static final int ID_SELECT_ALL = android.R.id.selectAll;
14971     static final int ID_UNDO = android.R.id.undo;
14972     static final int ID_REDO = android.R.id.redo;
14973     static final int ID_CUT = android.R.id.cut;
14974     static final int ID_COPY = android.R.id.copy;
14975     static final int ID_PASTE = android.R.id.paste;
14976     static final int ID_SHARE = android.R.id.shareText;
14977     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
14978     static final int ID_REPLACE = android.R.id.replaceText;
14979     static final int ID_ASSIST = android.R.id.textAssist;
14980     static final int ID_AUTOFILL = android.R.id.autofill;
14981 
14982     /**
14983      * Called when a context menu option for the text view is selected.  Currently
14984      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
14985      * {@link android.R.id#copy}, {@link android.R.id#paste},
14986      * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or
14987      * {@link android.R.id#shareText}.
14988      *
14989      * @return true if the context menu item action was performed.
14990      */
onTextContextMenuItem(int id)14991     public boolean onTextContextMenuItem(int id) {
14992         int min = 0;
14993         int max = mText.length();
14994 
14995         if (isFocused()) {
14996             final int selStart = getSelectionStart();
14997             final int selEnd = getSelectionEnd();
14998 
14999             min = Math.max(0, Math.min(selStart, selEnd));
15000             max = Math.max(0, Math.max(selStart, selEnd));
15001         }
15002 
15003         switch (id) {
15004             case ID_SELECT_ALL:
15005                 final boolean hadSelection = hasSelection();
15006                 selectAllText();
15007                 if (mEditor != null && hadSelection) {
15008                     mEditor.invalidateActionModeAsync();
15009                 }
15010                 return true;
15011 
15012             case ID_UNDO:
15013                 if (mEditor != null) {
15014                     mEditor.undo();
15015                 }
15016                 return true;  // Returns true even if nothing was undone.
15017 
15018             case ID_REDO:
15019                 if (mEditor != null) {
15020                     mEditor.redo();
15021                 }
15022                 return true;  // Returns true even if nothing was undone.
15023 
15024             case ID_PASTE:
15025                 paste(true /* withFormatting */);
15026                 return true;
15027 
15028             case ID_PASTE_AS_PLAIN_TEXT:
15029                 paste(false /* withFormatting */);
15030                 return true;
15031 
15032             case ID_CUT:
15033                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
15034                 if (setPrimaryClip(cutData)) {
15035                     deleteText_internal(min, max);
15036                 } else {
15037                     Toast.makeText(getContext(),
15038                             com.android.internal.R.string.failed_to_copy_to_clipboard,
15039                             Toast.LENGTH_SHORT).show();
15040                 }
15041                 return true;
15042 
15043             case ID_COPY:
15044                 // For link action mode in a non-selectable/non-focusable TextView,
15045                 // make sure that we set the appropriate min/max.
15046                 final int selStart = getSelectionStart();
15047                 final int selEnd = getSelectionEnd();
15048                 min = Math.max(0, Math.min(selStart, selEnd));
15049                 max = Math.max(0, Math.max(selStart, selEnd));
15050                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
15051                 if (setPrimaryClip(copyData)) {
15052                     stopTextActionMode();
15053                 } else {
15054                     Toast.makeText(getContext(),
15055                             com.android.internal.R.string.failed_to_copy_to_clipboard,
15056                             Toast.LENGTH_SHORT).show();
15057                 }
15058                 return true;
15059 
15060             case ID_REPLACE:
15061                 if (mEditor != null) {
15062                     mEditor.replace();
15063                 }
15064                 return true;
15065 
15066             case ID_SHARE:
15067                 shareSelectedText();
15068                 return true;
15069 
15070             case ID_AUTOFILL:
15071                 requestAutofill();
15072                 stopTextActionMode();
15073                 return true;
15074         }
15075         return false;
15076     }
15077 
15078     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getTransformedText(int start, int end)15079     CharSequence getTransformedText(int start, int end) {
15080         return removeSuggestionSpans(mTransformed.subSequence(start, end));
15081     }
15082 
15083     @Override
performLongClick()15084     public boolean performLongClick() {
15085         if (DEBUG_CURSOR) {
15086             logCursor("performLongClick", null);
15087         }
15088 
15089         boolean handled = false;
15090         boolean performedHapticFeedback = false;
15091 
15092         if (mEditor != null) {
15093             mEditor.mIsBeingLongClicked = true;
15094         }
15095 
15096         if (super.performLongClick()) {
15097             handled = true;
15098             performedHapticFeedback = true;
15099         }
15100 
15101         if (mEditor != null) {
15102             handled |= mEditor.performLongClick(handled);
15103             mEditor.mIsBeingLongClicked = false;
15104         }
15105 
15106         if (handled) {
15107             if (!performedHapticFeedback) {
15108               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
15109             }
15110             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
15111         } else {
15112             MetricsLogger.action(
15113                     mContext,
15114                     MetricsEvent.TEXT_LONGPRESS,
15115                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
15116         }
15117 
15118         return handled;
15119     }
15120 
15121     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)15122     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
15123         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
15124         if (mEditor != null) {
15125             mEditor.onScrollChanged();
15126         }
15127     }
15128 
15129     /**
15130      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
15131      * by the IME or by the spell checker as the user types. This is done by adding
15132      * {@link SuggestionSpan}s to the text.
15133      *
15134      * When suggestions are enabled (default), this list of suggestions will be displayed when the
15135      * user asks for them on these parts of the text. This value depends on the inputType of this
15136      * TextView.
15137      *
15138      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
15139      *
15140      * In addition, the type variation must be one of
15141      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
15142      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
15143      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
15144      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
15145      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
15146      *
15147      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
15148      *
15149      * @return true if the suggestions popup window is enabled, based on the inputType.
15150      */
isSuggestionsEnabled()15151     public boolean isSuggestionsEnabled() {
15152         if (mEditor == null) return false;
15153         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
15154             return false;
15155         }
15156         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
15157 
15158         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
15159         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
15160                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
15161                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
15162                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
15163                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
15164     }
15165 
15166     /**
15167      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
15168      * selection is initiated in this View.
15169      *
15170      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
15171      * Paste, Replace and Share actions, depending on what this View supports.
15172      *
15173      * <p>A custom implementation can add new entries in the default menu in its
15174      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
15175      * method. The default actions can also be removed from the menu using
15176      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
15177      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
15178      * {@link android.R.id#pasteAsPlainText} (starting at API level 23),
15179      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
15180      *
15181      * <p>Returning false from
15182      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
15183      * will prevent the action mode from being started.
15184      *
15185      * <p>Action click events should be handled by the custom implementation of
15186      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
15187      * android.view.MenuItem)}.
15188      *
15189      * <p>Note that text selection mode is not started when a TextView receives focus and the
15190      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
15191      * that case, to allow for quick replacement.
15192      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)15193     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
15194         createEditorIfNeeded();
15195         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
15196     }
15197 
15198     /**
15199      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
15200      *
15201      * @return The current custom selection callback.
15202      */
getCustomSelectionActionModeCallback()15203     public ActionMode.Callback getCustomSelectionActionModeCallback() {
15204         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
15205     }
15206 
15207     /**
15208      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
15209      * insertion is initiated in this View.
15210      * The standard implementation populates the menu with a subset of Select All,
15211      * Paste and Replace actions, depending on what this View supports.
15212      *
15213      * <p>A custom implementation can add new entries in the default menu in its
15214      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
15215      * android.view.Menu)} method. The default actions can also be removed from the menu using
15216      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
15217      * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API
15218      * level 23) or {@link android.R.id#replaceText} ids as parameters.</p>
15219      *
15220      * <p>Returning false from
15221      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
15222      * android.view.Menu)} will prevent the action mode from being started.</p>
15223      *
15224      * <p>Action click events should be handled by the custom implementation of
15225      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
15226      * android.view.MenuItem)}.</p>
15227      *
15228      * <p>Note that text insertion mode is not started when a TextView receives focus and the
15229      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
15230      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)15231     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
15232         createEditorIfNeeded();
15233         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
15234     }
15235 
15236     /**
15237      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
15238      *
15239      * @return The current custom insertion callback.
15240      */
getCustomInsertionActionModeCallback()15241     public ActionMode.Callback getCustomInsertionActionModeCallback() {
15242         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
15243     }
15244 
15245     /**
15246      * Sets the {@link TextClassifier} for this TextView.
15247      */
setTextClassifier(@ullable TextClassifier textClassifier)15248     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
15249         mTextClassifier = textClassifier;
15250     }
15251 
15252     /**
15253      * Returns the {@link TextClassifier} used by this TextView.
15254      * If no TextClassifier has been set, this TextView uses the default set by the
15255      * {@link TextClassificationManager}.
15256      */
15257     @NonNull
getTextClassifier()15258     public TextClassifier getTextClassifier() {
15259         if (mTextClassifier == null) {
15260             final TextClassificationManager tcm = getTextClassificationManagerForUser();
15261             if (tcm != null) {
15262                 return tcm.getTextClassifier();
15263             }
15264             return TextClassifier.NO_OP;
15265         }
15266         return mTextClassifier;
15267     }
15268 
15269     /**
15270      * Returns a session-aware text classifier.
15271      * This method creates one if none already exists or the current one is destroyed.
15272      */
15273     @NonNull
getTextClassificationSession()15274     TextClassifier getTextClassificationSession() {
15275         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
15276             final TextClassificationManager tcm = getTextClassificationManagerForUser();
15277             if (tcm != null) {
15278                 final String widgetType;
15279                 if (isTextEditable()) {
15280                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
15281                 } else if (isTextSelectable()) {
15282                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
15283                 } else {
15284                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
15285                 }
15286                 mTextClassificationContext = new TextClassificationContext.Builder(
15287                         mContext.getPackageName(), widgetType)
15288                         .build();
15289                 if (mTextClassifier != null) {
15290                     mTextClassificationSession = tcm.createTextClassificationSession(
15291                             mTextClassificationContext, mTextClassifier);
15292                 } else {
15293                     mTextClassificationSession = tcm.createTextClassificationSession(
15294                             mTextClassificationContext);
15295                 }
15296             } else {
15297                 mTextClassificationSession = TextClassifier.NO_OP;
15298             }
15299         }
15300         return mTextClassificationSession;
15301     }
15302 
15303     /**
15304      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
15305      * @see #getTextClassificationSession()
15306      */
15307     @Nullable
getTextClassificationContext()15308     TextClassificationContext getTextClassificationContext() {
15309         return mTextClassificationContext;
15310     }
15311 
15312     /**
15313      * Returns true if this TextView uses a no-op TextClassifier.
15314      */
usesNoOpTextClassifier()15315     boolean usesNoOpTextClassifier() {
15316         return getTextClassifier() == TextClassifier.NO_OP;
15317     }
15318 
15319     /**
15320      * Starts an ActionMode for the specified TextLinkSpan.
15321      *
15322      * @return Whether or not we're attempting to start the action mode.
15323      * @hide
15324      */
requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)15325     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
15326         Preconditions.checkNotNull(clickedSpan);
15327 
15328         if (!(mText instanceof Spanned)) {
15329             return false;
15330         }
15331 
15332         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
15333         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
15334 
15335         if (start < 0 || end > mText.length() || start >= end) {
15336             return false;
15337         }
15338 
15339         createEditorIfNeeded();
15340         mEditor.startLinkActionModeAsync(start, end);
15341         return true;
15342     }
15343 
15344     /**
15345      * Handles a click on the specified TextLinkSpan.
15346      *
15347      * @return Whether or not the click is being handled.
15348      * @hide
15349      */
handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)15350     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
15351         Preconditions.checkNotNull(clickedSpan);
15352         if (mText instanceof Spanned) {
15353             final Spanned spanned = (Spanned) mText;
15354             final int start = spanned.getSpanStart(clickedSpan);
15355             final int end = spanned.getSpanEnd(clickedSpan);
15356             if (start >= 0 && end <= mText.length() && start < end) {
15357                 final TextClassification.Request request = new TextClassification.Request.Builder(
15358                         mText, start, end)
15359                         .setDefaultLocales(getTextLocales())
15360                         .build();
15361                 final Supplier<TextClassification> supplier = () ->
15362                         getTextClassificationSession().classifyText(request);
15363                 final Consumer<TextClassification> consumer = classification -> {
15364                     if (classification != null) {
15365                         if (!classification.getActions().isEmpty()) {
15366                             try {
15367                                 classification.getActions().get(0).getActionIntent().send();
15368                             } catch (PendingIntent.CanceledException e) {
15369                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
15370                             }
15371                         } else {
15372                             Log.d(LOG_TAG, "No link action to perform");
15373                         }
15374                     } else {
15375                         // classification == null
15376                         Log.d(LOG_TAG, "Timeout while classifying text");
15377                     }
15378                 };
15379                 CompletableFuture.supplyAsync(supplier)
15380                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
15381                         .thenAccept(consumer);
15382                 return true;
15383             }
15384         }
15385         return false;
15386     }
15387 
15388     /**
15389      * @hide
15390      */
15391     @UnsupportedAppUsage
stopTextActionMode()15392     protected void stopTextActionMode() {
15393         if (mEditor != null) {
15394             mEditor.stopTextActionMode();
15395         }
15396     }
15397 
15398     /** @hide */
hideFloatingToolbar(int durationMs)15399     public void hideFloatingToolbar(int durationMs) {
15400         if (mEditor != null) {
15401             mEditor.hideFloatingToolbar(durationMs);
15402         }
15403     }
15404 
canUndo()15405     boolean canUndo() {
15406         return mEditor != null && mEditor.canUndo();
15407     }
15408 
canRedo()15409     boolean canRedo() {
15410         return mEditor != null && mEditor.canRedo();
15411     }
15412 
canCut()15413     boolean canCut() {
15414         if (hasPasswordTransformationMethod()) {
15415             return false;
15416         }
15417 
15418         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
15419                 && mEditor.mKeyListener != null) {
15420             return true;
15421         }
15422 
15423         return false;
15424     }
15425 
canCopy()15426     boolean canCopy() {
15427         if (hasPasswordTransformationMethod()) {
15428             return false;
15429         }
15430 
15431         if (mText.length() > 0 && hasSelection() && mEditor != null) {
15432             return true;
15433         }
15434 
15435         return false;
15436     }
15437 
canReplace()15438     boolean canReplace() {
15439         if (hasPasswordTransformationMethod()) {
15440             return false;
15441         }
15442 
15443         return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null)
15444                 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions();
15445     }
15446 
canShare()15447     boolean canShare() {
15448         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()
15449                 || !getContext().getResources().getBoolean(
15450                 com.android.internal.R.bool.config_textShareSupported)) {
15451             return false;
15452         }
15453         return canCopy();
15454     }
15455 
isDeviceProvisioned()15456     boolean isDeviceProvisioned() {
15457         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
15458             mDeviceProvisionedState = Settings.Global.getInt(
15459                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
15460                     ? DEVICE_PROVISIONED_YES
15461                     : DEVICE_PROVISIONED_NO;
15462         }
15463         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
15464     }
15465 
15466     @UnsupportedAppUsage
canPaste()15467     boolean canPaste() {
15468         return (mText instanceof Editable
15469                 && mEditor != null && mEditor.mKeyListener != null
15470                 && getSelectionStart() >= 0
15471                 && getSelectionEnd() >= 0
15472                 && getClipboardManagerForUser().hasPrimaryClip());
15473     }
15474 
canPasteAsPlainText()15475     boolean canPasteAsPlainText() {
15476         if (!canPaste()) {
15477             return false;
15478         }
15479 
15480         final ClipDescription description =
15481                 getClipboardManagerForUser().getPrimaryClipDescription();
15482         if (description == null) {
15483             return false;
15484         }
15485         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
15486         return (isPlainType && description.isStyledText())
15487                 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
15488     }
15489 
canProcessText()15490     boolean canProcessText() {
15491         if (getId() == View.NO_ID) {
15492             return false;
15493         }
15494         return canShare();
15495     }
15496 
canSelectAllText()15497     boolean canSelectAllText() {
15498         return canSelectText() && !hasPasswordTransformationMethod()
15499                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
15500     }
15501 
selectAllText()15502     boolean selectAllText() {
15503         if (mEditor != null) {
15504             // Hide the toolbar before changing the selection to avoid flickering.
15505             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
15506         }
15507         final int length = mText.length();
15508         Selection.setSelection(mSpannable, 0, length);
15509         return length > 0;
15510     }
15511 
paste(boolean withFormatting)15512     private void paste(boolean withFormatting) {
15513         ClipboardManager clipboard = getClipboardManagerForUser();
15514         ClipData clip = clipboard.getPrimaryClip();
15515         if (clip == null) {
15516             return;
15517         }
15518         final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD)
15519                 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT)
15520                 .build();
15521         performReceiveContent(payload);
15522         sLastCutCopyOrTextChangedTime = 0;
15523     }
15524 
shareSelectedText()15525     private void shareSelectedText() {
15526         String selectedText = getSelectedText();
15527         if (selectedText != null && !selectedText.isEmpty()) {
15528             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
15529             sharingIntent.setType("text/plain");
15530             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
15531             selectedText = TextUtils.trimToParcelableSize(selectedText);
15532             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
15533             getContext().startActivity(Intent.createChooser(sharingIntent, null));
15534             Selection.setSelection(mSpannable, getSelectionEnd());
15535         }
15536     }
15537 
15538     @CheckResult
setPrimaryClip(ClipData clip)15539     private boolean setPrimaryClip(ClipData clip) {
15540         ClipboardManager clipboard = getClipboardManagerForUser();
15541         try {
15542             clipboard.setPrimaryClip(clip);
15543         } catch (Throwable t) {
15544             return false;
15545         }
15546         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
15547         return true;
15548     }
15549 
15550     /**
15551      * Get the character offset closest to the specified absolute position. A typical use case is to
15552      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
15553      *
15554      * @param x The horizontal absolute position of a point on screen
15555      * @param y The vertical absolute position of a point on screen
15556      * @return the character offset for the character whose position is closest to the specified
15557      *  position. Returns -1 if there is no layout.
15558      */
getOffsetForPosition(float x, float y)15559     public int getOffsetForPosition(float x, float y) {
15560         if (getLayout() == null) return -1;
15561         final int line = getLineAtCoordinate(y);
15562         final int offset = getOffsetAtCoordinate(line, x);
15563         return offset;
15564     }
15565 
convertToLocalHorizontalCoordinate(float x)15566     float convertToLocalHorizontalCoordinate(float x) {
15567         x -= getTotalPaddingLeft();
15568         // Clamp the position to inside of the view.
15569         x = Math.max(0.0f, x);
15570         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
15571         x += getScrollX();
15572         return x;
15573     }
15574 
15575     /** @hide */
15576     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getLineAtCoordinate(float y)15577     public int getLineAtCoordinate(float y) {
15578         y -= getTotalPaddingTop();
15579         // Clamp the position to inside of the view.
15580         y = Math.max(0.0f, y);
15581         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
15582         y += getScrollY();
15583         return getLayout().getLineForVertical((int) y);
15584     }
15585 
getLineAtCoordinateUnclamped(float y)15586     int getLineAtCoordinateUnclamped(float y) {
15587         y -= getTotalPaddingTop();
15588         y += getScrollY();
15589         return getLayout().getLineForVertical((int) y);
15590     }
15591 
getOffsetAtCoordinate(int line, float x)15592     int getOffsetAtCoordinate(int line, float x) {
15593         x = convertToLocalHorizontalCoordinate(x);
15594         final int offset = getLayout().getOffsetForHorizontal(line, x);
15595         return transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
15596     }
15597 
15598     /**
15599      * Convenient method to convert an offset on the transformed text to the original text.
15600      * @hide
15601      */
transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy)15602     public int transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy) {
15603         if (getTransformationMethod() == null) {
15604             return offset;
15605         }
15606         if (mTransformed instanceof OffsetMapping) {
15607             final OffsetMapping transformedText = (OffsetMapping) mTransformed;
15608             return transformedText.transformedToOriginal(offset, strategy);
15609         }
15610         return offset;
15611     }
15612 
15613     /**
15614      * Convenient method to convert an offset on the original text to the transformed text.
15615      * @hide
15616      */
originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy)15617     public int originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy) {
15618         if (getTransformationMethod() == null) {
15619             return offset;
15620         }
15621         if (mTransformed instanceof OffsetMapping) {
15622             final OffsetMapping transformedText = (OffsetMapping) mTransformed;
15623             return transformedText.originalToTransformed(offset, strategy);
15624         }
15625         return offset;
15626     }
15627     /**
15628      * Handles drag events sent by the system following a call to
15629      * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
15630      * startDragAndDrop()}.
15631      *
15632      * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent}
15633      * implementation.
15634      *
15635      * <p>If this text view is editable, accepts all drag actions (returns true for an
15636      * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all
15637      * subsequent drag events). While the drag is in progress, updates the cursor position
15638      * to follow the touch location. Once a drop event is received, handles content insertion
15639      * via {@link #performReceiveContent}.
15640      *
15641      * @param event The {@link android.view.DragEvent} sent by the system.
15642      * The {@link android.view.DragEvent#getAction()} method returns an action type constant
15643      * defined in DragEvent, indicating the type of drag event represented by this object.
15644      * @return Returns true if this text view is editable and delegates to super otherwise.
15645      * See {@link View#onDragEvent}.
15646      */
15647     @Override
onDragEvent(DragEvent event)15648     public boolean onDragEvent(DragEvent event) {
15649         if (mEditor == null || !mEditor.hasInsertionController()) {
15650             // If this TextView is not editable, defer to the default View implementation. This
15651             // will check for the presence of an OnReceiveContentListener and accept/reject
15652             // drag events depending on whether the listener is/isn't set.
15653             return super.onDragEvent(event);
15654         }
15655         switch (event.getAction()) {
15656             case DragEvent.ACTION_DRAG_STARTED:
15657                 return true;
15658 
15659             case DragEvent.ACTION_DRAG_ENTERED:
15660                 TextView.this.requestFocus();
15661                 return true;
15662 
15663             case DragEvent.ACTION_DRAG_LOCATION:
15664                 if (mText instanceof Spannable) {
15665                     final int offset = getOffsetForPosition(event.getX(), event.getY());
15666                     Selection.setSelection(mSpannable, offset);
15667                 }
15668                 return true;
15669 
15670             case DragEvent.ACTION_DROP:
15671                 if (mEditor != null) mEditor.onDrop(event);
15672                 return true;
15673 
15674             case DragEvent.ACTION_DRAG_ENDED:
15675             case DragEvent.ACTION_DRAG_EXITED:
15676             default:
15677                 return true;
15678         }
15679     }
15680 
isInBatchEditMode()15681     boolean isInBatchEditMode() {
15682         if (mEditor == null) return false;
15683         final Editor.InputMethodState ims = mEditor.mInputMethodState;
15684         if (ims != null) {
15685             return ims.mBatchEditNesting > 0;
15686         }
15687         return mEditor.mInBatchEditControllers;
15688     }
15689 
15690     @Override
onRtlPropertiesChanged(int layoutDirection)15691     public void onRtlPropertiesChanged(int layoutDirection) {
15692         super.onRtlPropertiesChanged(layoutDirection);
15693 
15694         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
15695         if (mTextDir != newTextDir) {
15696             mTextDir = newTextDir;
15697             if (mLayout != null) {
15698                 checkForRelayout();
15699             }
15700         }
15701     }
15702 
15703     /**
15704      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
15705      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
15706      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
15707      * return value may not be the same as the one TextView uses if the View's layout direction is
15708      * not resolved or detached from parent root view.
15709      */
getTextDirectionHeuristic()15710     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
15711         if (hasPasswordTransformationMethod()) {
15712             // passwords fields should be LTR
15713             return TextDirectionHeuristics.LTR;
15714         }
15715 
15716         if (mEditor != null
15717                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
15718                     == EditorInfo.TYPE_CLASS_PHONE) {
15719             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
15720             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
15721             // RTL digits.
15722             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
15723             final String zero = symbols.getDigitStrings()[0];
15724             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
15725             // direction.
15726             final int firstCodepoint = zero.codePointAt(0);
15727             final byte digitDirection = Character.getDirectionality(firstCodepoint);
15728             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
15729                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
15730                 return TextDirectionHeuristics.RTL;
15731             } else {
15732                 return TextDirectionHeuristics.LTR;
15733             }
15734         }
15735 
15736         // Always need to resolve layout direction first
15737         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
15738 
15739         // Now, we can select the heuristic
15740         switch (getTextDirection()) {
15741             default:
15742             case TEXT_DIRECTION_FIRST_STRONG:
15743                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
15744                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
15745             case TEXT_DIRECTION_ANY_RTL:
15746                 return TextDirectionHeuristics.ANYRTL_LTR;
15747             case TEXT_DIRECTION_LTR:
15748                 return TextDirectionHeuristics.LTR;
15749             case TEXT_DIRECTION_RTL:
15750                 return TextDirectionHeuristics.RTL;
15751             case TEXT_DIRECTION_LOCALE:
15752                 return TextDirectionHeuristics.LOCALE;
15753             case TEXT_DIRECTION_FIRST_STRONG_LTR:
15754                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
15755             case TEXT_DIRECTION_FIRST_STRONG_RTL:
15756                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
15757         }
15758     }
15759 
15760     /**
15761      * @hide
15762      */
15763     @Override
onResolveDrawables(int layoutDirection)15764     public void onResolveDrawables(int layoutDirection) {
15765         // No need to resolve twice
15766         if (mLastLayoutDirection == layoutDirection) {
15767             return;
15768         }
15769         mLastLayoutDirection = layoutDirection;
15770 
15771         // Resolve drawables
15772         if (mDrawables != null) {
15773             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
15774                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
15775                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
15776                 applyCompoundDrawableTint();
15777             }
15778         }
15779     }
15780 
15781     /**
15782      * Prepares a drawable for display by propagating layout direction and
15783      * drawable state.
15784      *
15785      * @param dr the drawable to prepare
15786      */
prepareDrawableForDisplay(@ullable Drawable dr)15787     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
15788         if (dr == null) {
15789             return;
15790         }
15791 
15792         dr.setLayoutDirection(getLayoutDirection());
15793 
15794         if (dr.isStateful()) {
15795             dr.setState(getDrawableState());
15796             dr.jumpToCurrentState();
15797         }
15798     }
15799 
15800     /**
15801      * @hide
15802      */
resetResolvedDrawables()15803     protected void resetResolvedDrawables() {
15804         super.resetResolvedDrawables();
15805         mLastLayoutDirection = -1;
15806     }
15807 
15808     /**
15809      * @hide
15810      */
viewClicked(InputMethodManager imm)15811     protected void viewClicked(InputMethodManager imm) {
15812         if (imm != null) {
15813             imm.viewClicked(this);
15814         }
15815     }
15816 
15817     /**
15818      * Deletes the range of text [start, end[.
15819      * @hide
15820      */
15821     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
deleteText_internal(int start, int end)15822     protected void deleteText_internal(int start, int end) {
15823         ((Editable) mText).delete(start, end);
15824     }
15825 
15826     /**
15827      * Replaces the range of text [start, end[ by replacement text
15828      * @hide
15829      */
replaceText_internal(int start, int end, CharSequence text)15830     protected void replaceText_internal(int start, int end, CharSequence text) {
15831         ((Editable) mText).replace(start, end, text);
15832     }
15833 
15834     /**
15835      * Sets a span on the specified range of text
15836      * @hide
15837      */
setSpan_internal(Object span, int start, int end, int flags)15838     protected void setSpan_internal(Object span, int start, int end, int flags) {
15839         ((Editable) mText).setSpan(span, start, end, flags);
15840     }
15841 
15842     /**
15843      * Moves the cursor to the specified offset position in text
15844      * @hide
15845      */
setCursorPosition_internal(int start, int end)15846     protected void setCursorPosition_internal(int start, int end) {
15847         Selection.setSelection(((Editable) mText), start, end);
15848     }
15849 
15850     /**
15851      * An Editor should be created as soon as any of the editable-specific fields (grouped
15852      * inside the Editor object) is assigned to a non-default value.
15853      * This method will create the Editor if needed.
15854      *
15855      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
15856      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
15857      * Editor for backward compatibility, as soon as one of these fields is assigned.
15858      *
15859      * Also note that for performance reasons, the mEditor is created when needed, but not
15860      * reset when no more edit-specific fields are needed.
15861      */
15862     @UnsupportedAppUsage
createEditorIfNeeded()15863     private void createEditorIfNeeded() {
15864         if (mEditor == null) {
15865             mEditor = new Editor(this);
15866         }
15867     }
15868 
15869     /**
15870      * @hide
15871      */
15872     @Override
15873     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getIterableTextForAccessibility()15874     public CharSequence getIterableTextForAccessibility() {
15875         return mText;
15876     }
15877 
ensureIterableTextForAccessibilitySelectable()15878     private void ensureIterableTextForAccessibilitySelectable() {
15879         if (!(mText instanceof Spannable)) {
15880             setText(mText, BufferType.SPANNABLE);
15881             if (getLayout() == null) {
15882                 assumeLayout();
15883             }
15884         }
15885     }
15886 
15887     /**
15888      * @hide
15889      */
15890     @Override
getIteratorForGranularity(int granularity)15891     public TextSegmentIterator getIteratorForGranularity(int granularity) {
15892         switch (granularity) {
15893             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
15894                 Spannable text = (Spannable) getIterableTextForAccessibility();
15895                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
15896                     AccessibilityIterators.LineTextSegmentIterator iterator =
15897                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
15898                     iterator.initialize(text, getLayout());
15899                     return iterator;
15900                 }
15901             } break;
15902             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
15903                 Spannable text = (Spannable) getIterableTextForAccessibility();
15904                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
15905                     AccessibilityIterators.PageTextSegmentIterator iterator =
15906                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
15907                     iterator.initialize(this);
15908                     return iterator;
15909                 }
15910             } break;
15911         }
15912         return super.getIteratorForGranularity(granularity);
15913     }
15914 
15915     /**
15916      * @hide
15917      */
15918     @Override
getAccessibilitySelectionStart()15919     public int getAccessibilitySelectionStart() {
15920         return getSelectionStart();
15921     }
15922 
15923     /**
15924      * @hide
15925      */
isAccessibilitySelectionExtendable()15926     public boolean isAccessibilitySelectionExtendable() {
15927         return true;
15928     }
15929 
15930     /**
15931      * @hide
15932      */
prepareForExtendedAccessibilitySelection()15933     public void prepareForExtendedAccessibilitySelection() {
15934         requestFocusOnNonEditableSelectableText();
15935     }
15936 
15937     /**
15938      * @hide
15939      */
15940     @Override
getAccessibilitySelectionEnd()15941     public int getAccessibilitySelectionEnd() {
15942         return getSelectionEnd();
15943     }
15944 
15945     /**
15946      * @hide
15947      */
15948     @Override
setAccessibilitySelection(int start, int end)15949     public void setAccessibilitySelection(int start, int end) {
15950         if (getAccessibilitySelectionStart() == start
15951                 && getAccessibilitySelectionEnd() == end) {
15952             return;
15953         }
15954         CharSequence text = getIterableTextForAccessibility();
15955         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
15956             Selection.setSelection((Spannable) text, start, end);
15957         } else {
15958             Selection.removeSelection((Spannable) text);
15959         }
15960         // Hide all selection controllers used for adjusting selection
15961         // since we are doing so explicitlty by other means and these
15962         // controllers interact with how selection behaves.
15963         if (mEditor != null) {
15964             mEditor.hideCursorAndSpanControllers();
15965             mEditor.stopTextActionMode();
15966         }
15967     }
15968 
15969     /** @hide */
15970     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)15971     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
15972         super.encodeProperties(stream);
15973 
15974         TruncateAt ellipsize = getEllipsize();
15975         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
15976         stream.addProperty("text:textSize", getTextSize());
15977         stream.addProperty("text:scaledTextSize", getScaledTextSize());
15978         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
15979         stream.addProperty("text:selectionStart", getSelectionStart());
15980         stream.addProperty("text:selectionEnd", getSelectionEnd());
15981         stream.addProperty("text:curTextColor", mCurTextColor);
15982         stream.addUserProperty("text:text", mText == null ? null : mText.toString());
15983         stream.addProperty("text:gravity", mGravity);
15984     }
15985 
15986     /**
15987      * User interface state that is stored by TextView for implementing
15988      * {@link View#onSaveInstanceState}.
15989      */
15990     public static class SavedState extends BaseSavedState {
15991         int selStart = -1;
15992         int selEnd = -1;
15993         @UnsupportedAppUsage
15994         CharSequence text;
15995         boolean frozenWithFocus;
15996         CharSequence error;
15997         ParcelableParcel editorState;  // Optional state from Editor.
15998 
SavedState(Parcelable superState)15999         SavedState(Parcelable superState) {
16000             super(superState);
16001         }
16002 
16003         @Override
writeToParcel(Parcel out, int flags)16004         public void writeToParcel(Parcel out, int flags) {
16005             super.writeToParcel(out, flags);
16006             out.writeInt(selStart);
16007             out.writeInt(selEnd);
16008             out.writeInt(frozenWithFocus ? 1 : 0);
16009             TextUtils.writeToParcel(text, out, flags);
16010 
16011             if (error == null) {
16012                 out.writeInt(0);
16013             } else {
16014                 out.writeInt(1);
16015                 TextUtils.writeToParcel(error, out, flags);
16016             }
16017 
16018             if (editorState == null) {
16019                 out.writeInt(0);
16020             } else {
16021                 out.writeInt(1);
16022                 editorState.writeToParcel(out, flags);
16023             }
16024         }
16025 
16026         @Override
toString()16027         public String toString() {
16028             String str = "TextView.SavedState{"
16029                     + Integer.toHexString(System.identityHashCode(this))
16030                     + " start=" + selStart + " end=" + selEnd;
16031             if (text != null) {
16032                 str += " text=" + text;
16033             }
16034             return str + "}";
16035         }
16036 
16037         @SuppressWarnings("hiding")
16038         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
16039                 new Parcelable.Creator<SavedState>() {
16040                     public SavedState createFromParcel(Parcel in) {
16041                         return new SavedState(in);
16042                     }
16043 
16044                     public SavedState[] newArray(int size) {
16045                         return new SavedState[size];
16046                     }
16047                 };
16048 
SavedState(Parcel in)16049         private SavedState(Parcel in) {
16050             super(in);
16051             selStart = in.readInt();
16052             selEnd = in.readInt();
16053             frozenWithFocus = (in.readInt() != 0);
16054             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
16055 
16056             if (in.readInt() != 0) {
16057                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
16058             }
16059 
16060             if (in.readInt() != 0) {
16061                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
16062             }
16063         }
16064     }
16065 
16066     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
16067         @NonNull
16068         private char[] mChars;
16069         private int mStart, mLength;
16070 
CharWrapper(@onNull char[] chars, int start, int len)16071         CharWrapper(@NonNull char[] chars, int start, int len) {
16072             mChars = chars;
16073             mStart = start;
16074             mLength = len;
16075         }
16076 
set(@onNull char[] chars, int start, int len)16077         /* package */ void set(@NonNull char[] chars, int start, int len) {
16078             mChars = chars;
16079             mStart = start;
16080             mLength = len;
16081         }
16082 
length()16083         public int length() {
16084             return mLength;
16085         }
16086 
charAt(int off)16087         public char charAt(int off) {
16088             return mChars[off + mStart];
16089         }
16090 
16091         @Override
toString()16092         public String toString() {
16093             return new String(mChars, mStart, mLength);
16094         }
16095 
subSequence(int start, int end)16096         public CharSequence subSequence(int start, int end) {
16097             if (start < 0 || end < 0 || start > mLength || end > mLength) {
16098                 throw new IndexOutOfBoundsException(start + ", " + end);
16099             }
16100 
16101             return new String(mChars, start + mStart, end - start);
16102         }
16103 
getChars(int start, int end, char[] buf, int off)16104         public void getChars(int start, int end, char[] buf, int off) {
16105             if (start < 0 || end < 0 || start > mLength || end > mLength) {
16106                 throw new IndexOutOfBoundsException(start + ", " + end);
16107             }
16108 
16109             System.arraycopy(mChars, start + mStart, buf, off, end - start);
16110         }
16111 
16112         @Override
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)16113         public void drawText(BaseCanvas c, int start, int end,
16114                              float x, float y, Paint p) {
16115             c.drawText(mChars, start + mStart, end - start, x, y, p);
16116         }
16117 
16118         @Override
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)16119         public void drawTextRun(BaseCanvas c, int start, int end,
16120                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
16121             int count = end - start;
16122             int contextCount = contextEnd - contextStart;
16123             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
16124                     contextCount, x, y, isRtl, p);
16125         }
16126 
measureText(int start, int end, Paint p)16127         public float measureText(int start, int end, Paint p) {
16128             return p.measureText(mChars, start + mStart, end - start);
16129         }
16130 
getTextWidths(int start, int end, float[] widths, Paint p)16131         public int getTextWidths(int start, int end, float[] widths, Paint p) {
16132             return p.getTextWidths(mChars, start + mStart, end - start, widths);
16133         }
16134 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)16135         public float getTextRunAdvances(int start, int end, int contextStart,
16136                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
16137                 Paint p) {
16138             int count = end - start;
16139             int contextCount = contextEnd - contextStart;
16140             return p.getTextRunAdvances(mChars, start + mStart, count,
16141                     contextStart + mStart, contextCount, isRtl, advances,
16142                     advancesIndex);
16143         }
16144 
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)16145         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
16146                 int offset, int cursorOpt, Paint p) {
16147             int contextCount = contextEnd - contextStart;
16148             return p.getTextRunCursor(mChars, contextStart + mStart,
16149                     contextCount, isRtl, offset + mStart, cursorOpt);
16150         }
16151     }
16152 
16153     private static final class Marquee {
16154         // TODO: Add an option to configure this
16155         private static final float MARQUEE_DELTA_MAX = 0.07f;
16156         private static final int MARQUEE_DELAY = 1200;
16157         private static final int MARQUEE_DP_PER_SECOND = 30;
16158 
16159         private static final byte MARQUEE_STOPPED = 0x0;
16160         private static final byte MARQUEE_STARTING = 0x1;
16161         private static final byte MARQUEE_RUNNING = 0x2;
16162 
16163         private final WeakReference<TextView> mView;
16164         private final Choreographer mChoreographer;
16165 
16166         private byte mStatus = MARQUEE_STOPPED;
16167         private final float mPixelsPerMs;
16168         private float mMaxScroll;
16169         private float mMaxFadeScroll;
16170         private float mGhostStart;
16171         private float mGhostOffset;
16172         private float mFadeStop;
16173         private int mRepeatLimit;
16174 
16175         private float mScroll;
16176         private long mLastAnimationMs;
16177 
Marquee(TextView v)16178         Marquee(TextView v) {
16179             final float density = v.getContext().getResources().getDisplayMetrics().density;
16180             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
16181             mView = new WeakReference<TextView>(v);
16182             mChoreographer = Choreographer.getInstance();
16183         }
16184 
16185         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
16186             @Override
16187             public void doFrame(long frameTimeNanos) {
16188                 tick();
16189             }
16190         };
16191 
16192         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
16193             @Override
16194             public void doFrame(long frameTimeNanos) {
16195                 mStatus = MARQUEE_RUNNING;
16196                 mLastAnimationMs = mChoreographer.getFrameTime();
16197                 tick();
16198             }
16199         };
16200 
16201         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
16202             @Override
16203             public void doFrame(long frameTimeNanos) {
16204                 if (mStatus == MARQUEE_RUNNING) {
16205                     if (mRepeatLimit >= 0) {
16206                         mRepeatLimit--;
16207                     }
16208                     start(mRepeatLimit);
16209                 }
16210             }
16211         };
16212 
tick()16213         void tick() {
16214             if (mStatus != MARQUEE_RUNNING) {
16215                 return;
16216             }
16217 
16218             mChoreographer.removeFrameCallback(mTickCallback);
16219 
16220             final TextView textView = mView.get();
16221             if (textView != null && textView.isAggregatedVisible()
16222                     && (textView.isFocused() || textView.isSelected())) {
16223                 long currentMs = mChoreographer.getFrameTime();
16224                 long deltaMs = currentMs - mLastAnimationMs;
16225                 mLastAnimationMs = currentMs;
16226                 float deltaPx = deltaMs * mPixelsPerMs;
16227                 mScroll += deltaPx;
16228                 if (mScroll > mMaxScroll) {
16229                     mScroll = mMaxScroll;
16230                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
16231                 } else {
16232                     mChoreographer.postFrameCallback(mTickCallback);
16233                 }
16234                 textView.invalidate();
16235             }
16236         }
16237 
stop()16238         void stop() {
16239             mStatus = MARQUEE_STOPPED;
16240             mChoreographer.removeFrameCallback(mStartCallback);
16241             mChoreographer.removeFrameCallback(mRestartCallback);
16242             mChoreographer.removeFrameCallback(mTickCallback);
16243             resetScroll();
16244         }
16245 
resetScroll()16246         private void resetScroll() {
16247             mScroll = 0.0f;
16248             final TextView textView = mView.get();
16249             if (textView != null) textView.invalidate();
16250         }
16251 
start(int repeatLimit)16252         void start(int repeatLimit) {
16253             if (repeatLimit == 0) {
16254                 stop();
16255                 return;
16256             }
16257             mRepeatLimit = repeatLimit;
16258             final TextView textView = mView.get();
16259             if (textView != null && textView.mLayout != null) {
16260                 mStatus = MARQUEE_STARTING;
16261                 mScroll = 0.0f;
16262                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
16263                         - textView.getCompoundPaddingRight();
16264                 final float lineWidth = textView.mLayout.getLineWidth(0);
16265                 final float gap = textWidth / 3.0f;
16266                 mGhostStart = lineWidth - textWidth + gap;
16267                 mMaxScroll = mGhostStart + textWidth;
16268                 mGhostOffset = lineWidth + gap;
16269                 mFadeStop = lineWidth + textWidth / 6.0f;
16270                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
16271 
16272                 textView.invalidate();
16273                 mChoreographer.postFrameCallback(mStartCallback);
16274             }
16275         }
16276 
getGhostOffset()16277         float getGhostOffset() {
16278             return mGhostOffset;
16279         }
16280 
getScroll()16281         float getScroll() {
16282             return mScroll;
16283         }
16284 
getMaxFadeScroll()16285         float getMaxFadeScroll() {
16286             return mMaxFadeScroll;
16287         }
16288 
shouldDrawLeftFade()16289         boolean shouldDrawLeftFade() {
16290             return mScroll <= mFadeStop;
16291         }
16292 
shouldDrawGhost()16293         boolean shouldDrawGhost() {
16294             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
16295         }
16296 
isRunning()16297         boolean isRunning() {
16298             return mStatus == MARQUEE_RUNNING;
16299         }
16300 
isStopped()16301         boolean isStopped() {
16302             return mStatus == MARQUEE_STOPPED;
16303         }
16304     }
16305 
16306     private class ChangeWatcher implements TextWatcher, SpanWatcher {
16307 
16308         private CharSequence mBeforeText;
16309 
beforeTextChanged(CharSequence buffer, int start, int before, int after)16310         public void beforeTextChanged(CharSequence buffer, int start,
16311                                       int before, int after) {
16312             if (DEBUG_EXTRACT) {
16313                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
16314                         + " before=" + before + " after=" + after + ": " + buffer);
16315             }
16316 
16317             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
16318                 mBeforeText = mTransformed.toString();
16319             }
16320 
16321             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
16322         }
16323 
onTextChanged(CharSequence buffer, int start, int before, int after)16324         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
16325             if (DEBUG_EXTRACT) {
16326                 Log.v(LOG_TAG, "onTextChanged start=" + start
16327                         + " before=" + before + " after=" + after + ": " + buffer);
16328             }
16329             TextView.this.handleTextChanged(buffer, start, before, after);
16330 
16331             if (isVisibleToAccessibility()) {
16332                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
16333                 mBeforeText = null;
16334             }
16335         }
16336 
afterTextChanged(Editable buffer)16337         public void afterTextChanged(Editable buffer) {
16338             if (DEBUG_EXTRACT) {
16339                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
16340             }
16341             TextView.this.sendAfterTextChanged(buffer);
16342 
16343             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
16344                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
16345             }
16346         }
16347 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)16348         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
16349             if (DEBUG_EXTRACT) {
16350                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
16351                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
16352             }
16353             TextView.this.spanChange(buf, what, s, st, e, en);
16354         }
16355 
onSpanAdded(Spannable buf, Object what, int s, int e)16356         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
16357             if (DEBUG_EXTRACT) {
16358                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
16359             }
16360             TextView.this.spanChange(buf, what, -1, s, -1, e);
16361         }
16362 
onSpanRemoved(Spannable buf, Object what, int s, int e)16363         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
16364             if (DEBUG_EXTRACT) {
16365                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
16366             }
16367             TextView.this.spanChange(buf, what, s, -1, e, -1);
16368         }
16369     }
16370 
16371     /** @hide */
16372     @Override
onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)16373     public void onInputConnectionOpenedInternal(@NonNull InputConnection ic,
16374             @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
16375         if (mEditor != null) {
16376             mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic,
16377                     editorInfo);
16378         }
16379     }
16380 
16381     /** @hide */
16382     @Override
onInputConnectionClosedInternal()16383     public void onInputConnectionClosedInternal() {
16384         if (mEditor != null) {
16385             mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo();
16386         }
16387     }
16388 
16389     /**
16390      * Default {@link TextView} implementation for receiving content. Apps wishing to provide
16391      * custom behavior should configure a listener via {@link #setOnReceiveContentListener}.
16392      *
16393      * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in
16394      * content without acting on it).
16395      *
16396      * <p>For editable TextViews the default behavior is to insert text into the view, coercing
16397      * non-text content to text as needed. The MIME types "text/plain" and "text/html" have
16398      * well-defined behavior for this, while other MIME types have reasonable fallback behavior
16399      * (see {@link ClipData.Item#coerceToStyledText}).
16400      *
16401      * @param payload The content to insert and related metadata.
16402      *
16403      * @return The portion of the passed-in content that was not handled (may be all, some, or none
16404      * of the passed-in content).
16405      */
16406     @Nullable
16407     @Override
onReceiveContent(@onNull ContentInfo payload)16408     public ContentInfo onReceiveContent(@NonNull ContentInfo payload) {
16409         if (mEditor != null) {
16410             return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload);
16411         }
16412         return payload;
16413     }
16414 
logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)16415     private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
16416         if (msgFormat == null) {
16417             Log.d(LOG_TAG, location);
16418         } else {
16419             Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs));
16420         }
16421     }
16422 
16423     /**
16424      * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
16425      * the view.
16426      *
16427      * <p>NOTE: When overriding the method, it should not collect a request to translate this
16428      * TextView if it is displaying a password.
16429      *
16430      * @param supportedFormats the supported translation format. The value could be {@link
16431      *                         android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
16432      * @param requestsCollector {@link Consumer} to receiver the {@link ViewTranslationRequest}
16433      *                                         which contains the information to be translated.
16434      */
16435     @Override
onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)16436     public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats,
16437             @NonNull Consumer<ViewTranslationRequest> requestsCollector) {
16438         if (supportedFormats == null || supportedFormats.length == 0) {
16439             if (UiTranslationController.DEBUG) {
16440                 Log.w(LOG_TAG, "Do not provide the support translation formats.");
16441             }
16442             return;
16443         }
16444         ViewTranslationRequest.Builder requestBuilder =
16445                 new ViewTranslationRequest.Builder(getAutofillId());
16446         // Support Text translation
16447         if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) {
16448             if (mText == null || mText.length() == 0) {
16449                 if (UiTranslationController.DEBUG) {
16450                     Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
16451                 }
16452                 return;
16453             }
16454             boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
16455             if (isTextEditable() || isPassword) {
16456                 Log.w(LOG_TAG, "Cannot create translation request. editable = "
16457                         + isTextEditable() + ", isPassword = " + isPassword);
16458                 return;
16459             }
16460             // TODO(b/176488462): apply the view's important for translation
16461             requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
16462                     TranslationRequestValue.forText(mText));
16463             if (!TextUtils.isEmpty(getContentDescription())) {
16464                 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION,
16465                         TranslationRequestValue.forText(getContentDescription()));
16466             }
16467         }
16468         requestsCollector.accept(requestBuilder.build());
16469     }
16470 }
16471