OSDN Git Service

Merge "docs: Add documentation for equals() method" into qt-dev
[android-x86/frameworks-base.git] / core / java / android / widget / TextView.java
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.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
23 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
24
25 import android.R;
26 import android.annotation.CheckResult;
27 import android.annotation.ColorInt;
28 import android.annotation.DrawableRes;
29 import android.annotation.FloatRange;
30 import android.annotation.IntDef;
31 import android.annotation.IntRange;
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.annotation.Px;
35 import android.annotation.RequiresPermission;
36 import android.annotation.Size;
37 import android.annotation.StringRes;
38 import android.annotation.StyleRes;
39 import android.annotation.UnsupportedAppUsage;
40 import android.annotation.XmlRes;
41 import android.app.Activity;
42 import android.app.PendingIntent;
43 import android.app.assist.AssistStructure;
44 import android.content.ClipData;
45 import android.content.ClipDescription;
46 import android.content.ClipboardManager;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.content.UndoManager;
50 import android.content.pm.PackageManager;
51 import android.content.res.ColorStateList;
52 import android.content.res.CompatibilityInfo;
53 import android.content.res.Configuration;
54 import android.content.res.Resources;
55 import android.content.res.TypedArray;
56 import android.content.res.XmlResourceParser;
57 import android.graphics.BaseCanvas;
58 import android.graphics.BlendMode;
59 import android.graphics.Canvas;
60 import android.graphics.Insets;
61 import android.graphics.Paint;
62 import android.graphics.Paint.FontMetricsInt;
63 import android.graphics.Path;
64 import android.graphics.PorterDuff;
65 import android.graphics.Rect;
66 import android.graphics.RectF;
67 import android.graphics.Typeface;
68 import android.graphics.drawable.Drawable;
69 import android.graphics.fonts.FontStyle;
70 import android.graphics.fonts.FontVariationAxis;
71 import android.icu.text.DecimalFormatSymbols;
72 import android.os.AsyncTask;
73 import android.os.Build;
74 import android.os.Build.VERSION_CODES;
75 import android.os.Bundle;
76 import android.os.LocaleList;
77 import android.os.Parcel;
78 import android.os.Parcelable;
79 import android.os.ParcelableParcel;
80 import android.os.Process;
81 import android.os.SystemClock;
82 import android.os.UserHandle;
83 import android.provider.Settings;
84 import android.text.BoringLayout;
85 import android.text.DynamicLayout;
86 import android.text.Editable;
87 import android.text.GetChars;
88 import android.text.GraphicsOperations;
89 import android.text.InputFilter;
90 import android.text.InputType;
91 import android.text.Layout;
92 import android.text.ParcelableSpan;
93 import android.text.PrecomputedText;
94 import android.text.Selection;
95 import android.text.SpanWatcher;
96 import android.text.Spannable;
97 import android.text.SpannableStringBuilder;
98 import android.text.Spanned;
99 import android.text.SpannedString;
100 import android.text.StaticLayout;
101 import android.text.TextDirectionHeuristic;
102 import android.text.TextDirectionHeuristics;
103 import android.text.TextPaint;
104 import android.text.TextUtils;
105 import android.text.TextUtils.TruncateAt;
106 import android.text.TextWatcher;
107 import android.text.method.AllCapsTransformationMethod;
108 import android.text.method.ArrowKeyMovementMethod;
109 import android.text.method.DateKeyListener;
110 import android.text.method.DateTimeKeyListener;
111 import android.text.method.DialerKeyListener;
112 import android.text.method.DigitsKeyListener;
113 import android.text.method.KeyListener;
114 import android.text.method.LinkMovementMethod;
115 import android.text.method.MetaKeyKeyListener;
116 import android.text.method.MovementMethod;
117 import android.text.method.PasswordTransformationMethod;
118 import android.text.method.SingleLineTransformationMethod;
119 import android.text.method.TextKeyListener;
120 import android.text.method.TimeKeyListener;
121 import android.text.method.TransformationMethod;
122 import android.text.method.TransformationMethod2;
123 import android.text.method.WordIterator;
124 import android.text.style.CharacterStyle;
125 import android.text.style.ClickableSpan;
126 import android.text.style.ParagraphStyle;
127 import android.text.style.SpellCheckSpan;
128 import android.text.style.SuggestionSpan;
129 import android.text.style.URLSpan;
130 import android.text.style.UpdateAppearance;
131 import android.text.util.Linkify;
132 import android.util.AttributeSet;
133 import android.util.DisplayMetrics;
134 import android.util.IntArray;
135 import android.util.Log;
136 import android.util.SparseIntArray;
137 import android.util.TypedValue;
138 import android.view.AccessibilityIterators.TextSegmentIterator;
139 import android.view.ActionMode;
140 import android.view.Choreographer;
141 import android.view.ContextMenu;
142 import android.view.DragEvent;
143 import android.view.Gravity;
144 import android.view.HapticFeedbackConstants;
145 import android.view.InputDevice;
146 import android.view.KeyCharacterMap;
147 import android.view.KeyEvent;
148 import android.view.MotionEvent;
149 import android.view.PointerIcon;
150 import android.view.View;
151 import android.view.ViewConfiguration;
152 import android.view.ViewDebug;
153 import android.view.ViewGroup.LayoutParams;
154 import android.view.ViewHierarchyEncoder;
155 import android.view.ViewParent;
156 import android.view.ViewRootImpl;
157 import android.view.ViewStructure;
158 import android.view.ViewTreeObserver;
159 import android.view.accessibility.AccessibilityEvent;
160 import android.view.accessibility.AccessibilityManager;
161 import android.view.accessibility.AccessibilityNodeInfo;
162 import android.view.animation.AnimationUtils;
163 import android.view.autofill.AutofillManager;
164 import android.view.autofill.AutofillValue;
165 import android.view.inputmethod.BaseInputConnection;
166 import android.view.inputmethod.CompletionInfo;
167 import android.view.inputmethod.CorrectionInfo;
168 import android.view.inputmethod.CursorAnchorInfo;
169 import android.view.inputmethod.EditorInfo;
170 import android.view.inputmethod.ExtractedText;
171 import android.view.inputmethod.ExtractedTextRequest;
172 import android.view.inputmethod.InputConnection;
173 import android.view.inputmethod.InputMethodManager;
174 import android.view.inspector.InspectableProperty;
175 import android.view.inspector.InspectableProperty.EnumEntry;
176 import android.view.inspector.InspectableProperty.FlagEntry;
177 import android.view.textclassifier.TextClassification;
178 import android.view.textclassifier.TextClassificationContext;
179 import android.view.textclassifier.TextClassificationManager;
180 import android.view.textclassifier.TextClassifier;
181 import android.view.textclassifier.TextLinks;
182 import android.view.textservice.SpellCheckerSubtype;
183 import android.view.textservice.TextServicesManager;
184 import android.widget.RemoteViews.RemoteView;
185
186 import com.android.internal.annotations.VisibleForTesting;
187 import com.android.internal.logging.MetricsLogger;
188 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
189 import com.android.internal.util.FastMath;
190 import com.android.internal.util.Preconditions;
191 import com.android.internal.widget.EditableInputConnection;
192
193 import libcore.util.EmptyArray;
194
195 import org.xmlpull.v1.XmlPullParserException;
196
197 import java.io.IOException;
198 import java.lang.annotation.Retention;
199 import java.lang.annotation.RetentionPolicy;
200 import java.lang.ref.WeakReference;
201 import java.util.ArrayList;
202 import java.util.Arrays;
203 import java.util.Locale;
204 import java.util.Objects;
205 import java.util.concurrent.CompletableFuture;
206 import java.util.concurrent.TimeUnit;
207 import java.util.function.Consumer;
208 import java.util.function.Supplier;
209
210 /**
211  * A user interface element that displays text to the user.
212  * To provide user-editable text, see {@link EditText}.
213  * <p>
214  * The following code sample shows a typical use, with an XML layout
215  * and code to modify the contents of the text view:
216  * </p>
217
218  * <pre>
219  * &lt;LinearLayout
220        xmlns:android="http://schemas.android.com/apk/res/android"
221        android:layout_width="match_parent"
222        android:layout_height="match_parent"&gt;
223  *    &lt;TextView
224  *        android:id="@+id/text_view_id"
225  *        android:layout_height="wrap_content"
226  *        android:layout_width="wrap_content"
227  *        android:text="@string/hello" /&gt;
228  * &lt;/LinearLayout&gt;
229  * </pre>
230  * <p>
231  * This code sample demonstrates how to modify the contents of the text view
232  * defined in the previous XML layout:
233  * </p>
234  * <pre>
235  * public class MainActivity extends Activity {
236  *
237  *    protected void onCreate(Bundle savedInstanceState) {
238  *         super.onCreate(savedInstanceState);
239  *         setContentView(R.layout.activity_main);
240  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
241  *         helloTextView.setText(R.string.user_greeting);
242  *     }
243  * }
244  * </pre>
245  * <p>
246  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
247  * </p>
248  * <p>
249  * <b>XML attributes</b>
250  * <p>
251  * See {@link android.R.styleable#TextView TextView Attributes},
252  * {@link android.R.styleable#View View Attributes}
253  *
254  * @attr ref android.R.styleable#TextView_text
255  * @attr ref android.R.styleable#TextView_bufferType
256  * @attr ref android.R.styleable#TextView_hint
257  * @attr ref android.R.styleable#TextView_textColor
258  * @attr ref android.R.styleable#TextView_textColorHighlight
259  * @attr ref android.R.styleable#TextView_textColorHint
260  * @attr ref android.R.styleable#TextView_textAppearance
261  * @attr ref android.R.styleable#TextView_textColorLink
262  * @attr ref android.R.styleable#TextView_textFontWeight
263  * @attr ref android.R.styleable#TextView_textSize
264  * @attr ref android.R.styleable#TextView_textScaleX
265  * @attr ref android.R.styleable#TextView_fontFamily
266  * @attr ref android.R.styleable#TextView_typeface
267  * @attr ref android.R.styleable#TextView_textStyle
268  * @attr ref android.R.styleable#TextView_cursorVisible
269  * @attr ref android.R.styleable#TextView_maxLines
270  * @attr ref android.R.styleable#TextView_maxHeight
271  * @attr ref android.R.styleable#TextView_lines
272  * @attr ref android.R.styleable#TextView_height
273  * @attr ref android.R.styleable#TextView_minLines
274  * @attr ref android.R.styleable#TextView_minHeight
275  * @attr ref android.R.styleable#TextView_maxEms
276  * @attr ref android.R.styleable#TextView_maxWidth
277  * @attr ref android.R.styleable#TextView_ems
278  * @attr ref android.R.styleable#TextView_width
279  * @attr ref android.R.styleable#TextView_minEms
280  * @attr ref android.R.styleable#TextView_minWidth
281  * @attr ref android.R.styleable#TextView_gravity
282  * @attr ref android.R.styleable#TextView_scrollHorizontally
283  * @attr ref android.R.styleable#TextView_password
284  * @attr ref android.R.styleable#TextView_singleLine
285  * @attr ref android.R.styleable#TextView_selectAllOnFocus
286  * @attr ref android.R.styleable#TextView_includeFontPadding
287  * @attr ref android.R.styleable#TextView_maxLength
288  * @attr ref android.R.styleable#TextView_shadowColor
289  * @attr ref android.R.styleable#TextView_shadowDx
290  * @attr ref android.R.styleable#TextView_shadowDy
291  * @attr ref android.R.styleable#TextView_shadowRadius
292  * @attr ref android.R.styleable#TextView_autoLink
293  * @attr ref android.R.styleable#TextView_linksClickable
294  * @attr ref android.R.styleable#TextView_numeric
295  * @attr ref android.R.styleable#TextView_digits
296  * @attr ref android.R.styleable#TextView_phoneNumber
297  * @attr ref android.R.styleable#TextView_inputMethod
298  * @attr ref android.R.styleable#TextView_capitalize
299  * @attr ref android.R.styleable#TextView_autoText
300  * @attr ref android.R.styleable#TextView_editable
301  * @attr ref android.R.styleable#TextView_freezesText
302  * @attr ref android.R.styleable#TextView_ellipsize
303  * @attr ref android.R.styleable#TextView_drawableTop
304  * @attr ref android.R.styleable#TextView_drawableBottom
305  * @attr ref android.R.styleable#TextView_drawableRight
306  * @attr ref android.R.styleable#TextView_drawableLeft
307  * @attr ref android.R.styleable#TextView_drawableStart
308  * @attr ref android.R.styleable#TextView_drawableEnd
309  * @attr ref android.R.styleable#TextView_drawablePadding
310  * @attr ref android.R.styleable#TextView_drawableTint
311  * @attr ref android.R.styleable#TextView_drawableTintMode
312  * @attr ref android.R.styleable#TextView_lineSpacingExtra
313  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
314  * @attr ref android.R.styleable#TextView_justificationMode
315  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
316  * @attr ref android.R.styleable#TextView_inputType
317  * @attr ref android.R.styleable#TextView_imeOptions
318  * @attr ref android.R.styleable#TextView_privateImeOptions
319  * @attr ref android.R.styleable#TextView_imeActionLabel
320  * @attr ref android.R.styleable#TextView_imeActionId
321  * @attr ref android.R.styleable#TextView_editorExtras
322  * @attr ref android.R.styleable#TextView_elegantTextHeight
323  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
324  * @attr ref android.R.styleable#TextView_letterSpacing
325  * @attr ref android.R.styleable#TextView_fontFeatureSettings
326  * @attr ref android.R.styleable#TextView_fontVariationSettings
327  * @attr ref android.R.styleable#TextView_breakStrategy
328  * @attr ref android.R.styleable#TextView_hyphenationFrequency
329  * @attr ref android.R.styleable#TextView_autoSizeTextType
330  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
331  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
332  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
333  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
334  * @attr ref android.R.styleable#TextView_textCursorDrawable
335  * @attr ref android.R.styleable#TextView_textSelectHandle
336  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
337  * @attr ref android.R.styleable#TextView_textSelectHandleRight
338  * @attr ref android.R.styleable#TextView_allowUndo
339  * @attr ref android.R.styleable#TextView_enabled
340  */
341 @RemoteView
342 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
343     static final String LOG_TAG = "TextView";
344     static final boolean DEBUG_EXTRACT = false;
345     private static final float[] TEMP_POSITION = new float[2];
346
347     // Enum for the "typeface" XML parameter.
348     // TODO: How can we get this from the XML instead of hardcoding it here?
349     /** @hide */
350     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
351     @Retention(RetentionPolicy.SOURCE)
352     public @interface XMLTypefaceAttr{}
353     private static final int DEFAULT_TYPEFACE = -1;
354     private static final int SANS = 1;
355     private static final int SERIF = 2;
356     private static final int MONOSPACE = 3;
357
358     // Enum for the "ellipsize" XML parameter.
359     private static final int ELLIPSIZE_NOT_SET = -1;
360     private static final int ELLIPSIZE_NONE = 0;
361     private static final int ELLIPSIZE_START = 1;
362     private static final int ELLIPSIZE_MIDDLE = 2;
363     private static final int ELLIPSIZE_END = 3;
364     private static final int ELLIPSIZE_MARQUEE = 4;
365
366     // Bitfield for the "numeric" XML parameter.
367     // TODO: How can we get this from the XML instead of hardcoding it here?
368     private static final int SIGNED = 2;
369     private static final int DECIMAL = 4;
370
371     /**
372      * Draw marquee text with fading edges as usual
373      */
374     private static final int MARQUEE_FADE_NORMAL = 0;
375
376     /**
377      * Draw marquee text as ellipsize end while inactive instead of with the fade.
378      * (Useful for devices where the fade can be expensive if overdone)
379      */
380     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
381
382     /**
383      * Draw marquee text with fading edges because it is currently active/animating.
384      */
385     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
386
387     @UnsupportedAppUsage
388     private static final int LINES = 1;
389     private static final int EMS = LINES;
390     private static final int PIXELS = 2;
391
392     private static final RectF TEMP_RECTF = new RectF();
393
394     /** @hide */
395     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
396     private static final int ANIMATED_SCROLL_GAP = 250;
397
398     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
399     private static final Spanned EMPTY_SPANNED = new SpannedString("");
400
401     private static final int CHANGE_WATCHER_PRIORITY = 100;
402
403     // New state used to change background based on whether this TextView is multiline.
404     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
405
406     // Accessibility action to share selected text.
407     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
408
409     /**
410      * @hide
411      */
412     // Accessibility action start id for "process text" actions.
413     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
414
415     /**
416      * @hide
417      */
418     static final int PROCESS_TEXT_REQUEST_CODE = 100;
419
420     /**
421      *  Return code of {@link #doKeyDown}.
422      */
423     private static final int KEY_EVENT_NOT_HANDLED = 0;
424     private static final int KEY_EVENT_HANDLED = -1;
425     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
426     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
427
428     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
429
430     // System wide time for last cut, copy or text changed action.
431     static long sLastCutCopyOrTextChangedTime;
432
433     private ColorStateList mTextColor;
434     private ColorStateList mHintTextColor;
435     private ColorStateList mLinkTextColor;
436     @ViewDebug.ExportedProperty(category = "text")
437
438     /**
439      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
440      */
441     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
442     private int mCurTextColor;
443
444     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
445     private int mCurHintTextColor;
446     private boolean mFreezesText;
447
448     @UnsupportedAppUsage
449     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
450     @UnsupportedAppUsage
451     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
452
453     @UnsupportedAppUsage
454     private float mShadowRadius;
455     @UnsupportedAppUsage
456     private float mShadowDx;
457     @UnsupportedAppUsage
458     private float mShadowDy;
459     private int mShadowColor;
460
461     private boolean mPreDrawRegistered;
462     private boolean mPreDrawListenerDetached;
463
464     private TextClassifier mTextClassifier;
465     private TextClassifier mTextClassificationSession;
466     private TextClassificationContext mTextClassificationContext;
467
468     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
469     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
470     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
471     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
472     // into by the user holding the movement key down) then we shouldn't prevent the focus from
473     // changing.
474     private boolean mPreventDefaultMovement;
475
476     private TextUtils.TruncateAt mEllipsize;
477
478     static class Drawables {
479         static final int LEFT = 0;
480         static final int TOP = 1;
481         static final int RIGHT = 2;
482         static final int BOTTOM = 3;
483
484         static final int DRAWABLE_NONE = -1;
485         static final int DRAWABLE_RIGHT = 0;
486         static final int DRAWABLE_LEFT = 1;
487
488         final Rect mCompoundRect = new Rect();
489
490         final Drawable[] mShowing = new Drawable[4];
491
492         ColorStateList mTintList;
493         BlendMode mBlendMode;
494         boolean mHasTint;
495         boolean mHasTintMode;
496
497         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
498         Drawable mDrawableLeftInitial, mDrawableRightInitial;
499
500         boolean mIsRtlCompatibilityMode;
501         boolean mOverride;
502
503         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
504                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
505
506         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
507                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
508
509         int mDrawablePadding;
510
511         int mDrawableSaved = DRAWABLE_NONE;
512
513         public Drawables(Context context) {
514             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
515             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
516                     || !context.getApplicationInfo().hasRtlSupport();
517             mOverride = false;
518         }
519
520         /**
521          * @return {@code true} if this object contains metadata that needs to
522          *         be retained, {@code false} otherwise
523          */
524         public boolean hasMetadata() {
525             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
526         }
527
528         /**
529          * Updates the list of displayed drawables to account for the current
530          * layout direction.
531          *
532          * @param layoutDirection the current layout direction
533          * @return {@code true} if the displayed drawables changed
534          */
535         public boolean resolveWithLayoutDirection(int layoutDirection) {
536             final Drawable previousLeft = mShowing[Drawables.LEFT];
537             final Drawable previousRight = mShowing[Drawables.RIGHT];
538
539             // First reset "left" and "right" drawables to their initial values
540             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
541             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
542
543             if (mIsRtlCompatibilityMode) {
544                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
545                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
546                     mShowing[Drawables.LEFT] = mDrawableStart;
547                     mDrawableSizeLeft = mDrawableSizeStart;
548                     mDrawableHeightLeft = mDrawableHeightStart;
549                 }
550                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
551                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
552                     mShowing[Drawables.RIGHT] = mDrawableEnd;
553                     mDrawableSizeRight = mDrawableSizeEnd;
554                     mDrawableHeightRight = mDrawableHeightEnd;
555                 }
556             } else {
557                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
558                 // drawable if and only if they have been defined
559                 switch(layoutDirection) {
560                     case LAYOUT_DIRECTION_RTL:
561                         if (mOverride) {
562                             mShowing[Drawables.RIGHT] = mDrawableStart;
563                             mDrawableSizeRight = mDrawableSizeStart;
564                             mDrawableHeightRight = mDrawableHeightStart;
565
566                             mShowing[Drawables.LEFT] = mDrawableEnd;
567                             mDrawableSizeLeft = mDrawableSizeEnd;
568                             mDrawableHeightLeft = mDrawableHeightEnd;
569                         }
570                         break;
571
572                     case LAYOUT_DIRECTION_LTR:
573                     default:
574                         if (mOverride) {
575                             mShowing[Drawables.LEFT] = mDrawableStart;
576                             mDrawableSizeLeft = mDrawableSizeStart;
577                             mDrawableHeightLeft = mDrawableHeightStart;
578
579                             mShowing[Drawables.RIGHT] = mDrawableEnd;
580                             mDrawableSizeRight = mDrawableSizeEnd;
581                             mDrawableHeightRight = mDrawableHeightEnd;
582                         }
583                         break;
584                 }
585             }
586
587             applyErrorDrawableIfNeeded(layoutDirection);
588
589             return mShowing[Drawables.LEFT] != previousLeft
590                     || mShowing[Drawables.RIGHT] != previousRight;
591         }
592
593         public void setErrorDrawable(Drawable dr, TextView tv) {
594             if (mDrawableError != dr && mDrawableError != null) {
595                 mDrawableError.setCallback(null);
596             }
597             mDrawableError = dr;
598
599             if (mDrawableError != null) {
600                 final Rect compoundRect = mCompoundRect;
601                 final int[] state = tv.getDrawableState();
602
603                 mDrawableError.setState(state);
604                 mDrawableError.copyBounds(compoundRect);
605                 mDrawableError.setCallback(tv);
606                 mDrawableSizeError = compoundRect.width();
607                 mDrawableHeightError = compoundRect.height();
608             } else {
609                 mDrawableSizeError = mDrawableHeightError = 0;
610             }
611         }
612
613         private void applyErrorDrawableIfNeeded(int layoutDirection) {
614             // first restore the initial state if needed
615             switch (mDrawableSaved) {
616                 case DRAWABLE_LEFT:
617                     mShowing[Drawables.LEFT] = mDrawableTemp;
618                     mDrawableSizeLeft = mDrawableSizeTemp;
619                     mDrawableHeightLeft = mDrawableHeightTemp;
620                     break;
621                 case DRAWABLE_RIGHT:
622                     mShowing[Drawables.RIGHT] = mDrawableTemp;
623                     mDrawableSizeRight = mDrawableSizeTemp;
624                     mDrawableHeightRight = mDrawableHeightTemp;
625                     break;
626                 case DRAWABLE_NONE:
627                 default:
628             }
629             // then, if needed, assign the Error drawable to the correct location
630             if (mDrawableError != null) {
631                 switch(layoutDirection) {
632                     case LAYOUT_DIRECTION_RTL:
633                         mDrawableSaved = DRAWABLE_LEFT;
634
635                         mDrawableTemp = mShowing[Drawables.LEFT];
636                         mDrawableSizeTemp = mDrawableSizeLeft;
637                         mDrawableHeightTemp = mDrawableHeightLeft;
638
639                         mShowing[Drawables.LEFT] = mDrawableError;
640                         mDrawableSizeLeft = mDrawableSizeError;
641                         mDrawableHeightLeft = mDrawableHeightError;
642                         break;
643                     case LAYOUT_DIRECTION_LTR:
644                     default:
645                         mDrawableSaved = DRAWABLE_RIGHT;
646
647                         mDrawableTemp = mShowing[Drawables.RIGHT];
648                         mDrawableSizeTemp = mDrawableSizeRight;
649                         mDrawableHeightTemp = mDrawableHeightRight;
650
651                         mShowing[Drawables.RIGHT] = mDrawableError;
652                         mDrawableSizeRight = mDrawableSizeError;
653                         mDrawableHeightRight = mDrawableHeightError;
654                         break;
655                 }
656             }
657         }
658     }
659
660     @UnsupportedAppUsage
661     Drawables mDrawables;
662
663     @UnsupportedAppUsage
664     private CharWrapper mCharWrapper;
665
666     @UnsupportedAppUsage(trackingBug = 124050217)
667     private Marquee mMarquee;
668     @UnsupportedAppUsage
669     private boolean mRestartMarquee;
670
671     private int mMarqueeRepeatLimit = 3;
672
673     private int mLastLayoutDirection = -1;
674
675     /**
676      * On some devices the fading edges add a performance penalty if used
677      * extensively in the same layout. This mode indicates how the marquee
678      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
679      */
680     @UnsupportedAppUsage
681     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
682
683     /**
684      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
685      * the layout that should be used when the mode switches.
686      */
687     @UnsupportedAppUsage
688     private Layout mSavedMarqueeModeLayout;
689
690     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
691     @ViewDebug.ExportedProperty(category = "text")
692     @UnsupportedAppUsage
693     private @Nullable CharSequence mText;
694     private @Nullable Spannable mSpannable;
695     private @Nullable PrecomputedText mPrecomputed;
696
697     @UnsupportedAppUsage
698     private CharSequence mTransformed;
699     @UnsupportedAppUsage
700     private BufferType mBufferType = BufferType.NORMAL;
701
702     private CharSequence mHint;
703     @UnsupportedAppUsage
704     private Layout mHintLayout;
705
706     private MovementMethod mMovement;
707
708     private TransformationMethod mTransformation;
709     @UnsupportedAppUsage
710     private boolean mAllowTransformationLengthChange;
711     @UnsupportedAppUsage
712     private ChangeWatcher mChangeWatcher;
713
714     @UnsupportedAppUsage(trackingBug = 123769451)
715     private ArrayList<TextWatcher> mListeners;
716
717     // display attributes
718     @UnsupportedAppUsage
719     private final TextPaint mTextPaint;
720     @UnsupportedAppUsage
721     private boolean mUserSetTextScaleX;
722     @UnsupportedAppUsage
723     private Layout mLayout;
724     private boolean mLocalesChanged = false;
725
726     // True if setKeyListener() has been explicitly called
727     private boolean mListenerChanged = false;
728     // True if internationalized input should be used for numbers and date and time.
729     private final boolean mUseInternationalizedInput;
730     // True if fallback fonts that end up getting used should be allowed to affect line spacing.
731     /* package */ boolean mUseFallbackLineSpacing;
732
733     @ViewDebug.ExportedProperty(category = "text")
734     @UnsupportedAppUsage
735     private int mGravity = Gravity.TOP | Gravity.START;
736     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
737     private boolean mHorizontallyScrolling;
738
739     private int mAutoLinkMask;
740     private boolean mLinksClickable = true;
741
742     @UnsupportedAppUsage
743     private float mSpacingMult = 1.0f;
744     @UnsupportedAppUsage
745     private float mSpacingAdd = 0.0f;
746
747     private int mBreakStrategy;
748     private int mHyphenationFrequency;
749     private int mJustificationMode;
750
751     @UnsupportedAppUsage
752     private int mMaximum = Integer.MAX_VALUE;
753     @UnsupportedAppUsage
754     private int mMaxMode = LINES;
755     @UnsupportedAppUsage
756     private int mMinimum = 0;
757     @UnsupportedAppUsage
758     private int mMinMode = LINES;
759
760     @UnsupportedAppUsage
761     private int mOldMaximum = mMaximum;
762     @UnsupportedAppUsage
763     private int mOldMaxMode = mMaxMode;
764
765     @UnsupportedAppUsage
766     private int mMaxWidth = Integer.MAX_VALUE;
767     @UnsupportedAppUsage
768     private int mMaxWidthMode = PIXELS;
769     @UnsupportedAppUsage
770     private int mMinWidth = 0;
771     @UnsupportedAppUsage
772     private int mMinWidthMode = PIXELS;
773
774     @UnsupportedAppUsage
775     private boolean mSingleLine;
776     @UnsupportedAppUsage
777     private int mDesiredHeightAtMeasure = -1;
778     @UnsupportedAppUsage
779     private boolean mIncludePad = true;
780     private int mDeferScroll = -1;
781
782     // tmp primitives, so we don't alloc them on each draw
783     private Rect mTempRect;
784     private long mLastScroll;
785     private Scroller mScroller;
786     private TextPaint mTempTextPaint;
787
788     @UnsupportedAppUsage
789     private BoringLayout.Metrics mBoring;
790     @UnsupportedAppUsage
791     private BoringLayout.Metrics mHintBoring;
792     @UnsupportedAppUsage
793     private BoringLayout mSavedLayout;
794     @UnsupportedAppUsage
795     private BoringLayout mSavedHintLayout;
796
797     @UnsupportedAppUsage
798     private TextDirectionHeuristic mTextDir;
799
800     private InputFilter[] mFilters = NO_FILTERS;
801
802     /**
803      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
804      * the same as {@link Process#myUserHandle()}.
805      *
806      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
807      * other apps may need to set this so that the system can use right user's resources and
808      * services such as input methods and spell checkers.</p>
809      *
810      * @see #setTextOperationUser(UserHandle)
811      */
812     @Nullable
813     private UserHandle mTextOperationUser;
814
815     private volatile Locale mCurrentSpellCheckerLocaleCache;
816
817     // It is possible to have a selection even when mEditor is null (programmatically set, like when
818     // a link is pressed). These highlight-related fields do not go in mEditor.
819     @UnsupportedAppUsage
820     int mHighlightColor = 0x6633B5E5;
821     private Path mHighlightPath;
822     @UnsupportedAppUsage
823     private final Paint mHighlightPaint;
824     @UnsupportedAppUsage
825     private boolean mHighlightPathBogus = true;
826
827     // Although these fields are specific to editable text, they are not added to Editor because
828     // they are defined by the TextView's style and are theme-dependent.
829     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
830     int mCursorDrawableRes;
831     private Drawable mCursorDrawable;
832     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
833     // by removing it, but we would break apps targeting <= P that use it by reflection.
834     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
835     int mTextSelectHandleLeftRes;
836     private Drawable mTextSelectHandleLeft;
837     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
838     // by removing it, but we would break apps targeting <= P that use it by reflection.
839     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
840     int mTextSelectHandleRightRes;
841     private Drawable mTextSelectHandleRight;
842     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
843     // by removing it, but we would break apps targeting <= P that use it by reflection.
844     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
845     int mTextSelectHandleRes;
846     private Drawable mTextSelectHandle;
847     int mTextEditSuggestionItemLayout;
848     int mTextEditSuggestionContainerLayout;
849     int mTextEditSuggestionHighlightStyle;
850
851     /**
852      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
853      * See {@link #createEditorIfNeeded()}.
854      */
855     @UnsupportedAppUsage
856     private Editor mEditor;
857
858     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
859     private static final int DEVICE_PROVISIONED_NO = 1;
860     private static final int DEVICE_PROVISIONED_YES = 2;
861
862     /**
863      * Some special options such as sharing selected text should only be shown if the device
864      * is provisioned. Only check the provisioned state once for a given view instance.
865      */
866     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
867
868     /**
869      * The TextView does not auto-size text (default).
870      */
871     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
872
873     /**
874      * The TextView scales text size both horizontally and vertically to fit within the
875      * container.
876      */
877     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
878
879     /** @hide */
880     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
881             AUTO_SIZE_TEXT_TYPE_NONE,
882             AUTO_SIZE_TEXT_TYPE_UNIFORM
883     })
884     @Retention(RetentionPolicy.SOURCE)
885     public @interface AutoSizeTextType {}
886     // Default minimum size for auto-sizing text in scaled pixels.
887     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
888     // Default maximum size for auto-sizing text in scaled pixels.
889     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
890     // Default value for the step size in pixels.
891     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
892     // Use this to specify that any of the auto-size configuration int values have not been set.
893     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
894     // Auto-size text type.
895     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
896     // Specify if auto-size text is needed.
897     private boolean mNeedsAutoSizeText = false;
898     // Step size for auto-sizing in pixels.
899     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
900     // Minimum text size for auto-sizing in pixels.
901     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
902     // Maximum text size for auto-sizing in pixels.
903     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
904     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
905     // when auto-sizing text.
906     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
907     // Specifies whether auto-size should use the provided auto size steps set or if it should
908     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
909     // mAutoSizeStepGranularityInPx.
910     private boolean mHasPresetAutoSizeValues = false;
911
912     // Autofill-related attributes
913     //
914     // Indicates whether the text was set statically or dynamically, so it can be used to
915     // sanitize autofill requests.
916     private boolean mTextSetFromXmlOrResourceId = false;
917     // Resource id used to set the text.
918     private @StringRes int mTextId = Resources.ID_NULL;
919     //
920     // End of autofill-related attributes
921
922     /**
923      * Kick-start the font cache for the zygote process (to pay the cost of
924      * initializing freetype for our default font only once).
925      * @hide
926      */
927     public static void preloadFontCache() {
928         Paint p = new Paint();
929         p.setAntiAlias(true);
930         // Ensure that the Typeface is loaded here.
931         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
932         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
933         // since Paint.measureText can not be called without Typeface static initializer.
934         p.setTypeface(Typeface.DEFAULT);
935         // We don't care about the result, just the side-effect of measuring.
936         p.measureText("H");
937     }
938
939     /**
940      * Interface definition for a callback to be invoked when an action is
941      * performed on the editor.
942      */
943     public interface OnEditorActionListener {
944         /**
945          * Called when an action is being performed.
946          *
947          * @param v The view that was clicked.
948          * @param actionId Identifier of the action.  This will be either the
949          * identifier you supplied, or {@link EditorInfo#IME_NULL
950          * EditorInfo.IME_NULL} if being called due to the enter key
951          * being pressed.
952          * @param event If triggered by an enter key, this is the event;
953          * otherwise, this is null.
954          * @return Return true if you have consumed the action, else false.
955          */
956         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
957     }
958
959     public TextView(Context context) {
960         this(context, null);
961     }
962
963     public TextView(Context context, @Nullable AttributeSet attrs) {
964         this(context, attrs, com.android.internal.R.attr.textViewStyle);
965     }
966
967     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
968         this(context, attrs, defStyleAttr, 0);
969     }
970
971     @SuppressWarnings("deprecation")
972     public TextView(
973             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
974         super(context, attrs, defStyleAttr, defStyleRes);
975
976         // TextView is important by default, unless app developer overrode attribute.
977         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
978             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
979         }
980
981         setTextInternal("");
982
983         final Resources res = getResources();
984         final CompatibilityInfo compat = res.getCompatibilityInfo();
985
986         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
987         mTextPaint.density = res.getDisplayMetrics().density;
988         mTextPaint.setCompatibilityScaling(compat.applicationScale);
989
990         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
991         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
992
993         mMovement = getDefaultMovementMethod();
994
995         mTransformation = null;
996
997         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
998         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
999         attributes.mTextSize = 15;
1000         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
1001         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
1002         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
1003
1004         final Resources.Theme theme = context.getTheme();
1005
1006         /*
1007          * Look the appearance up without checking first if it exists because
1008          * almost every TextView has one and it greatly simplifies the logic
1009          * to be able to parse the appearance first and then let specific tags
1010          * for this View override it.
1011          */
1012         TypedArray a = theme.obtainStyledAttributes(attrs,
1013                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
1014         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
1015                 attrs, a, defStyleAttr, defStyleRes);
1016         TypedArray appearance = null;
1017         int ap = a.getResourceId(
1018                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
1019         a.recycle();
1020         if (ap != -1) {
1021             appearance = theme.obtainStyledAttributes(
1022                     ap, com.android.internal.R.styleable.TextAppearance);
1023             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
1024                     null, appearance, 0, ap);
1025         }
1026         if (appearance != null) {
1027             readTextAppearance(context, appearance, attributes, false /* styleArray */);
1028             attributes.mFontFamilyExplicit = false;
1029             appearance.recycle();
1030         }
1031
1032         boolean editable = getDefaultEditable();
1033         CharSequence inputMethod = null;
1034         int numeric = 0;
1035         CharSequence digits = null;
1036         boolean phone = false;
1037         boolean autotext = false;
1038         int autocap = -1;
1039         int buffertype = 0;
1040         boolean selectallonfocus = false;
1041         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
1042                 drawableBottom = null, drawableStart = null, drawableEnd = null;
1043         ColorStateList drawableTint = null;
1044         BlendMode drawableTintMode = null;
1045         int drawablePadding = 0;
1046         int ellipsize = ELLIPSIZE_NOT_SET;
1047         boolean singleLine = false;
1048         int maxlength = -1;
1049         CharSequence text = "";
1050         CharSequence hint = null;
1051         boolean password = false;
1052         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1053         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1054         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1055         int inputType = EditorInfo.TYPE_NULL;
1056         a = theme.obtainStyledAttributes(
1057                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
1058         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
1059                 defStyleAttr, defStyleRes);
1060         int firstBaselineToTopHeight = -1;
1061         int lastBaselineToBottomHeight = -1;
1062         int lineHeight = -1;
1063
1064         readTextAppearance(context, a, attributes, true /* styleArray */);
1065
1066         int n = a.getIndexCount();
1067
1068         // Must set id in a temporary variable because it will be reset by setText()
1069         boolean textIsSetFromXml = false;
1070         for (int i = 0; i < n; i++) {
1071             int attr = a.getIndex(i);
1072
1073             switch (attr) {
1074                 case com.android.internal.R.styleable.TextView_editable:
1075                     editable = a.getBoolean(attr, editable);
1076                     break;
1077
1078                 case com.android.internal.R.styleable.TextView_inputMethod:
1079                     inputMethod = a.getText(attr);
1080                     break;
1081
1082                 case com.android.internal.R.styleable.TextView_numeric:
1083                     numeric = a.getInt(attr, numeric);
1084                     break;
1085
1086                 case com.android.internal.R.styleable.TextView_digits:
1087                     digits = a.getText(attr);
1088                     break;
1089
1090                 case com.android.internal.R.styleable.TextView_phoneNumber:
1091                     phone = a.getBoolean(attr, phone);
1092                     break;
1093
1094                 case com.android.internal.R.styleable.TextView_autoText:
1095                     autotext = a.getBoolean(attr, autotext);
1096                     break;
1097
1098                 case com.android.internal.R.styleable.TextView_capitalize:
1099                     autocap = a.getInt(attr, autocap);
1100                     break;
1101
1102                 case com.android.internal.R.styleable.TextView_bufferType:
1103                     buffertype = a.getInt(attr, buffertype);
1104                     break;
1105
1106                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1107                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1108                     break;
1109
1110                 case com.android.internal.R.styleable.TextView_autoLink:
1111                     mAutoLinkMask = a.getInt(attr, 0);
1112                     break;
1113
1114                 case com.android.internal.R.styleable.TextView_linksClickable:
1115                     mLinksClickable = a.getBoolean(attr, true);
1116                     break;
1117
1118                 case com.android.internal.R.styleable.TextView_drawableLeft:
1119                     drawableLeft = a.getDrawable(attr);
1120                     break;
1121
1122                 case com.android.internal.R.styleable.TextView_drawableTop:
1123                     drawableTop = a.getDrawable(attr);
1124                     break;
1125
1126                 case com.android.internal.R.styleable.TextView_drawableRight:
1127                     drawableRight = a.getDrawable(attr);
1128                     break;
1129
1130                 case com.android.internal.R.styleable.TextView_drawableBottom:
1131                     drawableBottom = a.getDrawable(attr);
1132                     break;
1133
1134                 case com.android.internal.R.styleable.TextView_drawableStart:
1135                     drawableStart = a.getDrawable(attr);
1136                     break;
1137
1138                 case com.android.internal.R.styleable.TextView_drawableEnd:
1139                     drawableEnd = a.getDrawable(attr);
1140                     break;
1141
1142                 case com.android.internal.R.styleable.TextView_drawableTint:
1143                     drawableTint = a.getColorStateList(attr);
1144                     break;
1145
1146                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1147                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
1148                             drawableTintMode);
1149                     break;
1150
1151                 case com.android.internal.R.styleable.TextView_drawablePadding:
1152                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1153                     break;
1154
1155                 case com.android.internal.R.styleable.TextView_maxLines:
1156                     setMaxLines(a.getInt(attr, -1));
1157                     break;
1158
1159                 case com.android.internal.R.styleable.TextView_maxHeight:
1160                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1161                     break;
1162
1163                 case com.android.internal.R.styleable.TextView_lines:
1164                     setLines(a.getInt(attr, -1));
1165                     break;
1166
1167                 case com.android.internal.R.styleable.TextView_height:
1168                     setHeight(a.getDimensionPixelSize(attr, -1));
1169                     break;
1170
1171                 case com.android.internal.R.styleable.TextView_minLines:
1172                     setMinLines(a.getInt(attr, -1));
1173                     break;
1174
1175                 case com.android.internal.R.styleable.TextView_minHeight:
1176                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1177                     break;
1178
1179                 case com.android.internal.R.styleable.TextView_maxEms:
1180                     setMaxEms(a.getInt(attr, -1));
1181                     break;
1182
1183                 case com.android.internal.R.styleable.TextView_maxWidth:
1184                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1185                     break;
1186
1187                 case com.android.internal.R.styleable.TextView_ems:
1188                     setEms(a.getInt(attr, -1));
1189                     break;
1190
1191                 case com.android.internal.R.styleable.TextView_width:
1192                     setWidth(a.getDimensionPixelSize(attr, -1));
1193                     break;
1194
1195                 case com.android.internal.R.styleable.TextView_minEms:
1196                     setMinEms(a.getInt(attr, -1));
1197                     break;
1198
1199                 case com.android.internal.R.styleable.TextView_minWidth:
1200                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1201                     break;
1202
1203                 case com.android.internal.R.styleable.TextView_gravity:
1204                     setGravity(a.getInt(attr, -1));
1205                     break;
1206
1207                 case com.android.internal.R.styleable.TextView_hint:
1208                     hint = a.getText(attr);
1209                     break;
1210
1211                 case com.android.internal.R.styleable.TextView_text:
1212                     textIsSetFromXml = true;
1213                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
1214                     text = a.getText(attr);
1215                     break;
1216
1217                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1218                     if (a.getBoolean(attr, false)) {
1219                         setHorizontallyScrolling(true);
1220                     }
1221                     break;
1222
1223                 case com.android.internal.R.styleable.TextView_singleLine:
1224                     singleLine = a.getBoolean(attr, singleLine);
1225                     break;
1226
1227                 case com.android.internal.R.styleable.TextView_ellipsize:
1228                     ellipsize = a.getInt(attr, ellipsize);
1229                     break;
1230
1231                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1232                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1233                     break;
1234
1235                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1236                     if (!a.getBoolean(attr, true)) {
1237                         setIncludeFontPadding(false);
1238                     }
1239                     break;
1240
1241                 case com.android.internal.R.styleable.TextView_cursorVisible:
1242                     if (!a.getBoolean(attr, true)) {
1243                         setCursorVisible(false);
1244                     }
1245                     break;
1246
1247                 case com.android.internal.R.styleable.TextView_maxLength:
1248                     maxlength = a.getInt(attr, -1);
1249                     break;
1250
1251                 case com.android.internal.R.styleable.TextView_textScaleX:
1252                     setTextScaleX(a.getFloat(attr, 1.0f));
1253                     break;
1254
1255                 case com.android.internal.R.styleable.TextView_freezesText:
1256                     mFreezesText = a.getBoolean(attr, false);
1257                     break;
1258
1259                 case com.android.internal.R.styleable.TextView_enabled:
1260                     setEnabled(a.getBoolean(attr, isEnabled()));
1261                     break;
1262
1263                 case com.android.internal.R.styleable.TextView_password:
1264                     password = a.getBoolean(attr, password);
1265                     break;
1266
1267                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1268                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1269                     break;
1270
1271                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1272                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1273                     break;
1274
1275                 case com.android.internal.R.styleable.TextView_inputType:
1276                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1277                     break;
1278
1279                 case com.android.internal.R.styleable.TextView_allowUndo:
1280                     createEditorIfNeeded();
1281                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1282                     break;
1283
1284                 case com.android.internal.R.styleable.TextView_imeOptions:
1285                     createEditorIfNeeded();
1286                     mEditor.createInputContentTypeIfNeeded();
1287                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1288                             mEditor.mInputContentType.imeOptions);
1289                     break;
1290
1291                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1292                     createEditorIfNeeded();
1293                     mEditor.createInputContentTypeIfNeeded();
1294                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1295                     break;
1296
1297                 case com.android.internal.R.styleable.TextView_imeActionId:
1298                     createEditorIfNeeded();
1299                     mEditor.createInputContentTypeIfNeeded();
1300                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1301                             mEditor.mInputContentType.imeActionId);
1302                     break;
1303
1304                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1305                     setPrivateImeOptions(a.getString(attr));
1306                     break;
1307
1308                 case com.android.internal.R.styleable.TextView_editorExtras:
1309                     try {
1310                         setInputExtras(a.getResourceId(attr, 0));
1311                     } catch (XmlPullParserException e) {
1312                         Log.w(LOG_TAG, "Failure reading input extras", e);
1313                     } catch (IOException e) {
1314                         Log.w(LOG_TAG, "Failure reading input extras", e);
1315                     }
1316                     break;
1317
1318                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1319                     mCursorDrawableRes = a.getResourceId(attr, 0);
1320                     break;
1321
1322                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1323                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1324                     break;
1325
1326                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1327                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1328                     break;
1329
1330                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1331                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1332                     break;
1333
1334                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1335                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1336                     break;
1337
1338                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1339                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1340                     break;
1341
1342                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1343                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1344                     break;
1345
1346                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1347                     setTextIsSelectable(a.getBoolean(attr, false));
1348                     break;
1349
1350                 case com.android.internal.R.styleable.TextView_breakStrategy:
1351                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1352                     break;
1353
1354                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1355                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1356                     break;
1357
1358                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1359                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1360                     break;
1361
1362                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1363                     autoSizeStepGranularityInPx = a.getDimension(attr,
1364                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1365                     break;
1366
1367                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1368                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1369                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1370                     break;
1371
1372                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1373                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1374                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1375                     break;
1376
1377                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1378                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1379                     if (autoSizeStepSizeArrayResId > 0) {
1380                         final TypedArray autoSizePresetTextSizes = a.getResources()
1381                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1382                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1383                         autoSizePresetTextSizes.recycle();
1384                     }
1385                     break;
1386                 case com.android.internal.R.styleable.TextView_justificationMode:
1387                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1388                     break;
1389
1390                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
1391                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
1392                     break;
1393
1394                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
1395                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
1396                     break;
1397
1398                 case com.android.internal.R.styleable.TextView_lineHeight:
1399                     lineHeight = a.getDimensionPixelSize(attr, -1);
1400                     break;
1401             }
1402         }
1403
1404         a.recycle();
1405
1406         BufferType bufferType = BufferType.EDITABLE;
1407
1408         final int variation =
1409                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1410         final boolean passwordInputType = variation
1411                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1412         final boolean webPasswordInputType = variation
1413                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1414         final boolean numberPasswordInputType = variation
1415                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1416
1417         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
1418         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
1419         mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
1420
1421         if (inputMethod != null) {
1422             Class<?> c;
1423
1424             try {
1425                 c = Class.forName(inputMethod.toString());
1426             } catch (ClassNotFoundException ex) {
1427                 throw new RuntimeException(ex);
1428             }
1429
1430             try {
1431                 createEditorIfNeeded();
1432                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1433             } catch (InstantiationException ex) {
1434                 throw new RuntimeException(ex);
1435             } catch (IllegalAccessException ex) {
1436                 throw new RuntimeException(ex);
1437             }
1438             try {
1439                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1440                         ? inputType
1441                         : mEditor.mKeyListener.getInputType();
1442             } catch (IncompatibleClassChangeError e) {
1443                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1444             }
1445         } else if (digits != null) {
1446             createEditorIfNeeded();
1447             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1448             // If no input type was specified, we will default to generic
1449             // text, since we can't tell the IME about the set of digits
1450             // that was selected.
1451             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1452                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1453         } else if (inputType != EditorInfo.TYPE_NULL) {
1454             setInputType(inputType, true);
1455             // If set, the input type overrides what was set using the deprecated singleLine flag.
1456             singleLine = !isMultilineInputType(inputType);
1457         } else if (phone) {
1458             createEditorIfNeeded();
1459             mEditor.mKeyListener = DialerKeyListener.getInstance();
1460             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1461         } else if (numeric != 0) {
1462             createEditorIfNeeded();
1463             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1464                     null,  // locale
1465                     (numeric & SIGNED) != 0,
1466                     (numeric & DECIMAL) != 0);
1467             inputType = mEditor.mKeyListener.getInputType();
1468             mEditor.mInputType = inputType;
1469         } else if (autotext || autocap != -1) {
1470             TextKeyListener.Capitalize cap;
1471
1472             inputType = EditorInfo.TYPE_CLASS_TEXT;
1473
1474             switch (autocap) {
1475                 case 1:
1476                     cap = TextKeyListener.Capitalize.SENTENCES;
1477                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1478                     break;
1479
1480                 case 2:
1481                     cap = TextKeyListener.Capitalize.WORDS;
1482                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1483                     break;
1484
1485                 case 3:
1486                     cap = TextKeyListener.Capitalize.CHARACTERS;
1487                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1488                     break;
1489
1490                 default:
1491                     cap = TextKeyListener.Capitalize.NONE;
1492                     break;
1493             }
1494
1495             createEditorIfNeeded();
1496             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1497             mEditor.mInputType = inputType;
1498         } else if (editable) {
1499             createEditorIfNeeded();
1500             mEditor.mKeyListener = TextKeyListener.getInstance();
1501             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1502         } else if (isTextSelectable()) {
1503             // Prevent text changes from keyboard.
1504             if (mEditor != null) {
1505                 mEditor.mKeyListener = null;
1506                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1507             }
1508             bufferType = BufferType.SPANNABLE;
1509             // So that selection can be changed using arrow keys and touch is handled.
1510             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1511         } else {
1512             if (mEditor != null) mEditor.mKeyListener = null;
1513
1514             switch (buffertype) {
1515                 case 0:
1516                     bufferType = BufferType.NORMAL;
1517                     break;
1518                 case 1:
1519                     bufferType = BufferType.SPANNABLE;
1520                     break;
1521                 case 2:
1522                     bufferType = BufferType.EDITABLE;
1523                     break;
1524             }
1525         }
1526
1527         if (mEditor != null) {
1528             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1529                     numberPasswordInputType);
1530         }
1531
1532         if (selectallonfocus) {
1533             createEditorIfNeeded();
1534             mEditor.mSelectAllOnFocus = true;
1535
1536             if (bufferType == BufferType.NORMAL) {
1537                 bufferType = BufferType.SPANNABLE;
1538             }
1539         }
1540
1541         // Set up the tint (if needed) before setting the drawables so that it
1542         // gets applied correctly.
1543         if (drawableTint != null || drawableTintMode != null) {
1544             if (mDrawables == null) {
1545                 mDrawables = new Drawables(context);
1546             }
1547             if (drawableTint != null) {
1548                 mDrawables.mTintList = drawableTint;
1549                 mDrawables.mHasTint = true;
1550             }
1551             if (drawableTintMode != null) {
1552                 mDrawables.mBlendMode = drawableTintMode;
1553                 mDrawables.mHasTintMode = true;
1554             }
1555         }
1556
1557         // This call will save the initial left/right drawables
1558         setCompoundDrawablesWithIntrinsicBounds(
1559                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1560         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1561         setCompoundDrawablePadding(drawablePadding);
1562
1563         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1564         // of lines of height are unchanged for multi-line TextViews.
1565         setInputTypeSingleLine(singleLine);
1566         applySingleLine(singleLine, singleLine, singleLine);
1567
1568         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
1569             ellipsize = ELLIPSIZE_END;
1570         }
1571
1572         switch (ellipsize) {
1573             case ELLIPSIZE_START:
1574                 setEllipsize(TextUtils.TruncateAt.START);
1575                 break;
1576             case ELLIPSIZE_MIDDLE:
1577                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1578                 break;
1579             case ELLIPSIZE_END:
1580                 setEllipsize(TextUtils.TruncateAt.END);
1581                 break;
1582             case ELLIPSIZE_MARQUEE:
1583                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1584                     setHorizontalFadingEdgeEnabled(true);
1585                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1586                 } else {
1587                     setHorizontalFadingEdgeEnabled(false);
1588                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1589                 }
1590                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1591                 break;
1592         }
1593
1594         final boolean isPassword = password || passwordInputType || webPasswordInputType
1595                 || numberPasswordInputType;
1596         final boolean isMonospaceEnforced = isPassword || (mEditor != null
1597                 && (mEditor.mInputType
1598                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1599                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
1600         if (isMonospaceEnforced) {
1601             attributes.mTypefaceIndex = MONOSPACE;
1602         }
1603
1604         applyTextAppearance(attributes);
1605
1606         if (isPassword) {
1607             setTransformationMethod(PasswordTransformationMethod.getInstance());
1608         }
1609
1610         if (maxlength >= 0) {
1611             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1612         } else {
1613             setFilters(NO_FILTERS);
1614         }
1615
1616         setText(text, bufferType);
1617         if (mText == null) {
1618             mText = "";
1619         }
1620         if (mTransformed == null) {
1621             mTransformed = "";
1622         }
1623
1624         if (textIsSetFromXml) {
1625             mTextSetFromXmlOrResourceId = true;
1626         }
1627
1628         if (hint != null) setHint(hint);
1629
1630         /*
1631          * Views are not normally clickable unless specified to be.
1632          * However, TextViews that have input or movement methods *are*
1633          * clickable by default. By setting clickable here, we implicitly set focusable as well
1634          * if not overridden by the developer.
1635          */
1636         a = context.obtainStyledAttributes(
1637                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1638         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1639         boolean clickable = canInputOrMove || isClickable();
1640         boolean longClickable = canInputOrMove || isLongClickable();
1641         int focusable = getFocusable();
1642
1643         n = a.getIndexCount();
1644         for (int i = 0; i < n; i++) {
1645             int attr = a.getIndex(i);
1646
1647             switch (attr) {
1648                 case com.android.internal.R.styleable.View_focusable:
1649                     TypedValue val = new TypedValue();
1650                     if (a.getValue(attr, val)) {
1651                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1652                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1653                                 : val.data;
1654                     }
1655                     break;
1656
1657                 case com.android.internal.R.styleable.View_clickable:
1658                     clickable = a.getBoolean(attr, clickable);
1659                     break;
1660
1661                 case com.android.internal.R.styleable.View_longClickable:
1662                     longClickable = a.getBoolean(attr, longClickable);
1663                     break;
1664             }
1665         }
1666         a.recycle();
1667
1668         // Some apps were relying on the undefined behavior of focusable winning over
1669         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1670         // when starting with EditText and setting only focusable=false). To keep those apps from
1671         // breaking, re-apply the focusable attribute here.
1672         if (focusable != getFocusable()) {
1673             setFocusable(focusable);
1674         }
1675         setClickable(clickable);
1676         setLongClickable(longClickable);
1677
1678         if (mEditor != null) mEditor.prepareCursorControllers();
1679
1680         // If not explicitly specified this view is important for accessibility.
1681         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1682             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1683         }
1684
1685         if (supportsAutoSizeText()) {
1686             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1687                 // If uniform auto-size has been specified but preset values have not been set then
1688                 // replace the auto-size configuration values that have not been specified with the
1689                 // defaults.
1690                 if (!mHasPresetAutoSizeValues) {
1691                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1692
1693                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1694                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1695                                 TypedValue.COMPLEX_UNIT_SP,
1696                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1697                                 displayMetrics);
1698                     }
1699
1700                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1701                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1702                                 TypedValue.COMPLEX_UNIT_SP,
1703                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1704                                 displayMetrics);
1705                     }
1706
1707                     if (autoSizeStepGranularityInPx
1708                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1709                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1710                     }
1711
1712                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1713                             autoSizeMaxTextSizeInPx,
1714                             autoSizeStepGranularityInPx);
1715                 }
1716
1717                 setupAutoSizeText();
1718             }
1719         } else {
1720             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1721         }
1722
1723         if (firstBaselineToTopHeight >= 0) {
1724             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
1725         }
1726         if (lastBaselineToBottomHeight >= 0) {
1727             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
1728         }
1729         if (lineHeight >= 0) {
1730             setLineHeight(lineHeight);
1731         }
1732     }
1733
1734     // Update mText and mPrecomputed
1735     private void setTextInternal(@Nullable CharSequence text) {
1736         mText = text;
1737         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
1738         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
1739     }
1740
1741     /**
1742      * Specify whether this widget should automatically scale the text to try to perfectly fit
1743      * within the layout bounds by using the default auto-size configuration.
1744      *
1745      * @param autoSizeTextType the type of auto-size. Must be one of
1746      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1747      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1748      *
1749      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1750      *
1751      * @attr ref android.R.styleable#TextView_autoSizeTextType
1752      *
1753      * @see #getAutoSizeTextType()
1754      */
1755     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1756         if (supportsAutoSizeText()) {
1757             switch (autoSizeTextType) {
1758                 case AUTO_SIZE_TEXT_TYPE_NONE:
1759                     clearAutoSizeConfiguration();
1760                     break;
1761                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1762                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1763                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1764                             TypedValue.COMPLEX_UNIT_SP,
1765                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1766                             displayMetrics);
1767                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1768                             TypedValue.COMPLEX_UNIT_SP,
1769                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1770                             displayMetrics);
1771
1772                     validateAndSetAutoSizeTextTypeUniformConfiguration(
1773                             autoSizeMinTextSizeInPx,
1774                             autoSizeMaxTextSizeInPx,
1775                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1776                     if (setupAutoSizeText()) {
1777                         autoSizeText();
1778                         invalidate();
1779                     }
1780                     break;
1781                 default:
1782                     throw new IllegalArgumentException(
1783                             "Unknown auto-size text type: " + autoSizeTextType);
1784             }
1785         }
1786     }
1787
1788     /**
1789      * Specify whether this widget should automatically scale the text to try to perfectly fit
1790      * within the layout bounds. If all the configuration params are valid the type of auto-size is
1791      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1792      *
1793      * @param autoSizeMinTextSize the minimum text size available for auto-size
1794      * @param autoSizeMaxTextSize the maximum text size available for auto-size
1795      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1796      *                                the minimum and maximum text size in order to build the set of
1797      *                                text sizes the system uses to choose from when auto-sizing
1798      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1799      *             possible dimension units
1800      *
1801      * @throws IllegalArgumentException if any of the configuration params are invalid.
1802      *
1803      * @attr ref android.R.styleable#TextView_autoSizeTextType
1804      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1805      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1806      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1807      *
1808      * @see #setAutoSizeTextTypeWithDefaults(int)
1809      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1810      * @see #getAutoSizeMinTextSize()
1811      * @see #getAutoSizeMaxTextSize()
1812      * @see #getAutoSizeStepGranularity()
1813      * @see #getAutoSizeTextAvailableSizes()
1814      */
1815     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1816             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1817         if (supportsAutoSizeText()) {
1818             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1819             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1820                     unit, autoSizeMinTextSize, displayMetrics);
1821             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1822                     unit, autoSizeMaxTextSize, displayMetrics);
1823             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1824                     unit, autoSizeStepGranularity, displayMetrics);
1825
1826             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1827                     autoSizeMaxTextSizeInPx,
1828                     autoSizeStepGranularityInPx);
1829
1830             if (setupAutoSizeText()) {
1831                 autoSizeText();
1832                 invalidate();
1833             }
1834         }
1835     }
1836
1837     /**
1838      * Specify whether this widget should automatically scale the text to try to perfectly fit
1839      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1840      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1841      *
1842      * @param presetSizes an {@code int} array of sizes in pixels
1843      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1844      *             the possible dimension units
1845      *
1846      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1847      *
1848      * @attr ref android.R.styleable#TextView_autoSizeTextType
1849      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1850      *
1851      * @see #setAutoSizeTextTypeWithDefaults(int)
1852      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1853      * @see #getAutoSizeMinTextSize()
1854      * @see #getAutoSizeMaxTextSize()
1855      * @see #getAutoSizeTextAvailableSizes()
1856      */
1857     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1858         if (supportsAutoSizeText()) {
1859             final int presetSizesLength = presetSizes.length;
1860             if (presetSizesLength > 0) {
1861                 int[] presetSizesInPx = new int[presetSizesLength];
1862
1863                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1864                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1865                 } else {
1866                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1867                     // Convert all to sizes to pixels.
1868                     for (int i = 0; i < presetSizesLength; i++) {
1869                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
1870                             presetSizes[i], displayMetrics));
1871                     }
1872                 }
1873
1874                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1875                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
1876                     throw new IllegalArgumentException("None of the preset sizes is valid: "
1877                             + Arrays.toString(presetSizes));
1878                 }
1879             } else {
1880                 mHasPresetAutoSizeValues = false;
1881             }
1882
1883             if (setupAutoSizeText()) {
1884                 autoSizeText();
1885                 invalidate();
1886             }
1887         }
1888     }
1889
1890     /**
1891      * Returns the type of auto-size set for this widget.
1892      *
1893      * @return an {@code int} corresponding to one of the auto-size types:
1894      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1895      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1896      *
1897      * @attr ref android.R.styleable#TextView_autoSizeTextType
1898      *
1899      * @see #setAutoSizeTextTypeWithDefaults(int)
1900      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1901      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1902      */
1903     @InspectableProperty(enumMapping = {
1904             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
1905             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
1906     })
1907     @AutoSizeTextType
1908     public int getAutoSizeTextType() {
1909         return mAutoSizeTextType;
1910     }
1911
1912     /**
1913      * @return the current auto-size step granularity in pixels.
1914      *
1915      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1916      *
1917      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1918      */
1919     @InspectableProperty
1920     public int getAutoSizeStepGranularity() {
1921         return Math.round(mAutoSizeStepGranularityInPx);
1922     }
1923
1924     /**
1925      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
1926      *         if auto-size has not been configured this function returns {@code -1}.
1927      *
1928      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1929      *
1930      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1931      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1932      */
1933     @InspectableProperty
1934     public int getAutoSizeMinTextSize() {
1935         return Math.round(mAutoSizeMinTextSizeInPx);
1936     }
1937
1938     /**
1939      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
1940      *         if auto-size has not been configured this function returns {@code -1}.
1941      *
1942      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1943      *
1944      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1945      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1946      */
1947     @InspectableProperty
1948     public int getAutoSizeMaxTextSize() {
1949         return Math.round(mAutoSizeMaxTextSizeInPx);
1950     }
1951
1952     /**
1953      * @return the current auto-size {@code int} sizes array (in pixels).
1954      *
1955      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1956      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1957      */
1958     public int[] getAutoSizeTextAvailableSizes() {
1959         return mAutoSizeTextSizesInPx;
1960     }
1961
1962     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
1963         final int textSizesLength = textSizes.length();
1964         final int[] parsedSizes = new int[textSizesLength];
1965
1966         if (textSizesLength > 0) {
1967             for (int i = 0; i < textSizesLength; i++) {
1968                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
1969             }
1970             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
1971             setupAutoSizeUniformPresetSizesConfiguration();
1972         }
1973     }
1974
1975     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
1976         final int sizesLength = mAutoSizeTextSizesInPx.length;
1977         mHasPresetAutoSizeValues = sizesLength > 0;
1978         if (mHasPresetAutoSizeValues) {
1979             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1980             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
1981             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
1982             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1983         }
1984         return mHasPresetAutoSizeValues;
1985     }
1986
1987     /**
1988      * If all params are valid then save the auto-size configuration.
1989      *
1990      * @throws IllegalArgumentException if any of the params are invalid
1991      */
1992     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
1993             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
1994         // First validate.
1995         if (autoSizeMinTextSizeInPx <= 0) {
1996             throw new IllegalArgumentException("Minimum auto-size text size ("
1997                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
1998         }
1999
2000         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
2001             throw new IllegalArgumentException("Maximum auto-size text size ("
2002                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
2003                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
2004         }
2005
2006         if (autoSizeStepGranularityInPx <= 0) {
2007             throw new IllegalArgumentException("The auto-size step granularity ("
2008                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
2009         }
2010
2011         // All good, persist the configuration.
2012         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
2013         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
2014         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
2015         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
2016         mHasPresetAutoSizeValues = false;
2017     }
2018
2019     private void clearAutoSizeConfiguration() {
2020         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2021         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2022         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2023         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2024         mAutoSizeTextSizesInPx = EmptyArray.INT;
2025         mNeedsAutoSizeText = false;
2026     }
2027
2028     // Returns distinct sorted positive values.
2029     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2030         final int presetValuesLength = presetValues.length;
2031         if (presetValuesLength == 0) {
2032             return presetValues;
2033         }
2034         Arrays.sort(presetValues);
2035
2036         final IntArray uniqueValidSizes = new IntArray();
2037         for (int i = 0; i < presetValuesLength; i++) {
2038             final int currentPresetValue = presetValues[i];
2039
2040             if (currentPresetValue > 0
2041                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2042                 uniqueValidSizes.add(currentPresetValue);
2043             }
2044         }
2045
2046         return presetValuesLength == uniqueValidSizes.size()
2047             ? presetValues
2048             : uniqueValidSizes.toArray();
2049     }
2050
2051     private boolean setupAutoSizeText() {
2052         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2053             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2054             // not have a predefined set of sizes or if the current sizes array is empty.
2055             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2056                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
2057                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
2058                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2059                 for (int i = 0; i < autoSizeValuesLength; i++) {
2060                     autoSizeTextSizesInPx[i] = Math.round(
2061                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
2062                 }
2063                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2064             }
2065
2066             mNeedsAutoSizeText = true;
2067         } else {
2068             mNeedsAutoSizeText = false;
2069         }
2070
2071         return mNeedsAutoSizeText;
2072     }
2073
2074     private int[] parseDimensionArray(TypedArray dimens) {
2075         if (dimens == null) {
2076             return null;
2077         }
2078         int[] result = new int[dimens.length()];
2079         for (int i = 0; i < result.length; i++) {
2080             result[i] = dimens.getDimensionPixelSize(i, 0);
2081         }
2082         return result;
2083     }
2084
2085     /**
2086      * @hide
2087      */
2088     @Override
2089     public void onActivityResult(int requestCode, int resultCode, Intent data) {
2090         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2091             if (resultCode == Activity.RESULT_OK && data != null) {
2092                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2093                 if (result != null) {
2094                     if (isTextEditable()) {
2095                         replaceSelectionWithText(result);
2096                         if (mEditor != null) {
2097                             mEditor.refreshTextActionMode();
2098                         }
2099                     } else {
2100                         if (result.length() > 0) {
2101                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2102                                 .show();
2103                         }
2104                     }
2105                 }
2106             } else if (mSpannable != null) {
2107                 // Reset the selection.
2108                 Selection.setSelection(mSpannable, getSelectionEnd());
2109             }
2110         }
2111     }
2112
2113     /**
2114      * Sets the Typeface taking into account the given attributes.
2115      *
2116      * @param typeface a typeface
2117      * @param familyName family name string, e.g. "serif"
2118      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
2119      * @param style a typeface style
2120      * @param weight a weight value for the Typeface or -1 if not specified.
2121      */
2122     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
2123             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
2124             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2125         if (typeface == null && familyName != null) {
2126             // Lookup normal Typeface from system font map.
2127             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
2128             resolveStyleAndSetTypeface(normalTypeface, style, weight);
2129         } else if (typeface != null) {
2130             resolveStyleAndSetTypeface(typeface, style, weight);
2131         } else {  // both typeface and familyName is null.
2132             switch (typefaceIndex) {
2133                 case SANS:
2134                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
2135                     break;
2136                 case SERIF:
2137                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
2138                     break;
2139                 case MONOSPACE:
2140                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
2141                     break;
2142                 case DEFAULT_TYPEFACE:
2143                 default:
2144                     resolveStyleAndSetTypeface(null, style, weight);
2145                     break;
2146             }
2147         }
2148     }
2149
2150     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
2151             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
2152         if (weight >= 0) {
2153             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
2154             final boolean italic = (style & Typeface.ITALIC) != 0;
2155             setTypeface(Typeface.create(typeface, weight, italic));
2156         } else {
2157             setTypeface(typeface, style);
2158         }
2159     }
2160
2161     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2162         boolean hasRelativeDrawables = (start != null) || (end != null);
2163         if (hasRelativeDrawables) {
2164             Drawables dr = mDrawables;
2165             if (dr == null) {
2166                 mDrawables = dr = new Drawables(getContext());
2167             }
2168             mDrawables.mOverride = true;
2169             final Rect compoundRect = dr.mCompoundRect;
2170             int[] state = getDrawableState();
2171             if (start != null) {
2172                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2173                 start.setState(state);
2174                 start.copyBounds(compoundRect);
2175                 start.setCallback(this);
2176
2177                 dr.mDrawableStart = start;
2178                 dr.mDrawableSizeStart = compoundRect.width();
2179                 dr.mDrawableHeightStart = compoundRect.height();
2180             } else {
2181                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2182             }
2183             if (end != null) {
2184                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2185                 end.setState(state);
2186                 end.copyBounds(compoundRect);
2187                 end.setCallback(this);
2188
2189                 dr.mDrawableEnd = end;
2190                 dr.mDrawableSizeEnd = compoundRect.width();
2191                 dr.mDrawableHeightEnd = compoundRect.height();
2192             } else {
2193                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2194             }
2195             resetResolvedDrawables();
2196             resolveDrawables();
2197             applyCompoundDrawableTint();
2198         }
2199     }
2200
2201     @android.view.RemotableViewMethod
2202     @Override
2203     public void setEnabled(boolean enabled) {
2204         if (enabled == isEnabled()) {
2205             return;
2206         }
2207
2208         if (!enabled) {
2209             // Hide the soft input if the currently active TextView is disabled
2210             InputMethodManager imm = getInputMethodManager();
2211             if (imm != null && imm.isActive(this)) {
2212                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2213             }
2214         }
2215
2216         super.setEnabled(enabled);
2217
2218         if (enabled) {
2219             // Make sure IME is updated with current editor info.
2220             InputMethodManager imm = getInputMethodManager();
2221             if (imm != null) imm.restartInput(this);
2222         }
2223
2224         // Will change text color
2225         if (mEditor != null) {
2226             mEditor.invalidateTextDisplayList();
2227             mEditor.prepareCursorControllers();
2228
2229             // start or stop the cursor blinking as appropriate
2230             mEditor.makeBlink();
2231         }
2232     }
2233
2234     /**
2235      * Sets the typeface and style in which the text should be displayed,
2236      * and turns on the fake bold and italic bits in the Paint if the
2237      * Typeface that you provided does not have all the bits in the
2238      * style that you specified.
2239      *
2240      * @attr ref android.R.styleable#TextView_typeface
2241      * @attr ref android.R.styleable#TextView_textStyle
2242      */
2243     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
2244         if (style > 0) {
2245             if (tf == null) {
2246                 tf = Typeface.defaultFromStyle(style);
2247             } else {
2248                 tf = Typeface.create(tf, style);
2249             }
2250
2251             setTypeface(tf);
2252             // now compute what (if any) algorithmic styling is needed
2253             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2254             int need = style & ~typefaceStyle;
2255             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2256             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2257         } else {
2258             mTextPaint.setFakeBoldText(false);
2259             mTextPaint.setTextSkewX(0);
2260             setTypeface(tf);
2261         }
2262     }
2263
2264     /**
2265      * Subclasses override this to specify that they have a KeyListener
2266      * by default even if not specifically called for in the XML options.
2267      */
2268     protected boolean getDefaultEditable() {
2269         return false;
2270     }
2271
2272     /**
2273      * Subclasses override this to specify a default movement method.
2274      */
2275     protected MovementMethod getDefaultMovementMethod() {
2276         return null;
2277     }
2278
2279     /**
2280      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2281      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2282      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2283      * the return value from this method to Spannable or Editable, respectively.
2284      *
2285      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2286      * should make your own copy first.</p>
2287      *
2288      * @return The text displayed by the text view.
2289      * @attr ref android.R.styleable#TextView_text
2290      */
2291     @ViewDebug.CapturedViewProperty
2292     @InspectableProperty
2293     public CharSequence getText() {
2294         return mText;
2295     }
2296
2297     /**
2298      * Returns the length, in characters, of the text managed by this TextView
2299      * @return The length of the text managed by the TextView in characters.
2300      */
2301     public int length() {
2302         return mText.length();
2303     }
2304
2305     /**
2306      * Return the text that TextView is displaying as an Editable object. If the text is not
2307      * editable, null is returned.
2308      *
2309      * @see #getText
2310      */
2311     public Editable getEditableText() {
2312         return (mText instanceof Editable) ? (Editable) mText : null;
2313     }
2314
2315     /**
2316      * @hide
2317      */
2318     @VisibleForTesting
2319     public CharSequence getTransformed() {
2320         return mTransformed;
2321     }
2322
2323     /**
2324      * Gets the vertical distance between lines of text, in pixels.
2325      * Note that markup within the text can cause individual lines
2326      * to be taller or shorter than this height, and the layout may
2327      * contain additional first-or last-line padding.
2328      * @return The height of one standard line in pixels.
2329      */
2330     @InspectableProperty
2331     public int getLineHeight() {
2332         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2333     }
2334
2335     /**
2336      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2337      * This value can be null if the text or width has recently changed.
2338      * @return The Layout that is currently being used to display the text.
2339      */
2340     public final Layout getLayout() {
2341         return mLayout;
2342     }
2343
2344     /**
2345      * @return the {@link android.text.Layout} that is currently being used to
2346      * display the hint text. This can be null.
2347      */
2348     @UnsupportedAppUsage
2349     final Layout getHintLayout() {
2350         return mHintLayout;
2351     }
2352
2353     /**
2354      * Retrieve the {@link android.content.UndoManager} that is currently associated
2355      * with this TextView.  By default there is no associated UndoManager, so null
2356      * is returned.  One can be associated with the TextView through
2357      * {@link #setUndoManager(android.content.UndoManager, String)}
2358      *
2359      * @hide
2360      */
2361     public final UndoManager getUndoManager() {
2362         // TODO: Consider supporting a global undo manager.
2363         throw new UnsupportedOperationException("not implemented");
2364     }
2365
2366
2367     /**
2368      * @hide
2369      */
2370     @VisibleForTesting
2371     public final Editor getEditorForTesting() {
2372         return mEditor;
2373     }
2374
2375     /**
2376      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2377      * done, all edit operations on the TextView will result in appropriate
2378      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2379      * stack.
2380      *
2381      * @param undoManager The {@link android.content.UndoManager} to associate with
2382      * this TextView, or null to clear any existing association.
2383      * @param tag String tag identifying this particular TextView owner in the
2384      * UndoManager.  This is used to keep the correct association with the
2385      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2386      *
2387      * @hide
2388      */
2389     public final void setUndoManager(UndoManager undoManager, String tag) {
2390         // TODO: Consider supporting a global undo manager. An implementation will need to:
2391         // * createEditorIfNeeded()
2392         // * Promote to BufferType.EDITABLE if needed.
2393         // * Update the UndoManager and UndoOwner.
2394         // Likewise it will need to be able to restore the default UndoManager.
2395         throw new UnsupportedOperationException("not implemented");
2396     }
2397
2398     /**
2399      * Gets the current {@link KeyListener} for the TextView.
2400      * This will frequently be null for non-EditText TextViews.
2401      * @return the current key listener for this TextView.
2402      *
2403      * @attr ref android.R.styleable#TextView_numeric
2404      * @attr ref android.R.styleable#TextView_digits
2405      * @attr ref android.R.styleable#TextView_phoneNumber
2406      * @attr ref android.R.styleable#TextView_inputMethod
2407      * @attr ref android.R.styleable#TextView_capitalize
2408      * @attr ref android.R.styleable#TextView_autoText
2409      */
2410     public final KeyListener getKeyListener() {
2411         return mEditor == null ? null : mEditor.mKeyListener;
2412     }
2413
2414     /**
2415      * Sets the key listener to be used with this TextView.  This can be null
2416      * to disallow user input.  Note that this method has significant and
2417      * subtle interactions with soft keyboards and other input method:
2418      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
2419      * for important details.  Calling this method will replace the current
2420      * content type of the text view with the content type returned by the
2421      * key listener.
2422      * <p>
2423      * Be warned that if you want a TextView with a key listener or movement
2424      * method not to be focusable, or if you want a TextView without a
2425      * key listener or movement method to be focusable, you must call
2426      * {@link #setFocusable} again after calling this to get the focusability
2427      * back the way you want it.
2428      *
2429      * @attr ref android.R.styleable#TextView_numeric
2430      * @attr ref android.R.styleable#TextView_digits
2431      * @attr ref android.R.styleable#TextView_phoneNumber
2432      * @attr ref android.R.styleable#TextView_inputMethod
2433      * @attr ref android.R.styleable#TextView_capitalize
2434      * @attr ref android.R.styleable#TextView_autoText
2435      */
2436     public void setKeyListener(KeyListener input) {
2437         mListenerChanged = true;
2438         setKeyListenerOnly(input);
2439         fixFocusableAndClickableSettings();
2440
2441         if (input != null) {
2442             createEditorIfNeeded();
2443             setInputTypeFromEditor();
2444         } else {
2445             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2446         }
2447
2448         InputMethodManager imm = getInputMethodManager();
2449         if (imm != null) imm.restartInput(this);
2450     }
2451
2452     private void setInputTypeFromEditor() {
2453         try {
2454             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2455         } catch (IncompatibleClassChangeError e) {
2456             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2457         }
2458         // Change inputType, without affecting transformation.
2459         // No need to applySingleLine since mSingleLine is unchanged.
2460         setInputTypeSingleLine(mSingleLine);
2461     }
2462
2463     private void setKeyListenerOnly(KeyListener input) {
2464         if (mEditor == null && input == null) return; // null is the default value
2465
2466         createEditorIfNeeded();
2467         if (mEditor.mKeyListener != input) {
2468             mEditor.mKeyListener = input;
2469             if (input != null && !(mText instanceof Editable)) {
2470                 setText(mText);
2471             }
2472
2473             setFilters((Editable) mText, mFilters);
2474         }
2475     }
2476
2477     /**
2478      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2479      * which provides positioning, scrolling, and text selection functionality.
2480      * This will frequently be null for non-EditText TextViews.
2481      * @return the movement method being used for this TextView.
2482      * @see android.text.method.MovementMethod
2483      */
2484     public final MovementMethod getMovementMethod() {
2485         return mMovement;
2486     }
2487
2488     /**
2489      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2490      * for this TextView. This can be null to disallow using the arrow keys to move the
2491      * cursor or scroll the view.
2492      * <p>
2493      * Be warned that if you want a TextView with a key listener or movement
2494      * method not to be focusable, or if you want a TextView without a
2495      * key listener or movement method to be focusable, you must call
2496      * {@link #setFocusable} again after calling this to get the focusability
2497      * back the way you want it.
2498      */
2499     public final void setMovementMethod(MovementMethod movement) {
2500         if (mMovement != movement) {
2501             mMovement = movement;
2502
2503             if (movement != null && mSpannable == null) {
2504                 setText(mText);
2505             }
2506
2507             fixFocusableAndClickableSettings();
2508
2509             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2510             // mMovement
2511             if (mEditor != null) mEditor.prepareCursorControllers();
2512         }
2513     }
2514
2515     private void fixFocusableAndClickableSettings() {
2516         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2517             setFocusable(FOCUSABLE);
2518             setClickable(true);
2519             setLongClickable(true);
2520         } else {
2521             setFocusable(FOCUSABLE_AUTO);
2522             setClickable(false);
2523             setLongClickable(false);
2524         }
2525     }
2526
2527     /**
2528      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2529      * This is frequently null, except for single-line and password fields.
2530      * @return the current transformation method for this TextView.
2531      *
2532      * @attr ref android.R.styleable#TextView_password
2533      * @attr ref android.R.styleable#TextView_singleLine
2534      */
2535     public final TransformationMethod getTransformationMethod() {
2536         return mTransformation;
2537     }
2538
2539     /**
2540      * Sets the transformation that is applied to the text that this
2541      * TextView is displaying.
2542      *
2543      * @attr ref android.R.styleable#TextView_password
2544      * @attr ref android.R.styleable#TextView_singleLine
2545      */
2546     public final void setTransformationMethod(TransformationMethod method) {
2547         if (method == mTransformation) {
2548             // Avoid the setText() below if the transformation is
2549             // the same.
2550             return;
2551         }
2552         if (mTransformation != null) {
2553             if (mSpannable != null) {
2554                 mSpannable.removeSpan(mTransformation);
2555             }
2556         }
2557
2558         mTransformation = method;
2559
2560         if (method instanceof TransformationMethod2) {
2561             TransformationMethod2 method2 = (TransformationMethod2) method;
2562             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2563             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2564         } else {
2565             mAllowTransformationLengthChange = false;
2566         }
2567
2568         setText(mText);
2569
2570         if (hasPasswordTransformationMethod()) {
2571             notifyViewAccessibilityStateChangedIfNeeded(
2572                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2573         }
2574
2575         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2576         // getTextDirectionHeuristic, needs reset
2577         mTextDir = getTextDirectionHeuristic();
2578     }
2579
2580     /**
2581      * Returns the top padding of the view, plus space for the top
2582      * Drawable if any.
2583      */
2584     public int getCompoundPaddingTop() {
2585         final Drawables dr = mDrawables;
2586         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2587             return mPaddingTop;
2588         } else {
2589             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2590         }
2591     }
2592
2593     /**
2594      * Returns the bottom padding of the view, plus space for the bottom
2595      * Drawable if any.
2596      */
2597     public int getCompoundPaddingBottom() {
2598         final Drawables dr = mDrawables;
2599         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2600             return mPaddingBottom;
2601         } else {
2602             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2603         }
2604     }
2605
2606     /**
2607      * Returns the left padding of the view, plus space for the left
2608      * Drawable if any.
2609      */
2610     public int getCompoundPaddingLeft() {
2611         final Drawables dr = mDrawables;
2612         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2613             return mPaddingLeft;
2614         } else {
2615             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2616         }
2617     }
2618
2619     /**
2620      * Returns the right padding of the view, plus space for the right
2621      * Drawable if any.
2622      */
2623     public int getCompoundPaddingRight() {
2624         final Drawables dr = mDrawables;
2625         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2626             return mPaddingRight;
2627         } else {
2628             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2629         }
2630     }
2631
2632     /**
2633      * Returns the start padding of the view, plus space for the start
2634      * Drawable if any.
2635      */
2636     public int getCompoundPaddingStart() {
2637         resolveDrawables();
2638         switch(getLayoutDirection()) {
2639             default:
2640             case LAYOUT_DIRECTION_LTR:
2641                 return getCompoundPaddingLeft();
2642             case LAYOUT_DIRECTION_RTL:
2643                 return getCompoundPaddingRight();
2644         }
2645     }
2646
2647     /**
2648      * Returns the end padding of the view, plus space for the end
2649      * Drawable if any.
2650      */
2651     public int getCompoundPaddingEnd() {
2652         resolveDrawables();
2653         switch(getLayoutDirection()) {
2654             default:
2655             case LAYOUT_DIRECTION_LTR:
2656                 return getCompoundPaddingRight();
2657             case LAYOUT_DIRECTION_RTL:
2658                 return getCompoundPaddingLeft();
2659         }
2660     }
2661
2662     /**
2663      * Returns the extended top padding of the view, including both the
2664      * top Drawable if any and any extra space to keep more than maxLines
2665      * of text from showing.  It is only valid to call this after measuring.
2666      */
2667     public int getExtendedPaddingTop() {
2668         if (mMaxMode != LINES) {
2669             return getCompoundPaddingTop();
2670         }
2671
2672         if (mLayout == null) {
2673             assumeLayout();
2674         }
2675
2676         if (mLayout.getLineCount() <= mMaximum) {
2677             return getCompoundPaddingTop();
2678         }
2679
2680         int top = getCompoundPaddingTop();
2681         int bottom = getCompoundPaddingBottom();
2682         int viewht = getHeight() - top - bottom;
2683         int layoutht = mLayout.getLineTop(mMaximum);
2684
2685         if (layoutht >= viewht) {
2686             return top;
2687         }
2688
2689         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2690         if (gravity == Gravity.TOP) {
2691             return top;
2692         } else if (gravity == Gravity.BOTTOM) {
2693             return top + viewht - layoutht;
2694         } else { // (gravity == Gravity.CENTER_VERTICAL)
2695             return top + (viewht - layoutht) / 2;
2696         }
2697     }
2698
2699     /**
2700      * Returns the extended bottom padding of the view, including both the
2701      * bottom Drawable if any and any extra space to keep more than maxLines
2702      * of text from showing.  It is only valid to call this after measuring.
2703      */
2704     public int getExtendedPaddingBottom() {
2705         if (mMaxMode != LINES) {
2706             return getCompoundPaddingBottom();
2707         }
2708
2709         if (mLayout == null) {
2710             assumeLayout();
2711         }
2712
2713         if (mLayout.getLineCount() <= mMaximum) {
2714             return getCompoundPaddingBottom();
2715         }
2716
2717         int top = getCompoundPaddingTop();
2718         int bottom = getCompoundPaddingBottom();
2719         int viewht = getHeight() - top - bottom;
2720         int layoutht = mLayout.getLineTop(mMaximum);
2721
2722         if (layoutht >= viewht) {
2723             return bottom;
2724         }
2725
2726         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2727         if (gravity == Gravity.TOP) {
2728             return bottom + viewht - layoutht;
2729         } else if (gravity == Gravity.BOTTOM) {
2730             return bottom;
2731         } else { // (gravity == Gravity.CENTER_VERTICAL)
2732             return bottom + (viewht - layoutht) / 2;
2733         }
2734     }
2735
2736     /**
2737      * Returns the total left padding of the view, including the left
2738      * Drawable if any.
2739      */
2740     public int getTotalPaddingLeft() {
2741         return getCompoundPaddingLeft();
2742     }
2743
2744     /**
2745      * Returns the total right padding of the view, including the right
2746      * Drawable if any.
2747      */
2748     public int getTotalPaddingRight() {
2749         return getCompoundPaddingRight();
2750     }
2751
2752     /**
2753      * Returns the total start padding of the view, including the start
2754      * Drawable if any.
2755      */
2756     public int getTotalPaddingStart() {
2757         return getCompoundPaddingStart();
2758     }
2759
2760     /**
2761      * Returns the total end padding of the view, including the end
2762      * Drawable if any.
2763      */
2764     public int getTotalPaddingEnd() {
2765         return getCompoundPaddingEnd();
2766     }
2767
2768     /**
2769      * Returns the total top padding of the view, including the top
2770      * Drawable if any, the extra space to keep more than maxLines
2771      * from showing, and the vertical offset for gravity, if any.
2772      */
2773     public int getTotalPaddingTop() {
2774         return getExtendedPaddingTop() + getVerticalOffset(true);
2775     }
2776
2777     /**
2778      * Returns the total bottom padding of the view, including the bottom
2779      * Drawable if any, the extra space to keep more than maxLines
2780      * from showing, and the vertical offset for gravity, if any.
2781      */
2782     public int getTotalPaddingBottom() {
2783         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2784     }
2785
2786     /**
2787      * Sets the Drawables (if any) to appear to the left of, above, to the
2788      * right of, and below the text. Use {@code null} if you do not want a
2789      * Drawable there. The Drawables must already have had
2790      * {@link Drawable#setBounds} called.
2791      * <p>
2792      * Calling this method will overwrite any Drawables previously set using
2793      * {@link #setCompoundDrawablesRelative} or related methods.
2794      *
2795      * @attr ref android.R.styleable#TextView_drawableLeft
2796      * @attr ref android.R.styleable#TextView_drawableTop
2797      * @attr ref android.R.styleable#TextView_drawableRight
2798      * @attr ref android.R.styleable#TextView_drawableBottom
2799      */
2800     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2801             @Nullable Drawable right, @Nullable Drawable bottom) {
2802         Drawables dr = mDrawables;
2803
2804         // We're switching to absolute, discard relative.
2805         if (dr != null) {
2806             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2807             dr.mDrawableStart = null;
2808             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2809             dr.mDrawableEnd = null;
2810             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2811             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2812         }
2813
2814         final boolean drawables = left != null || top != null || right != null || bottom != null;
2815         if (!drawables) {
2816             // Clearing drawables...  can we free the data structure?
2817             if (dr != null) {
2818                 if (!dr.hasMetadata()) {
2819                     mDrawables = null;
2820                 } else {
2821                     // We need to retain the last set padding, so just clear
2822                     // out all of the fields in the existing structure.
2823                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2824                         if (dr.mShowing[i] != null) {
2825                             dr.mShowing[i].setCallback(null);
2826                         }
2827                         dr.mShowing[i] = null;
2828                     }
2829                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2830                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2831                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2832                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2833                 }
2834             }
2835         } else {
2836             if (dr == null) {
2837                 mDrawables = dr = new Drawables(getContext());
2838             }
2839
2840             mDrawables.mOverride = false;
2841
2842             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2843                 dr.mShowing[Drawables.LEFT].setCallback(null);
2844             }
2845             dr.mShowing[Drawables.LEFT] = left;
2846
2847             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2848                 dr.mShowing[Drawables.TOP].setCallback(null);
2849             }
2850             dr.mShowing[Drawables.TOP] = top;
2851
2852             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2853                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2854             }
2855             dr.mShowing[Drawables.RIGHT] = right;
2856
2857             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2858                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2859             }
2860             dr.mShowing[Drawables.BOTTOM] = bottom;
2861
2862             final Rect compoundRect = dr.mCompoundRect;
2863             int[] state;
2864
2865             state = getDrawableState();
2866
2867             if (left != null) {
2868                 left.setState(state);
2869                 left.copyBounds(compoundRect);
2870                 left.setCallback(this);
2871                 dr.mDrawableSizeLeft = compoundRect.width();
2872                 dr.mDrawableHeightLeft = compoundRect.height();
2873             } else {
2874                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2875             }
2876
2877             if (right != null) {
2878                 right.setState(state);
2879                 right.copyBounds(compoundRect);
2880                 right.setCallback(this);
2881                 dr.mDrawableSizeRight = compoundRect.width();
2882                 dr.mDrawableHeightRight = compoundRect.height();
2883             } else {
2884                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2885             }
2886
2887             if (top != null) {
2888                 top.setState(state);
2889                 top.copyBounds(compoundRect);
2890                 top.setCallback(this);
2891                 dr.mDrawableSizeTop = compoundRect.height();
2892                 dr.mDrawableWidthTop = compoundRect.width();
2893             } else {
2894                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2895             }
2896
2897             if (bottom != null) {
2898                 bottom.setState(state);
2899                 bottom.copyBounds(compoundRect);
2900                 bottom.setCallback(this);
2901                 dr.mDrawableSizeBottom = compoundRect.height();
2902                 dr.mDrawableWidthBottom = compoundRect.width();
2903             } else {
2904                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2905             }
2906         }
2907
2908         // Save initial left/right drawables
2909         if (dr != null) {
2910             dr.mDrawableLeftInitial = left;
2911             dr.mDrawableRightInitial = right;
2912         }
2913
2914         resetResolvedDrawables();
2915         resolveDrawables();
2916         applyCompoundDrawableTint();
2917         invalidate();
2918         requestLayout();
2919     }
2920
2921     /**
2922      * Sets the Drawables (if any) to appear to the left of, above, to the
2923      * right of, and below the text. Use 0 if you do not want a Drawable there.
2924      * The Drawables' bounds will be set to their intrinsic bounds.
2925      * <p>
2926      * Calling this method will overwrite any Drawables previously set using
2927      * {@link #setCompoundDrawablesRelative} or related methods.
2928      *
2929      * @param left Resource identifier of the left Drawable.
2930      * @param top Resource identifier of the top Drawable.
2931      * @param right Resource identifier of the right Drawable.
2932      * @param bottom Resource identifier of the bottom Drawable.
2933      *
2934      * @attr ref android.R.styleable#TextView_drawableLeft
2935      * @attr ref android.R.styleable#TextView_drawableTop
2936      * @attr ref android.R.styleable#TextView_drawableRight
2937      * @attr ref android.R.styleable#TextView_drawableBottom
2938      */
2939     @android.view.RemotableViewMethod
2940     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2941             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2942         final Context context = getContext();
2943         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2944                 top != 0 ? context.getDrawable(top) : null,
2945                 right != 0 ? context.getDrawable(right) : null,
2946                 bottom != 0 ? context.getDrawable(bottom) : null);
2947     }
2948
2949     /**
2950      * Sets the Drawables (if any) to appear to the left of, above, to the
2951      * right of, and below the text. Use {@code null} if you do not want a
2952      * Drawable there. The Drawables' bounds will be set to their intrinsic
2953      * bounds.
2954      * <p>
2955      * Calling this method will overwrite any Drawables previously set using
2956      * {@link #setCompoundDrawablesRelative} or related methods.
2957      *
2958      * @attr ref android.R.styleable#TextView_drawableLeft
2959      * @attr ref android.R.styleable#TextView_drawableTop
2960      * @attr ref android.R.styleable#TextView_drawableRight
2961      * @attr ref android.R.styleable#TextView_drawableBottom
2962      */
2963     @android.view.RemotableViewMethod
2964     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2965             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2966
2967         if (left != null) {
2968             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2969         }
2970         if (right != null) {
2971             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2972         }
2973         if (top != null) {
2974             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2975         }
2976         if (bottom != null) {
2977             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2978         }
2979         setCompoundDrawables(left, top, right, bottom);
2980     }
2981
2982     /**
2983      * Sets the Drawables (if any) to appear to the start of, above, to the end
2984      * of, and below the text. Use {@code null} if you do not want a Drawable
2985      * there. The Drawables must already have had {@link Drawable#setBounds}
2986      * called.
2987      * <p>
2988      * Calling this method will overwrite any Drawables previously set using
2989      * {@link #setCompoundDrawables} or related methods.
2990      *
2991      * @attr ref android.R.styleable#TextView_drawableStart
2992      * @attr ref android.R.styleable#TextView_drawableTop
2993      * @attr ref android.R.styleable#TextView_drawableEnd
2994      * @attr ref android.R.styleable#TextView_drawableBottom
2995      */
2996     @android.view.RemotableViewMethod
2997     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2998             @Nullable Drawable end, @Nullable Drawable bottom) {
2999         Drawables dr = mDrawables;
3000
3001         // We're switching to relative, discard absolute.
3002         if (dr != null) {
3003             if (dr.mShowing[Drawables.LEFT] != null) {
3004                 dr.mShowing[Drawables.LEFT].setCallback(null);
3005             }
3006             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
3007             if (dr.mShowing[Drawables.RIGHT] != null) {
3008                 dr.mShowing[Drawables.RIGHT].setCallback(null);
3009             }
3010             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
3011             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
3012             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
3013         }
3014
3015         final boolean drawables = start != null || top != null
3016                 || end != null || bottom != null;
3017
3018         if (!drawables) {
3019             // Clearing drawables...  can we free the data structure?
3020             if (dr != null) {
3021                 if (!dr.hasMetadata()) {
3022                     mDrawables = null;
3023                 } else {
3024                     // We need to retain the last set padding, so just clear
3025                     // out all of the fields in the existing structure.
3026                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
3027                     dr.mDrawableStart = null;
3028                     if (dr.mShowing[Drawables.TOP] != null) {
3029                         dr.mShowing[Drawables.TOP].setCallback(null);
3030                     }
3031                     dr.mShowing[Drawables.TOP] = null;
3032                     if (dr.mDrawableEnd != null) {
3033                         dr.mDrawableEnd.setCallback(null);
3034                     }
3035                     dr.mDrawableEnd = null;
3036                     if (dr.mShowing[Drawables.BOTTOM] != null) {
3037                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
3038                     }
3039                     dr.mShowing[Drawables.BOTTOM] = null;
3040                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3041                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3042                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3043                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3044                 }
3045             }
3046         } else {
3047             if (dr == null) {
3048                 mDrawables = dr = new Drawables(getContext());
3049             }
3050
3051             mDrawables.mOverride = true;
3052
3053             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3054                 dr.mDrawableStart.setCallback(null);
3055             }
3056             dr.mDrawableStart = start;
3057
3058             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3059                 dr.mShowing[Drawables.TOP].setCallback(null);
3060             }
3061             dr.mShowing[Drawables.TOP] = top;
3062
3063             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3064                 dr.mDrawableEnd.setCallback(null);
3065             }
3066             dr.mDrawableEnd = end;
3067
3068             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3069                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3070             }
3071             dr.mShowing[Drawables.BOTTOM] = bottom;
3072
3073             final Rect compoundRect = dr.mCompoundRect;
3074             int[] state;
3075
3076             state = getDrawableState();
3077
3078             if (start != null) {
3079                 start.setState(state);
3080                 start.copyBounds(compoundRect);
3081                 start.setCallback(this);
3082                 dr.mDrawableSizeStart = compoundRect.width();
3083                 dr.mDrawableHeightStart = compoundRect.height();
3084             } else {
3085                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3086             }
3087
3088             if (end != null) {
3089                 end.setState(state);
3090                 end.copyBounds(compoundRect);
3091                 end.setCallback(this);
3092                 dr.mDrawableSizeEnd = compoundRect.width();
3093                 dr.mDrawableHeightEnd = compoundRect.height();
3094             } else {
3095                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3096             }
3097
3098             if (top != null) {
3099                 top.setState(state);
3100                 top.copyBounds(compoundRect);
3101                 top.setCallback(this);
3102                 dr.mDrawableSizeTop = compoundRect.height();
3103                 dr.mDrawableWidthTop = compoundRect.width();
3104             } else {
3105                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3106             }
3107
3108             if (bottom != null) {
3109                 bottom.setState(state);
3110                 bottom.copyBounds(compoundRect);
3111                 bottom.setCallback(this);
3112                 dr.mDrawableSizeBottom = compoundRect.height();
3113                 dr.mDrawableWidthBottom = compoundRect.width();
3114             } else {
3115                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3116             }
3117         }
3118
3119         resetResolvedDrawables();
3120         resolveDrawables();
3121         invalidate();
3122         requestLayout();
3123     }
3124
3125     /**
3126      * Sets the Drawables (if any) to appear to the start of, above, to the end
3127      * of, and below the text. Use 0 if you do not want a Drawable there. The
3128      * Drawables' bounds will be set to their intrinsic bounds.
3129      * <p>
3130      * Calling this method will overwrite any Drawables previously set using
3131      * {@link #setCompoundDrawables} or related methods.
3132      *
3133      * @param start Resource identifier of the start Drawable.
3134      * @param top Resource identifier of the top Drawable.
3135      * @param end Resource identifier of the end Drawable.
3136      * @param bottom Resource identifier of the bottom Drawable.
3137      *
3138      * @attr ref android.R.styleable#TextView_drawableStart
3139      * @attr ref android.R.styleable#TextView_drawableTop
3140      * @attr ref android.R.styleable#TextView_drawableEnd
3141      * @attr ref android.R.styleable#TextView_drawableBottom
3142      */
3143     @android.view.RemotableViewMethod
3144     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3145             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3146         final Context context = getContext();
3147         setCompoundDrawablesRelativeWithIntrinsicBounds(
3148                 start != 0 ? context.getDrawable(start) : null,
3149                 top != 0 ? context.getDrawable(top) : null,
3150                 end != 0 ? context.getDrawable(end) : null,
3151                 bottom != 0 ? context.getDrawable(bottom) : null);
3152     }
3153
3154     /**
3155      * Sets the Drawables (if any) to appear to the start of, above, to the end
3156      * of, and below the text. Use {@code null} if you do not want a Drawable
3157      * there. The Drawables' bounds will be set to their intrinsic bounds.
3158      * <p>
3159      * Calling this method will overwrite any Drawables previously set using
3160      * {@link #setCompoundDrawables} or related methods.
3161      *
3162      * @attr ref android.R.styleable#TextView_drawableStart
3163      * @attr ref android.R.styleable#TextView_drawableTop
3164      * @attr ref android.R.styleable#TextView_drawableEnd
3165      * @attr ref android.R.styleable#TextView_drawableBottom
3166      */
3167     @android.view.RemotableViewMethod
3168     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3169             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3170
3171         if (start != null) {
3172             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3173         }
3174         if (end != null) {
3175             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3176         }
3177         if (top != null) {
3178             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3179         }
3180         if (bottom != null) {
3181             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3182         }
3183         setCompoundDrawablesRelative(start, top, end, bottom);
3184     }
3185
3186     /**
3187      * Returns drawables for the left, top, right, and bottom borders.
3188      *
3189      * @attr ref android.R.styleable#TextView_drawableLeft
3190      * @attr ref android.R.styleable#TextView_drawableTop
3191      * @attr ref android.R.styleable#TextView_drawableRight
3192      * @attr ref android.R.styleable#TextView_drawableBottom
3193      */
3194     @NonNull
3195     public Drawable[] getCompoundDrawables() {
3196         final Drawables dr = mDrawables;
3197         if (dr != null) {
3198             return dr.mShowing.clone();
3199         } else {
3200             return new Drawable[] { null, null, null, null };
3201         }
3202     }
3203
3204     /**
3205      * Returns drawables for the start, top, end, and bottom borders.
3206      *
3207      * @attr ref android.R.styleable#TextView_drawableStart
3208      * @attr ref android.R.styleable#TextView_drawableTop
3209      * @attr ref android.R.styleable#TextView_drawableEnd
3210      * @attr ref android.R.styleable#TextView_drawableBottom
3211      */
3212     @NonNull
3213     public Drawable[] getCompoundDrawablesRelative() {
3214         final Drawables dr = mDrawables;
3215         if (dr != null) {
3216             return new Drawable[] {
3217                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3218                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3219             };
3220         } else {
3221             return new Drawable[] { null, null, null, null };
3222         }
3223     }
3224
3225     /**
3226      * Sets the size of the padding between the compound drawables and
3227      * the text.
3228      *
3229      * @attr ref android.R.styleable#TextView_drawablePadding
3230      */
3231     @android.view.RemotableViewMethod
3232     public void setCompoundDrawablePadding(int pad) {
3233         Drawables dr = mDrawables;
3234         if (pad == 0) {
3235             if (dr != null) {
3236                 dr.mDrawablePadding = pad;
3237             }
3238         } else {
3239             if (dr == null) {
3240                 mDrawables = dr = new Drawables(getContext());
3241             }
3242             dr.mDrawablePadding = pad;
3243         }
3244
3245         invalidate();
3246         requestLayout();
3247     }
3248
3249     /**
3250      * Returns the padding between the compound drawables and the text.
3251      *
3252      * @attr ref android.R.styleable#TextView_drawablePadding
3253      */
3254     @InspectableProperty(name = "drawablePadding")
3255     public int getCompoundDrawablePadding() {
3256         final Drawables dr = mDrawables;
3257         return dr != null ? dr.mDrawablePadding : 0;
3258     }
3259
3260     /**
3261      * Applies a tint to the compound drawables. Does not modify the
3262      * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
3263      * <p>
3264      * Subsequent calls to
3265      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3266      * and related methods will automatically mutate the drawables and apply
3267      * the specified tint and tint mode using
3268      * {@link Drawable#setTintList(ColorStateList)}.
3269      *
3270      * @param tint the tint to apply, may be {@code null} to clear tint
3271      *
3272      * @attr ref android.R.styleable#TextView_drawableTint
3273      * @see #getCompoundDrawableTintList()
3274      * @see Drawable#setTintList(ColorStateList)
3275      */
3276     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3277         if (mDrawables == null) {
3278             mDrawables = new Drawables(getContext());
3279         }
3280         mDrawables.mTintList = tint;
3281         mDrawables.mHasTint = true;
3282
3283         applyCompoundDrawableTint();
3284     }
3285
3286     /**
3287      * @return the tint applied to the compound drawables
3288      * @attr ref android.R.styleable#TextView_drawableTint
3289      * @see #setCompoundDrawableTintList(ColorStateList)
3290      */
3291     @InspectableProperty(name = "drawableTint")
3292     public ColorStateList getCompoundDrawableTintList() {
3293         return mDrawables != null ? mDrawables.mTintList : null;
3294     }
3295
3296     /**
3297      * Specifies the blending mode used to apply the tint specified by
3298      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3299      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3300      *
3301      * @param tintMode the blending mode used to apply the tint, may be
3302      *                 {@code null} to clear tint
3303      * @attr ref android.R.styleable#TextView_drawableTintMode
3304      * @see #setCompoundDrawableTintList(ColorStateList)
3305      * @see Drawable#setTintMode(PorterDuff.Mode)
3306      */
3307     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3308         setCompoundDrawableTintBlendMode(tintMode != null
3309                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
3310     }
3311
3312     /**
3313      * Specifies the blending mode used to apply the tint specified by
3314      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3315      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3316      *
3317      * @param blendMode the blending mode used to apply the tint, may be
3318      *                 {@code null} to clear tint
3319      * @attr ref android.R.styleable#TextView_drawableTintMode
3320      * @see #setCompoundDrawableTintList(ColorStateList)
3321      * @see Drawable#setTintBlendMode(BlendMode)
3322      */
3323     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
3324         if (mDrawables == null) {
3325             mDrawables = new Drawables(getContext());
3326         }
3327         mDrawables.mBlendMode = blendMode;
3328         mDrawables.mHasTintMode = true;
3329
3330         applyCompoundDrawableTint();
3331     }
3332
3333     /**
3334      * Returns the blending mode used to apply the tint to the compound
3335      * drawables, if specified.
3336      *
3337      * @return the blending mode used to apply the tint to the compound
3338      *         drawables
3339      * @attr ref android.R.styleable#TextView_drawableTintMode
3340      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3341      *
3342      */
3343     @InspectableProperty(name = "drawableTintMode")
3344     public PorterDuff.Mode getCompoundDrawableTintMode() {
3345         BlendMode mode = getCompoundDrawableTintBlendMode();
3346         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
3347     }
3348
3349     /**
3350      * Returns the blending mode used to apply the tint to the compound
3351      * drawables, if specified.
3352      *
3353      * @return the blending mode used to apply the tint to the compound
3354      *         drawables
3355      * @attr ref android.R.styleable#TextView_drawableTintMode
3356      * @see #setCompoundDrawableTintBlendMode(BlendMode)
3357      */
3358     @InspectableProperty(name = "drawableBlendMode",
3359             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
3360     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
3361         return mDrawables != null ? mDrawables.mBlendMode : null;
3362     }
3363
3364     private void applyCompoundDrawableTint() {
3365         if (mDrawables == null) {
3366             return;
3367         }
3368
3369         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3370             final ColorStateList tintList = mDrawables.mTintList;
3371             final BlendMode blendMode = mDrawables.mBlendMode;
3372             final boolean hasTint = mDrawables.mHasTint;
3373             final boolean hasTintMode = mDrawables.mHasTintMode;
3374             final int[] state = getDrawableState();
3375
3376             for (Drawable dr : mDrawables.mShowing) {
3377                 if (dr == null) {
3378                     continue;
3379                 }
3380
3381                 if (dr == mDrawables.mDrawableError) {
3382                     // From a developer's perspective, the error drawable isn't
3383                     // a compound drawable. Don't apply the generic compound
3384                     // drawable tint to it.
3385                     continue;
3386                 }
3387
3388                 dr.mutate();
3389
3390                 if (hasTint) {
3391                     dr.setTintList(tintList);
3392                 }
3393
3394                 if (hasTintMode) {
3395                     dr.setTintBlendMode(blendMode);
3396                 }
3397
3398                 // The drawable (or one of its children) may not have been
3399                 // stateful before applying the tint, so let's try again.
3400                 if (dr.isStateful()) {
3401                     dr.setState(state);
3402                 }
3403             }
3404         }
3405     }
3406
3407     /**
3408      * @inheritDoc
3409      *
3410      * @see #setFirstBaselineToTopHeight(int)
3411      * @see #setLastBaselineToBottomHeight(int)
3412      */
3413     @Override
3414     public void setPadding(int left, int top, int right, int bottom) {
3415         if (left != mPaddingLeft
3416                 || right != mPaddingRight
3417                 || top != mPaddingTop
3418                 ||  bottom != mPaddingBottom) {
3419             nullLayouts();
3420         }
3421
3422         // the super call will requestLayout()
3423         super.setPadding(left, top, right, bottom);
3424         invalidate();
3425     }
3426
3427     /**
3428      * @inheritDoc
3429      *
3430      * @see #setFirstBaselineToTopHeight(int)
3431      * @see #setLastBaselineToBottomHeight(int)
3432      */
3433     @Override
3434     public void setPaddingRelative(int start, int top, int end, int bottom) {
3435         if (start != getPaddingStart()
3436                 || end != getPaddingEnd()
3437                 || top != mPaddingTop
3438                 || bottom != mPaddingBottom) {
3439             nullLayouts();
3440         }
3441
3442         // the super call will requestLayout()
3443         super.setPaddingRelative(start, top, end, bottom);
3444         invalidate();
3445     }
3446
3447     /**
3448      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
3449      * the distance between the top of the TextView and first line's baseline.
3450      * <p>
3451      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3452      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3453      *
3454      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
3455      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
3456      * Moreover since this function sets the top padding, if the height of the TextView is less than
3457      * the sum of top padding, line height and bottom padding, top of the line will be pushed
3458      * down and bottom will be clipped.
3459      *
3460      * @param firstBaselineToTopHeight distance between first baseline to top of the container
3461      *      in pixels
3462      *
3463      * @see #getFirstBaselineToTopHeight()
3464      * @see #setLastBaselineToBottomHeight(int)
3465      * @see #setPadding(int, int, int, int)
3466      * @see #setPaddingRelative(int, int, int, int)
3467      *
3468      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3469      */
3470     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
3471         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
3472
3473         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3474         final int fontMetricsTop;
3475         if (getIncludeFontPadding()) {
3476             fontMetricsTop = fontMetrics.top;
3477         } else {
3478             fontMetricsTop = fontMetrics.ascent;
3479         }
3480
3481         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3482         // in settings). At the moment, we don't.
3483
3484         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
3485             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
3486             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
3487         }
3488     }
3489
3490     /**
3491      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
3492      * the distance between the bottom of the TextView and the last line's baseline.
3493      * <p>
3494      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
3495      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
3496      *
3497      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
3498      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
3499      * Moreover since this function sets the bottom padding, if the height of the TextView is less
3500      * than the sum of top padding, line height and bottom padding, bottom of the text will be
3501      * clipped.
3502      *
3503      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
3504      *      in pixels
3505      *
3506      * @see #getLastBaselineToBottomHeight()
3507      * @see #setFirstBaselineToTopHeight(int)
3508      * @see #setPadding(int, int, int, int)
3509      * @see #setPaddingRelative(int, int, int, int)
3510      *
3511      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3512      */
3513     public void setLastBaselineToBottomHeight(
3514             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
3515         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
3516
3517         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
3518         final int fontMetricsBottom;
3519         if (getIncludeFontPadding()) {
3520             fontMetricsBottom = fontMetrics.bottom;
3521         } else {
3522             fontMetricsBottom = fontMetrics.descent;
3523         }
3524
3525         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
3526         // in settings). At the moment, we don't.
3527
3528         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
3529             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
3530             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
3531         }
3532     }
3533
3534     /**
3535      * Returns the distance between the first text baseline and the top of this TextView.
3536      *
3537      * @see #setFirstBaselineToTopHeight(int)
3538      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
3539      */
3540     @InspectableProperty
3541     public int getFirstBaselineToTopHeight() {
3542         return getPaddingTop() - getPaint().getFontMetricsInt().top;
3543     }
3544
3545     /**
3546      * Returns the distance between the last text baseline and the bottom of this TextView.
3547      *
3548      * @see #setLastBaselineToBottomHeight(int)
3549      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
3550      */
3551     @InspectableProperty
3552     public int getLastBaselineToBottomHeight() {
3553         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
3554     }
3555
3556     /**
3557      * Gets the autolink mask of the text.
3558      *
3559      * See {@link Linkify#ALL} and peers for possible values.
3560      *
3561      * @attr ref android.R.styleable#TextView_autoLink
3562      */
3563     @InspectableProperty(name = "autoLink", flagMapping = {
3564             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
3565             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
3566             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
3567             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
3568     })
3569     public final int getAutoLinkMask() {
3570         return mAutoLinkMask;
3571     }
3572
3573     /**
3574      * Sets the Drawable corresponding to the selection handle used for
3575      * positioning the cursor within text. The Drawable defaults to the value
3576      * of the textSelectHandle attribute.
3577      * Note that any change applied to the handle Drawable will not be visible
3578      * until the handle is hidden and then drawn again.
3579      *
3580      * @see #setTextSelectHandle(int)
3581      * @attr ref android.R.styleable#TextView_textSelectHandle
3582      */
3583     @android.view.RemotableViewMethod
3584     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
3585         Preconditions.checkNotNull(textSelectHandle,
3586                 "The text select handle should not be null.");
3587         mTextSelectHandle = textSelectHandle;
3588         mTextSelectHandleRes = 0;
3589         if (mEditor != null) {
3590             mEditor.loadHandleDrawables(true /* overwrite */);
3591         }
3592     }
3593
3594     /**
3595      * Sets the Drawable corresponding to the selection handle used for
3596      * positioning the cursor within text. The Drawable defaults to the value
3597      * of the textSelectHandle attribute.
3598      * Note that any change applied to the handle Drawable will not be visible
3599      * until the handle is hidden and then drawn again.
3600      *
3601      * @see #setTextSelectHandle(Drawable)
3602      * @attr ref android.R.styleable#TextView_textSelectHandle
3603      */
3604     @android.view.RemotableViewMethod
3605     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
3606         Preconditions.checkArgument(textSelectHandle != 0,
3607                 "The text select handle should be a valid drawable resource id.");
3608         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
3609     }
3610
3611     /**
3612      * Returns the Drawable corresponding to the selection handle used
3613      * for positioning the cursor within text.
3614      * Note that any change applied to the handle Drawable will not be visible
3615      * until the handle is hidden and then drawn again.
3616      *
3617      * @return the text select handle drawable
3618      *
3619      * @see #setTextSelectHandle(Drawable)
3620      * @see #setTextSelectHandle(int)
3621      * @attr ref android.R.styleable#TextView_textSelectHandle
3622      */
3623     @Nullable public Drawable getTextSelectHandle() {
3624         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
3625             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
3626         }
3627         return mTextSelectHandle;
3628     }
3629
3630     /**
3631      * Sets the Drawable corresponding to the left handle used
3632      * for selecting text. The Drawable defaults to the value of the
3633      * textSelectHandleLeft attribute.
3634      * Note that any change applied to the handle Drawable will not be visible
3635      * until the handle is hidden and then drawn again.
3636      *
3637      * @see #setTextSelectHandleLeft(int)
3638      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3639      */
3640     @android.view.RemotableViewMethod
3641     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
3642         Preconditions.checkNotNull(textSelectHandleLeft,
3643                 "The left text select handle should not be null.");
3644         mTextSelectHandleLeft = textSelectHandleLeft;
3645         mTextSelectHandleLeftRes = 0;
3646         if (mEditor != null) {
3647             mEditor.loadHandleDrawables(true /* overwrite */);
3648         }
3649     }
3650
3651     /**
3652      * Sets the Drawable corresponding to the left handle used
3653      * for selecting text. The Drawable defaults to the value of the
3654      * textSelectHandleLeft attribute.
3655      * Note that any change applied to the handle Drawable will not be visible
3656      * until the handle is hidden and then drawn again.
3657      *
3658      * @see #setTextSelectHandleLeft(Drawable)
3659      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3660      */
3661     @android.view.RemotableViewMethod
3662     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
3663         Preconditions.checkArgument(textSelectHandleLeft != 0,
3664                 "The text select left handle should be a valid drawable resource id.");
3665         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
3666     }
3667
3668     /**
3669      * Returns the Drawable corresponding to the left handle used
3670      * for selecting text.
3671      * Note that any change applied to the handle Drawable will not be visible
3672      * until the handle is hidden and then drawn again.
3673      *
3674      * @return the left text selection handle drawable
3675      *
3676      * @see #setTextSelectHandleLeft(Drawable)
3677      * @see #setTextSelectHandleLeft(int)
3678      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
3679      */
3680     @Nullable public Drawable getTextSelectHandleLeft() {
3681         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
3682             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
3683         }
3684         return mTextSelectHandleLeft;
3685     }
3686
3687     /**
3688      * Sets the Drawable corresponding to the right handle used
3689      * for selecting text. The Drawable defaults to the value of the
3690      * textSelectHandleRight attribute.
3691      * Note that any change applied to the handle Drawable will not be visible
3692      * until the handle is hidden and then drawn again.
3693      *
3694      * @see #setTextSelectHandleRight(int)
3695      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3696      */
3697     @android.view.RemotableViewMethod
3698     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
3699         Preconditions.checkNotNull(textSelectHandleRight,
3700                 "The right text select handle should not be null.");
3701         mTextSelectHandleRight = textSelectHandleRight;
3702         mTextSelectHandleRightRes = 0;
3703         if (mEditor != null) {
3704             mEditor.loadHandleDrawables(true /* overwrite */);
3705         }
3706     }
3707
3708     /**
3709      * Sets the Drawable corresponding to the right handle used
3710      * for selecting text. The Drawable defaults to the value of the
3711      * textSelectHandleRight attribute.
3712      * Note that any change applied to the handle Drawable will not be visible
3713      * until the handle is hidden and then drawn again.
3714      *
3715      * @see #setTextSelectHandleRight(Drawable)
3716      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3717      */
3718     @android.view.RemotableViewMethod
3719     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
3720         Preconditions.checkArgument(textSelectHandleRight != 0,
3721                 "The text select right handle should be a valid drawable resource id.");
3722         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
3723     }
3724
3725     /**
3726      * Returns the Drawable corresponding to the right handle used
3727      * for selecting text.
3728      * Note that any change applied to the handle Drawable will not be visible
3729      * until the handle is hidden and then drawn again.
3730      *
3731      * @return the right text selection handle drawable
3732      *
3733      * @see #setTextSelectHandleRight(Drawable)
3734      * @see #setTextSelectHandleRight(int)
3735      * @attr ref android.R.styleable#TextView_textSelectHandleRight
3736      */
3737     @Nullable public Drawable getTextSelectHandleRight() {
3738         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
3739             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
3740         }
3741         return mTextSelectHandleRight;
3742     }
3743
3744     /**
3745      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3746      * value of the textCursorDrawable attribute.
3747      * Note that any change applied to the cursor Drawable will not be visible
3748      * until the cursor is hidden and then drawn again.
3749      *
3750      * @see #setTextCursorDrawable(int)
3751      * @attr ref android.R.styleable#TextView_textCursorDrawable
3752      */
3753     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
3754         mCursorDrawable = textCursorDrawable;
3755         mCursorDrawableRes = 0;
3756         if (mEditor != null) {
3757             mEditor.loadCursorDrawable();
3758         }
3759     }
3760
3761     /**
3762      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
3763      * value of the textCursorDrawable attribute.
3764      * Note that any change applied to the cursor Drawable will not be visible
3765      * until the cursor is hidden and then drawn again.
3766      *
3767      * @see #setTextCursorDrawable(Drawable)
3768      * @attr ref android.R.styleable#TextView_textCursorDrawable
3769      */
3770     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
3771         setTextCursorDrawable(
3772                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
3773     }
3774
3775     /**
3776      * Returns the Drawable corresponding to the text cursor.
3777      * Note that any change applied to the cursor Drawable will not be visible
3778      * until the cursor is hidden and then drawn again.
3779      *
3780      * @return the text cursor drawable
3781      *
3782      * @see #setTextCursorDrawable(Drawable)
3783      * @see #setTextCursorDrawable(int)
3784      * @attr ref android.R.styleable#TextView_textCursorDrawable
3785      */
3786     @Nullable public Drawable getTextCursorDrawable() {
3787         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
3788             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
3789         }
3790         return mCursorDrawable;
3791     }
3792
3793     /**
3794      * Sets the text appearance from the specified style resource.
3795      * <p>
3796      * Use a framework-defined {@code TextAppearance} style like
3797      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3798      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3799      * set of attributes that can be used in a custom style.
3800      *
3801      * @param resId the resource identifier of the style to apply
3802      * @attr ref android.R.styleable#TextView_textAppearance
3803      */
3804     @SuppressWarnings("deprecation")
3805     public void setTextAppearance(@StyleRes int resId) {
3806         setTextAppearance(mContext, resId);
3807     }
3808
3809     /**
3810      * Sets the text color, size, style, hint color, and highlight color
3811      * from the specified TextAppearance resource.
3812      *
3813      * @deprecated Use {@link #setTextAppearance(int)} instead.
3814      */
3815     @Deprecated
3816     public void setTextAppearance(Context context, @StyleRes int resId) {
3817         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3818         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
3819         readTextAppearance(context, ta, attributes, false /* styleArray */);
3820         ta.recycle();
3821         applyTextAppearance(attributes);
3822     }
3823
3824     /**
3825      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
3826      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
3827      */
3828     private static class TextAppearanceAttributes {
3829         int mTextColorHighlight = 0;
3830         ColorStateList mTextColor = null;
3831         ColorStateList mTextColorHint = null;
3832         ColorStateList mTextColorLink = null;
3833         int mTextSize = -1;
3834         LocaleList mTextLocales = null;
3835         String mFontFamily = null;
3836         Typeface mFontTypeface = null;
3837         boolean mFontFamilyExplicit = false;
3838         int mTypefaceIndex = -1;
3839         int mTextStyle = 0;
3840         int mFontWeight = -1;
3841         boolean mAllCaps = false;
3842         int mShadowColor = 0;
3843         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
3844         boolean mHasElegant = false;
3845         boolean mElegant = false;
3846         boolean mHasFallbackLineSpacing = false;
3847         boolean mFallbackLineSpacing = false;
3848         boolean mHasLetterSpacing = false;
3849         float mLetterSpacing = 0;
3850         String mFontFeatureSettings = null;
3851         String mFontVariationSettings = null;
3852
3853         @Override
3854         public String toString() {
3855             return "TextAppearanceAttributes {\n"
3856                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
3857                     + "    mTextColor:" + mTextColor + "\n"
3858                     + "    mTextColorHint:" + mTextColorHint + "\n"
3859                     + "    mTextColorLink:" + mTextColorLink + "\n"
3860                     + "    mTextSize:" + mTextSize + "\n"
3861                     + "    mTextLocales:" + mTextLocales + "\n"
3862                     + "    mFontFamily:" + mFontFamily + "\n"
3863                     + "    mFontTypeface:" + mFontTypeface + "\n"
3864                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
3865                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
3866                     + "    mTextStyle:" + mTextStyle + "\n"
3867                     + "    mFontWeight:" + mFontWeight + "\n"
3868                     + "    mAllCaps:" + mAllCaps + "\n"
3869                     + "    mShadowColor:" + mShadowColor + "\n"
3870                     + "    mShadowDx:" + mShadowDx + "\n"
3871                     + "    mShadowDy:" + mShadowDy + "\n"
3872                     + "    mShadowRadius:" + mShadowRadius + "\n"
3873                     + "    mHasElegant:" + mHasElegant + "\n"
3874                     + "    mElegant:" + mElegant + "\n"
3875                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
3876                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
3877                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
3878                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
3879                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
3880                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
3881                     + "}";
3882         }
3883     }
3884
3885     // Maps styleable attributes that exist both in TextView style and TextAppearance.
3886     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
3887     static {
3888         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
3889                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
3890         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
3891                 com.android.internal.R.styleable.TextAppearance_textColor);
3892         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
3893                 com.android.internal.R.styleable.TextAppearance_textColorHint);
3894         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
3895                 com.android.internal.R.styleable.TextAppearance_textColorLink);
3896         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
3897                 com.android.internal.R.styleable.TextAppearance_textSize);
3898         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
3899                 com.android.internal.R.styleable.TextAppearance_textLocale);
3900         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
3901                 com.android.internal.R.styleable.TextAppearance_typeface);
3902         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
3903                 com.android.internal.R.styleable.TextAppearance_fontFamily);
3904         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
3905                 com.android.internal.R.styleable.TextAppearance_textStyle);
3906         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
3907                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
3908         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
3909                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
3910         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
3911                 com.android.internal.R.styleable.TextAppearance_shadowColor);
3912         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
3913                 com.android.internal.R.styleable.TextAppearance_shadowDx);
3914         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
3915                 com.android.internal.R.styleable.TextAppearance_shadowDy);
3916         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
3917                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
3918         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
3919                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
3920         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
3921                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
3922         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
3923                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
3924         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
3925                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
3926         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
3927                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
3928     }
3929
3930     /**
3931      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
3932      * set. If the TypedArray contains a value that was already set in the given attributes, that
3933      * will be overridden.
3934      *
3935      * @param context The Context to be used
3936      * @param appearance The TypedArray to read properties from
3937      * @param attributes the TextAppearanceAttributes to fill in
3938      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
3939      *                   what attribute indexes will be used to read the properties.
3940      */
3941     private void readTextAppearance(Context context, TypedArray appearance,
3942             TextAppearanceAttributes attributes, boolean styleArray) {
3943         final int n = appearance.getIndexCount();
3944         for (int i = 0; i < n; i++) {
3945             final int attr = appearance.getIndex(i);
3946             int index = attr;
3947             // Translate style array index ids to TextAppearance ids.
3948             if (styleArray) {
3949                 index = sAppearanceValues.get(attr, -1);
3950                 if (index == -1) {
3951                     // This value is not part of a Text Appearance and should be ignored.
3952                     continue;
3953                 }
3954             }
3955             switch (index) {
3956                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
3957                     attributes.mTextColorHighlight =
3958                             appearance.getColor(attr, attributes.mTextColorHighlight);
3959                     break;
3960                 case com.android.internal.R.styleable.TextAppearance_textColor:
3961                     attributes.mTextColor = appearance.getColorStateList(attr);
3962                     break;
3963                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
3964                     attributes.mTextColorHint = appearance.getColorStateList(attr);
3965                     break;
3966                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
3967                     attributes.mTextColorLink = appearance.getColorStateList(attr);
3968                     break;
3969                 case com.android.internal.R.styleable.TextAppearance_textSize:
3970                     attributes.mTextSize =
3971                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
3972                     break;
3973                 case com.android.internal.R.styleable.TextAppearance_textLocale:
3974                     final String localeString = appearance.getString(attr);
3975                     if (localeString != null) {
3976                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
3977                         if (!localeList.isEmpty()) {
3978                             attributes.mTextLocales = localeList;
3979                         }
3980                     }
3981                     break;
3982                 case com.android.internal.R.styleable.TextAppearance_typeface:
3983                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
3984                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
3985                         attributes.mFontFamily = null;
3986                     }
3987                     break;
3988                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
3989                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
3990                         try {
3991                             attributes.mFontTypeface = appearance.getFont(attr);
3992                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
3993                             // Expected if it is not a font resource.
3994                         }
3995                     }
3996                     if (attributes.mFontTypeface == null) {
3997                         attributes.mFontFamily = appearance.getString(attr);
3998                     }
3999                     attributes.mFontFamilyExplicit = true;
4000                     break;
4001                 case com.android.internal.R.styleable.TextAppearance_textStyle:
4002                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
4003                     break;
4004                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
4005                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
4006                     break;
4007                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
4008                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
4009                     break;
4010                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
4011                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
4012                     break;
4013                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
4014                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
4015                     break;
4016                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
4017                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
4018                     break;
4019                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
4020                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
4021                     break;
4022                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
4023                     attributes.mHasElegant = true;
4024                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
4025                     break;
4026                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
4027                     attributes.mHasFallbackLineSpacing = true;
4028                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
4029                             attributes.mFallbackLineSpacing);
4030                     break;
4031                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
4032                     attributes.mHasLetterSpacing = true;
4033                     attributes.mLetterSpacing =
4034                             appearance.getFloat(attr, attributes.mLetterSpacing);
4035                     break;
4036                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
4037                     attributes.mFontFeatureSettings = appearance.getString(attr);
4038                     break;
4039                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
4040                     attributes.mFontVariationSettings = appearance.getString(attr);
4041                     break;
4042                 default:
4043             }
4044         }
4045     }
4046
4047     private void applyTextAppearance(TextAppearanceAttributes attributes) {
4048         if (attributes.mTextColor != null) {
4049             setTextColor(attributes.mTextColor);
4050         }
4051
4052         if (attributes.mTextColorHint != null) {
4053             setHintTextColor(attributes.mTextColorHint);
4054         }
4055
4056         if (attributes.mTextColorLink != null) {
4057             setLinkTextColor(attributes.mTextColorLink);
4058         }
4059
4060         if (attributes.mTextColorHighlight != 0) {
4061             setHighlightColor(attributes.mTextColorHighlight);
4062         }
4063
4064         if (attributes.mTextSize != -1) {
4065             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
4066         }
4067
4068         if (attributes.mTextLocales != null) {
4069             setTextLocales(attributes.mTextLocales);
4070         }
4071
4072         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
4073             attributes.mFontFamily = null;
4074         }
4075         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
4076                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
4077
4078         if (attributes.mShadowColor != 0) {
4079             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
4080                     attributes.mShadowColor);
4081         }
4082
4083         if (attributes.mAllCaps) {
4084             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
4085         }
4086
4087         if (attributes.mHasElegant) {
4088             setElegantTextHeight(attributes.mElegant);
4089         }
4090
4091         if (attributes.mHasFallbackLineSpacing) {
4092             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
4093         }
4094
4095         if (attributes.mHasLetterSpacing) {
4096             setLetterSpacing(attributes.mLetterSpacing);
4097         }
4098
4099         if (attributes.mFontFeatureSettings != null) {
4100             setFontFeatureSettings(attributes.mFontFeatureSettings);
4101         }
4102
4103         if (attributes.mFontVariationSettings != null) {
4104             setFontVariationSettings(attributes.mFontVariationSettings);
4105         }
4106     }
4107
4108     /**
4109      * Get the default primary {@link Locale} of the text in this TextView. This will always be
4110      * the first member of {@link #getTextLocales()}.
4111      * @return the default primary {@link Locale} of the text in this TextView.
4112      */
4113     @NonNull
4114     public Locale getTextLocale() {
4115         return mTextPaint.getTextLocale();
4116     }
4117
4118     /**
4119      * Get the default {@link LocaleList} of the text in this TextView.
4120      * @return the default {@link LocaleList} of the text in this TextView.
4121      */
4122     @NonNull @Size(min = 1)
4123     public LocaleList getTextLocales() {
4124         return mTextPaint.getTextLocales();
4125     }
4126
4127     private void changeListenerLocaleTo(@Nullable Locale locale) {
4128         if (mListenerChanged) {
4129             // If a listener has been explicitly set, don't change it. We may break something.
4130             return;
4131         }
4132         // The following null check is not absolutely necessary since all calling points of
4133         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
4134         // here in case others would want to call this method in the future.
4135         if (mEditor != null) {
4136             KeyListener listener = mEditor.mKeyListener;
4137             if (listener instanceof DigitsKeyListener) {
4138                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
4139             } else if (listener instanceof DateKeyListener) {
4140                 listener = DateKeyListener.getInstance(locale);
4141             } else if (listener instanceof TimeKeyListener) {
4142                 listener = TimeKeyListener.getInstance(locale);
4143             } else if (listener instanceof DateTimeKeyListener) {
4144                 listener = DateTimeKeyListener.getInstance(locale);
4145             } else {
4146                 return;
4147             }
4148             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
4149             setKeyListenerOnly(listener);
4150             setInputTypeFromEditor();
4151             if (wasPasswordType) {
4152                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
4153                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
4154                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
4155                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
4156                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
4157                 }
4158             }
4159         }
4160     }
4161
4162     /**
4163      * Set the default {@link Locale} of the text in this TextView to a one-member
4164      * {@link LocaleList} containing just the given Locale.
4165      *
4166      * @param locale the {@link Locale} for drawing text, must not be null.
4167      *
4168      * @see #setTextLocales
4169      */
4170     public void setTextLocale(@NonNull Locale locale) {
4171         mLocalesChanged = true;
4172         mTextPaint.setTextLocale(locale);
4173         if (mLayout != null) {
4174             nullLayouts();
4175             requestLayout();
4176             invalidate();
4177         }
4178     }
4179
4180     /**
4181      * Set the default {@link LocaleList} of the text in this TextView to the given value.
4182      *
4183      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
4184      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
4185      * other aspects of text display, including line breaking.
4186      *
4187      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
4188      *
4189      * @see Paint#setTextLocales
4190      */
4191     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
4192         mLocalesChanged = true;
4193         mTextPaint.setTextLocales(locales);
4194         if (mLayout != null) {
4195             nullLayouts();
4196             requestLayout();
4197             invalidate();
4198         }
4199     }
4200
4201     @Override
4202     protected void onConfigurationChanged(Configuration newConfig) {
4203         super.onConfigurationChanged(newConfig);
4204         if (!mLocalesChanged) {
4205             mTextPaint.setTextLocales(LocaleList.getDefault());
4206             if (mLayout != null) {
4207                 nullLayouts();
4208                 requestLayout();
4209                 invalidate();
4210             }
4211         }
4212     }
4213
4214     /**
4215      * @return the size (in pixels) of the default text size in this TextView.
4216      */
4217     @InspectableProperty
4218     @ViewDebug.ExportedProperty(category = "text")
4219     public float getTextSize() {
4220         return mTextPaint.getTextSize();
4221     }
4222
4223     /**
4224      * @return the size (in scaled pixels) of the default text size in this TextView.
4225      * @hide
4226      */
4227     @ViewDebug.ExportedProperty(category = "text")
4228     public float getScaledTextSize() {
4229         return mTextPaint.getTextSize() / mTextPaint.density;
4230     }
4231
4232     /** @hide */
4233     @ViewDebug.ExportedProperty(category = "text", mapping = {
4234             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
4235             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
4236             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
4237             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
4238     })
4239     public int getTypefaceStyle() {
4240         Typeface typeface = mTextPaint.getTypeface();
4241         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
4242     }
4243
4244     /**
4245      * Set the default text size to the given value, interpreted as "scaled
4246      * pixel" units.  This size is adjusted based on the current density and
4247      * user font size preference.
4248      *
4249      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4250      *
4251      * @param size The scaled pixel size.
4252      *
4253      * @attr ref android.R.styleable#TextView_textSize
4254      */
4255     @android.view.RemotableViewMethod
4256     public void setTextSize(float size) {
4257         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
4258     }
4259
4260     /**
4261      * Set the default text size to a given unit and value. See {@link
4262      * TypedValue} for the possible dimension units.
4263      *
4264      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
4265      *
4266      * @param unit The desired dimension unit.
4267      * @param size The desired size in the given units.
4268      *
4269      * @attr ref android.R.styleable#TextView_textSize
4270      */
4271     public void setTextSize(int unit, float size) {
4272         if (!isAutoSizeEnabled()) {
4273             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
4274         }
4275     }
4276
4277     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
4278         Context c = getContext();
4279         Resources r;
4280
4281         if (c == null) {
4282             r = Resources.getSystem();
4283         } else {
4284             r = c.getResources();
4285         }
4286
4287         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
4288                 shouldRequestLayout);
4289     }
4290
4291     @UnsupportedAppUsage
4292     private void setRawTextSize(float size, boolean shouldRequestLayout) {
4293         if (size != mTextPaint.getTextSize()) {
4294             mTextPaint.setTextSize(size);
4295
4296             if (shouldRequestLayout && mLayout != null) {
4297                 // Do not auto-size right after setting the text size.
4298                 mNeedsAutoSizeText = false;
4299                 nullLayouts();
4300                 requestLayout();
4301                 invalidate();
4302             }
4303         }
4304     }
4305
4306     /**
4307      * Gets the extent by which text should be stretched horizontally.
4308      * This will usually be 1.0.
4309      * @return The horizontal scale factor.
4310      */
4311     @InspectableProperty
4312     public float getTextScaleX() {
4313         return mTextPaint.getTextScaleX();
4314     }
4315
4316     /**
4317      * Sets the horizontal scale factor for text. The default value
4318      * is 1.0. Values greater than 1.0 stretch the text wider.
4319      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
4320      * @param size The horizontal scale factor.
4321      * @attr ref android.R.styleable#TextView_textScaleX
4322      */
4323     @android.view.RemotableViewMethod
4324     public void setTextScaleX(float size) {
4325         if (size != mTextPaint.getTextScaleX()) {
4326             mUserSetTextScaleX = true;
4327             mTextPaint.setTextScaleX(size);
4328
4329             if (mLayout != null) {
4330                 nullLayouts();
4331                 requestLayout();
4332                 invalidate();
4333             }
4334         }
4335     }
4336
4337     /**
4338      * Sets the typeface and style in which the text should be displayed.
4339      * Note that not all Typeface families actually have bold and italic
4340      * variants, so you may need to use
4341      * {@link #setTypeface(Typeface, int)} to get the appearance
4342      * that you actually want.
4343      *
4344      * @see #getTypeface()
4345      *
4346      * @attr ref android.R.styleable#TextView_fontFamily
4347      * @attr ref android.R.styleable#TextView_typeface
4348      * @attr ref android.R.styleable#TextView_textStyle
4349      */
4350     public void setTypeface(@Nullable Typeface tf) {
4351         if (mTextPaint.getTypeface() != tf) {
4352             mTextPaint.setTypeface(tf);
4353
4354             if (mLayout != null) {
4355                 nullLayouts();
4356                 requestLayout();
4357                 invalidate();
4358             }
4359         }
4360     }
4361
4362     /**
4363      * Gets the current {@link Typeface} that is used to style the text.
4364      * @return The current Typeface.
4365      *
4366      * @see #setTypeface(Typeface)
4367      *
4368      * @attr ref android.R.styleable#TextView_fontFamily
4369      * @attr ref android.R.styleable#TextView_typeface
4370      * @attr ref android.R.styleable#TextView_textStyle
4371      */
4372     @InspectableProperty
4373     public Typeface getTypeface() {
4374         return mTextPaint.getTypeface();
4375     }
4376
4377     /**
4378      * Set the TextView's elegant height metrics flag. This setting selects font
4379      * variants that have not been compacted to fit Latin-based vertical
4380      * metrics, and also increases top and bottom bounds to provide more space.
4381      *
4382      * @param elegant set the paint's elegant metrics flag.
4383      *
4384      * @see #isElegantTextHeight()
4385      * @see Paint#isElegantTextHeight()
4386      *
4387      * @attr ref android.R.styleable#TextView_elegantTextHeight
4388      */
4389     public void setElegantTextHeight(boolean elegant) {
4390         if (elegant != mTextPaint.isElegantTextHeight()) {
4391             mTextPaint.setElegantTextHeight(elegant);
4392             if (mLayout != null) {
4393                 nullLayouts();
4394                 requestLayout();
4395                 invalidate();
4396             }
4397         }
4398     }
4399
4400     /**
4401      * Set whether to respect the ascent and descent of the fallback fonts that are used in
4402      * displaying the text (which is needed to avoid text from consecutive lines running into
4403      * each other). If set, fallback fonts that end up getting used can increase the ascent
4404      * and descent of the lines that they are used on.
4405      * <p/>
4406      * It is required to be true if text could be in languages like Burmese or Tibetan where text
4407      * is typically much taller or deeper than Latin text.
4408      *
4409      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
4410      *
4411      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
4412      *
4413      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4414      */
4415     public void setFallbackLineSpacing(boolean enabled) {
4416         if (mUseFallbackLineSpacing != enabled) {
4417             mUseFallbackLineSpacing = enabled;
4418             if (mLayout != null) {
4419                 nullLayouts();
4420                 requestLayout();
4421                 invalidate();
4422             }
4423         }
4424     }
4425
4426     /**
4427      * @return whether fallback line spacing is enabled, {@code true} by default
4428      *
4429      * @see #setFallbackLineSpacing(boolean)
4430      *
4431      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
4432      */
4433     @InspectableProperty
4434     public boolean isFallbackLineSpacing() {
4435         return mUseFallbackLineSpacing;
4436     }
4437
4438     /**
4439      * Get the value of the TextView's elegant height metrics flag. This setting selects font
4440      * variants that have not been compacted to fit Latin-based vertical
4441      * metrics, and also increases top and bottom bounds to provide more space.
4442      * @return {@code true} if the elegant height metrics flag is set.
4443      *
4444      * @see #setElegantTextHeight(boolean)
4445      * @see Paint#setElegantTextHeight(boolean)
4446      */
4447     @InspectableProperty
4448     public boolean isElegantTextHeight() {
4449         return mTextPaint.isElegantTextHeight();
4450     }
4451
4452     /**
4453      * Gets the text letter-space value, which determines the spacing between characters.
4454      * The value returned is in ems. Normally, this value is 0.0.
4455      * @return The text letter-space value in ems.
4456      *
4457      * @see #setLetterSpacing(float)
4458      * @see Paint#setLetterSpacing
4459      */
4460     @InspectableProperty
4461     public float getLetterSpacing() {
4462         return mTextPaint.getLetterSpacing();
4463     }
4464
4465     /**
4466      * Sets text letter-spacing in em units.  Typical values
4467      * for slight expansion will be around 0.05.  Negative values tighten text.
4468      *
4469      * @see #getLetterSpacing()
4470      * @see Paint#getLetterSpacing
4471      *
4472      * @param letterSpacing A text letter-space value in ems.
4473      * @attr ref android.R.styleable#TextView_letterSpacing
4474      */
4475     @android.view.RemotableViewMethod
4476     public void setLetterSpacing(float letterSpacing) {
4477         if (letterSpacing != mTextPaint.getLetterSpacing()) {
4478             mTextPaint.setLetterSpacing(letterSpacing);
4479
4480             if (mLayout != null) {
4481                 nullLayouts();
4482                 requestLayout();
4483                 invalidate();
4484             }
4485         }
4486     }
4487
4488     /**
4489      * Returns the font feature settings. The format is the same as the CSS
4490      * font-feature-settings attribute:
4491      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4492      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4493      *
4494      * @return the currently set font feature settings.  Default is null.
4495      *
4496      * @see #setFontFeatureSettings(String)
4497      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
4498      */
4499     @InspectableProperty
4500     @Nullable
4501     public String getFontFeatureSettings() {
4502         return mTextPaint.getFontFeatureSettings();
4503     }
4504
4505     /**
4506      * Returns the font variation settings.
4507      *
4508      * @return the currently set font variation settings.  Returns null if no variation is
4509      * specified.
4510      *
4511      * @see #setFontVariationSettings(String)
4512      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
4513      */
4514     @Nullable
4515     public String getFontVariationSettings() {
4516         return mTextPaint.getFontVariationSettings();
4517     }
4518
4519     /**
4520      * Sets the break strategy for breaking paragraphs into lines. The default value for
4521      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
4522      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
4523      * text "dancing" when being edited.
4524      * <p/>
4525      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4526      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4527      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4528      * improves the structure of text layout however has performance impact and requires more time
4529      * to do the text layout.
4530      *
4531      * @attr ref android.R.styleable#TextView_breakStrategy
4532      * @see #getBreakStrategy()
4533      * @see #setHyphenationFrequency(int)
4534      */
4535     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
4536         mBreakStrategy = breakStrategy;
4537         if (mLayout != null) {
4538             nullLayouts();
4539             requestLayout();
4540             invalidate();
4541         }
4542     }
4543
4544     /**
4545      * Gets the current strategy for breaking paragraphs into lines.
4546      * @return the current strategy for breaking paragraphs into lines.
4547      *
4548      * @attr ref android.R.styleable#TextView_breakStrategy
4549      * @see #setBreakStrategy(int)
4550      */
4551     @InspectableProperty(enumMapping = {
4552             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
4553             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
4554             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
4555     })
4556     @Layout.BreakStrategy
4557     public int getBreakStrategy() {
4558         return mBreakStrategy;
4559     }
4560
4561     /**
4562      * Sets the frequency of automatic hyphenation to use when determining word breaks.
4563      * The default value for both TextView and {@link EditText} is
4564      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
4565      * is set from the theme.
4566      * <p/>
4567      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
4568      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
4569      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
4570      * improves the structure of text layout however has performance impact and requires more time
4571      * to do the text layout.
4572      * <p/>
4573      * Note: Before Android Q, in the theme hyphenation frequency is set to
4574      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
4575      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
4576      *
4577      * @param hyphenationFrequency the hyphenation frequency to use, one of
4578      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
4579      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
4580      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
4581      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4582      * @see #getHyphenationFrequency()
4583      * @see #getBreakStrategy()
4584      */
4585     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
4586         mHyphenationFrequency = hyphenationFrequency;
4587         if (mLayout != null) {
4588             nullLayouts();
4589             requestLayout();
4590             invalidate();
4591         }
4592     }
4593
4594     /**
4595      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
4596      * @return the current frequency of automatic hyphenation to be used when determining word
4597      * breaks.
4598      *
4599      * @attr ref android.R.styleable#TextView_hyphenationFrequency
4600      * @see #setHyphenationFrequency(int)
4601      */
4602     @InspectableProperty(enumMapping = {
4603             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
4604             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
4605             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
4606     })
4607     @Layout.HyphenationFrequency
4608     public int getHyphenationFrequency() {
4609         return mHyphenationFrequency;
4610     }
4611
4612     /**
4613      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
4614      *
4615      * @return a current {@link PrecomputedText.Params}
4616      * @see PrecomputedText
4617      */
4618     public @NonNull PrecomputedText.Params getTextMetricsParams() {
4619         return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
4620                 mBreakStrategy, mHyphenationFrequency);
4621     }
4622
4623     /**
4624      * Apply the text layout parameter.
4625      *
4626      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
4627      * @see PrecomputedText
4628      */
4629     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
4630         mTextPaint.set(params.getTextPaint());
4631         mUserSetTextScaleX = true;
4632         mTextDir = params.getTextDirection();
4633         mBreakStrategy = params.getBreakStrategy();
4634         mHyphenationFrequency = params.getHyphenationFrequency();
4635         if (mLayout != null) {
4636             nullLayouts();
4637             requestLayout();
4638             invalidate();
4639         }
4640     }
4641
4642     /**
4643      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
4644      * last line is too short for justification, the last line will be displayed with the
4645      * alignment set by {@link android.view.View#setTextAlignment}.
4646      *
4647      * @see #getJustificationMode()
4648      */
4649     @Layout.JustificationMode
4650     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
4651         mJustificationMode = justificationMode;
4652         if (mLayout != null) {
4653             nullLayouts();
4654             requestLayout();
4655             invalidate();
4656         }
4657     }
4658
4659     /**
4660      * @return true if currently paragraph justification mode.
4661      *
4662      * @see #setJustificationMode(int)
4663      */
4664     @InspectableProperty(enumMapping = {
4665             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
4666             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
4667     })
4668     public @Layout.JustificationMode int getJustificationMode() {
4669         return mJustificationMode;
4670     }
4671
4672     /**
4673      * Sets font feature settings. The format is the same as the CSS
4674      * font-feature-settings attribute:
4675      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
4676      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
4677      *
4678      * @param fontFeatureSettings font feature settings represented as CSS compatible string
4679      *
4680      * @see #getFontFeatureSettings()
4681      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
4682      *
4683      * @attr ref android.R.styleable#TextView_fontFeatureSettings
4684      */
4685     @android.view.RemotableViewMethod
4686     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
4687         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
4688             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
4689
4690             if (mLayout != null) {
4691                 nullLayouts();
4692                 requestLayout();
4693                 invalidate();
4694             }
4695         }
4696     }
4697
4698
4699     /**
4700      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
4701      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
4702      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
4703      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
4704      * are invalid. If a specified axis name is not defined in the font, the settings will be
4705      * ignored.
4706      *
4707      * <p>
4708      * Examples,
4709      * <ul>
4710      * <li>Set font width to 150.
4711      * <pre>
4712      * <code>
4713      *   TextView textView = (TextView) findViewById(R.id.textView);
4714      *   textView.setFontVariationSettings("'wdth' 150");
4715      * </code>
4716      * </pre>
4717      * </li>
4718      *
4719      * <li>Set the font slant to 20 degrees and ask for italic style.
4720      * <pre>
4721      * <code>
4722      *   TextView textView = (TextView) findViewById(R.id.textView);
4723      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
4724      * </code>
4725      * </pre>
4726      * </p>
4727      * </li>
4728      * </ul>
4729      *
4730      * @param fontVariationSettings font variation settings. You can pass null or empty string as
4731      *                              no variation settings.
4732      * @return true if the given settings is effective to at least one font file underlying this
4733      *         TextView. This function also returns true for empty settings string. Otherwise
4734      *         returns false.
4735      *
4736      * @throws IllegalArgumentException If given string is not a valid font variation settings
4737      *                                  format.
4738      *
4739      * @see #getFontVariationSettings()
4740      * @see FontVariationAxis
4741      *
4742      * @attr ref android.R.styleable#TextView_fontVariationSettings
4743      */
4744     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
4745         final String existingSettings = mTextPaint.getFontVariationSettings();
4746         if (fontVariationSettings == existingSettings
4747                 || (fontVariationSettings != null
4748                         && fontVariationSettings.equals(existingSettings))) {
4749             return true;
4750         }
4751         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
4752
4753         if (effective && mLayout != null) {
4754             nullLayouts();
4755             requestLayout();
4756             invalidate();
4757         }
4758         return effective;
4759     }
4760
4761     /**
4762      * Sets the text color for all the states (normal, selected,
4763      * focused) to be this color.
4764      *
4765      * @param color A color value in the form 0xAARRGGBB.
4766      * Do not pass a resource ID. To get a color value from a resource ID, call
4767      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
4768      *
4769      * @see #setTextColor(ColorStateList)
4770      * @see #getTextColors()
4771      *
4772      * @attr ref android.R.styleable#TextView_textColor
4773      */
4774     @android.view.RemotableViewMethod
4775     public void setTextColor(@ColorInt int color) {
4776         mTextColor = ColorStateList.valueOf(color);
4777         updateTextColors();
4778     }
4779
4780     /**
4781      * Sets the text color.
4782      *
4783      * @see #setTextColor(int)
4784      * @see #getTextColors()
4785      * @see #setHintTextColor(ColorStateList)
4786      * @see #setLinkTextColor(ColorStateList)
4787      *
4788      * @attr ref android.R.styleable#TextView_textColor
4789      */
4790     @android.view.RemotableViewMethod
4791     public void setTextColor(ColorStateList colors) {
4792         if (colors == null) {
4793             throw new NullPointerException();
4794         }
4795
4796         mTextColor = colors;
4797         updateTextColors();
4798     }
4799
4800     /**
4801      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
4802      *
4803      * @see #setTextColor(ColorStateList)
4804      * @see #setTextColor(int)
4805      *
4806      * @attr ref android.R.styleable#TextView_textColor
4807      */
4808     @InspectableProperty(name = "textColor")
4809     public final ColorStateList getTextColors() {
4810         return mTextColor;
4811     }
4812
4813     /**
4814      * Return the current color selected for normal text.
4815      *
4816      * @return Returns the current text color.
4817      */
4818     @ColorInt
4819     public final int getCurrentTextColor() {
4820         return mCurTextColor;
4821     }
4822
4823     /**
4824      * Sets the color used to display the selection highlight.
4825      *
4826      * @attr ref android.R.styleable#TextView_textColorHighlight
4827      */
4828     @android.view.RemotableViewMethod
4829     public void setHighlightColor(@ColorInt int color) {
4830         if (mHighlightColor != color) {
4831             mHighlightColor = color;
4832             invalidate();
4833         }
4834     }
4835
4836     /**
4837      * @return the color used to display the selection highlight
4838      *
4839      * @see #setHighlightColor(int)
4840      *
4841      * @attr ref android.R.styleable#TextView_textColorHighlight
4842      */
4843     @InspectableProperty(name = "textColorHighlight")
4844     @ColorInt
4845     public int getHighlightColor() {
4846         return mHighlightColor;
4847     }
4848
4849     /**
4850      * Sets whether the soft input method will be made visible when this
4851      * TextView gets focused. The default is true.
4852      */
4853     @android.view.RemotableViewMethod
4854     public final void setShowSoftInputOnFocus(boolean show) {
4855         createEditorIfNeeded();
4856         mEditor.mShowSoftInputOnFocus = show;
4857     }
4858
4859     /**
4860      * Returns whether the soft input method will be made visible when this
4861      * TextView gets focused. The default is true.
4862      */
4863     public final boolean getShowSoftInputOnFocus() {
4864         // When there is no Editor, return default true value
4865         return mEditor == null || mEditor.mShowSoftInputOnFocus;
4866     }
4867
4868     /**
4869      * Gives the text a shadow of the specified blur radius and color, the specified
4870      * distance from its drawn position.
4871      * <p>
4872      * The text shadow produced does not interact with the properties on view
4873      * that are responsible for real time shadows,
4874      * {@link View#getElevation() elevation} and
4875      * {@link View#getTranslationZ() translationZ}.
4876      *
4877      * @see Paint#setShadowLayer(float, float, float, int)
4878      *
4879      * @attr ref android.R.styleable#TextView_shadowColor
4880      * @attr ref android.R.styleable#TextView_shadowDx
4881      * @attr ref android.R.styleable#TextView_shadowDy
4882      * @attr ref android.R.styleable#TextView_shadowRadius
4883      */
4884     public void setShadowLayer(float radius, float dx, float dy, int color) {
4885         mTextPaint.setShadowLayer(radius, dx, dy, color);
4886
4887         mShadowRadius = radius;
4888         mShadowDx = dx;
4889         mShadowDy = dy;
4890         mShadowColor = color;
4891
4892         // Will change text clip region
4893         if (mEditor != null) {
4894             mEditor.invalidateTextDisplayList();
4895             mEditor.invalidateHandlesAndActionMode();
4896         }
4897         invalidate();
4898     }
4899
4900     /**
4901      * Gets the radius of the shadow layer.
4902      *
4903      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
4904      *
4905      * @see #setShadowLayer(float, float, float, int)
4906      *
4907      * @attr ref android.R.styleable#TextView_shadowRadius
4908      */
4909     @InspectableProperty
4910     public float getShadowRadius() {
4911         return mShadowRadius;
4912     }
4913
4914     /**
4915      * @return the horizontal offset of the shadow layer
4916      *
4917      * @see #setShadowLayer(float, float, float, int)
4918      *
4919      * @attr ref android.R.styleable#TextView_shadowDx
4920      */
4921     @InspectableProperty
4922     public float getShadowDx() {
4923         return mShadowDx;
4924     }
4925
4926     /**
4927      * Gets the vertical offset of the shadow layer.
4928      * @return The vertical offset of the shadow layer.
4929      *
4930      * @see #setShadowLayer(float, float, float, int)
4931      *
4932      * @attr ref android.R.styleable#TextView_shadowDy
4933      */
4934     @InspectableProperty
4935     public float getShadowDy() {
4936         return mShadowDy;
4937     }
4938
4939     /**
4940      * Gets the color of the shadow layer.
4941      * @return the color of the shadow layer
4942      *
4943      * @see #setShadowLayer(float, float, float, int)
4944      *
4945      * @attr ref android.R.styleable#TextView_shadowColor
4946      */
4947     @InspectableProperty
4948     @ColorInt
4949     public int getShadowColor() {
4950         return mShadowColor;
4951     }
4952
4953     /**
4954      * Gets the {@link TextPaint} used for the text.
4955      * Use this only to consult the Paint's properties and not to change them.
4956      * @return The base paint used for the text.
4957      */
4958     public TextPaint getPaint() {
4959         return mTextPaint;
4960     }
4961
4962     /**
4963      * Sets the autolink mask of the text.  See {@link
4964      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
4965      * possible values.
4966      *
4967      * <p class="note"><b>Note:</b>
4968      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
4969      * is deprecated and should be avoided; see its documentation.
4970      *
4971      * @attr ref android.R.styleable#TextView_autoLink
4972      */
4973     @android.view.RemotableViewMethod
4974     public final void setAutoLinkMask(int mask) {
4975         mAutoLinkMask = mask;
4976     }
4977
4978     /**
4979      * Sets whether the movement method will automatically be set to
4980      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4981      * set to nonzero and links are detected in {@link #setText}.
4982      * The default is true.
4983      *
4984      * @attr ref android.R.styleable#TextView_linksClickable
4985      */
4986     @android.view.RemotableViewMethod
4987     public final void setLinksClickable(boolean whether) {
4988         mLinksClickable = whether;
4989     }
4990
4991     /**
4992      * Returns whether the movement method will automatically be set to
4993      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4994      * set to nonzero and links are detected in {@link #setText}.
4995      * The default is true.
4996      *
4997      * @attr ref android.R.styleable#TextView_linksClickable
4998      */
4999     @InspectableProperty
5000     public final boolean getLinksClickable() {
5001         return mLinksClickable;
5002     }
5003
5004     /**
5005      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
5006      * (by {@link Linkify} or otherwise) if any.  You can call
5007      * {@link URLSpan#getURL} on them to find where they link to
5008      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
5009      * to find the region of the text they are attached to.
5010      */
5011     public URLSpan[] getUrls() {
5012         if (mText instanceof Spanned) {
5013             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
5014         } else {
5015             return new URLSpan[0];
5016         }
5017     }
5018
5019     /**
5020      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
5021      * TextView.
5022      *
5023      * @see #setHintTextColor(ColorStateList)
5024      * @see #getHintTextColors()
5025      * @see #setTextColor(int)
5026      *
5027      * @attr ref android.R.styleable#TextView_textColorHint
5028      */
5029     @android.view.RemotableViewMethod
5030     public final void setHintTextColor(@ColorInt int color) {
5031         mHintTextColor = ColorStateList.valueOf(color);
5032         updateTextColors();
5033     }
5034
5035     /**
5036      * Sets the color of the hint text.
5037      *
5038      * @see #getHintTextColors()
5039      * @see #setHintTextColor(int)
5040      * @see #setTextColor(ColorStateList)
5041      * @see #setLinkTextColor(ColorStateList)
5042      *
5043      * @attr ref android.R.styleable#TextView_textColorHint
5044      */
5045     public final void setHintTextColor(ColorStateList colors) {
5046         mHintTextColor = colors;
5047         updateTextColors();
5048     }
5049
5050     /**
5051      * @return the color of the hint text, for the different states of this TextView.
5052      *
5053      * @see #setHintTextColor(ColorStateList)
5054      * @see #setHintTextColor(int)
5055      * @see #setTextColor(ColorStateList)
5056      * @see #setLinkTextColor(ColorStateList)
5057      *
5058      * @attr ref android.R.styleable#TextView_textColorHint
5059      */
5060     @InspectableProperty(name = "textColorHint")
5061     public final ColorStateList getHintTextColors() {
5062         return mHintTextColor;
5063     }
5064
5065     /**
5066      * <p>Return the current color selected to paint the hint text.</p>
5067      *
5068      * @return Returns the current hint text color.
5069      */
5070     @ColorInt
5071     public final int getCurrentHintTextColor() {
5072         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
5073     }
5074
5075     /**
5076      * Sets the color of links in the text.
5077      *
5078      * @see #setLinkTextColor(ColorStateList)
5079      * @see #getLinkTextColors()
5080      *
5081      * @attr ref android.R.styleable#TextView_textColorLink
5082      */
5083     @android.view.RemotableViewMethod
5084     public final void setLinkTextColor(@ColorInt int color) {
5085         mLinkTextColor = ColorStateList.valueOf(color);
5086         updateTextColors();
5087     }
5088
5089     /**
5090      * Sets the color of links in the text.
5091      *
5092      * @see #setLinkTextColor(int)
5093      * @see #getLinkTextColors()
5094      * @see #setTextColor(ColorStateList)
5095      * @see #setHintTextColor(ColorStateList)
5096      *
5097      * @attr ref android.R.styleable#TextView_textColorLink
5098      */
5099     public final void setLinkTextColor(ColorStateList colors) {
5100         mLinkTextColor = colors;
5101         updateTextColors();
5102     }
5103
5104     /**
5105      * @return the list of colors used to paint the links in the text, for the different states of
5106      * this TextView
5107      *
5108      * @see #setLinkTextColor(ColorStateList)
5109      * @see #setLinkTextColor(int)
5110      *
5111      * @attr ref android.R.styleable#TextView_textColorLink
5112      */
5113     @InspectableProperty(name = "textColorLink")
5114     public final ColorStateList getLinkTextColors() {
5115         return mLinkTextColor;
5116     }
5117
5118     /**
5119      * Sets the horizontal alignment of the text and the
5120      * vertical gravity that will be used when there is extra space
5121      * in the TextView beyond what is required for the text itself.
5122      *
5123      * @see android.view.Gravity
5124      * @attr ref android.R.styleable#TextView_gravity
5125      */
5126     public void setGravity(int gravity) {
5127         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
5128             gravity |= Gravity.START;
5129         }
5130         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
5131             gravity |= Gravity.TOP;
5132         }
5133
5134         boolean newLayout = false;
5135
5136         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
5137                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
5138             newLayout = true;
5139         }
5140
5141         if (gravity != mGravity) {
5142             invalidate();
5143         }
5144
5145         mGravity = gravity;
5146
5147         if (mLayout != null && newLayout) {
5148             // XXX this is heavy-handed because no actual content changes.
5149             int want = mLayout.getWidth();
5150             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5151
5152             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5153                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
5154         }
5155     }
5156
5157     /**
5158      * Returns the horizontal and vertical alignment of this TextView.
5159      *
5160      * @see android.view.Gravity
5161      * @attr ref android.R.styleable#TextView_gravity
5162      */
5163     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
5164     public int getGravity() {
5165         return mGravity;
5166     }
5167
5168     /**
5169      * Gets the flags on the Paint being used to display the text.
5170      * @return The flags on the Paint being used to display the text.
5171      * @see Paint#getFlags
5172      */
5173     public int getPaintFlags() {
5174         return mTextPaint.getFlags();
5175     }
5176
5177     /**
5178      * Sets flags on the Paint being used to display the text and
5179      * reflows the text if they are different from the old flags.
5180      * @see Paint#setFlags
5181      */
5182     @android.view.RemotableViewMethod
5183     public void setPaintFlags(int flags) {
5184         if (mTextPaint.getFlags() != flags) {
5185             mTextPaint.setFlags(flags);
5186
5187             if (mLayout != null) {
5188                 nullLayouts();
5189                 requestLayout();
5190                 invalidate();
5191             }
5192         }
5193     }
5194
5195     /**
5196      * Sets whether the text should be allowed to be wider than the
5197      * View is.  If false, it will be wrapped to the width of the View.
5198      *
5199      * @attr ref android.R.styleable#TextView_scrollHorizontally
5200      */
5201     public void setHorizontallyScrolling(boolean whether) {
5202         if (mHorizontallyScrolling != whether) {
5203             mHorizontallyScrolling = whether;
5204
5205             if (mLayout != null) {
5206                 nullLayouts();
5207                 requestLayout();
5208                 invalidate();
5209             }
5210         }
5211     }
5212
5213     /**
5214      * Returns whether the text is allowed to be wider than the View.
5215      * If false, the text will be wrapped to the width of the View.
5216      *
5217      * @attr ref android.R.styleable#TextView_scrollHorizontally
5218      * @see #setHorizontallyScrolling(boolean)
5219      */
5220     @InspectableProperty(name = "scrollHorizontally")
5221     public final boolean isHorizontallyScrollable() {
5222         return mHorizontallyScrolling;
5223     }
5224
5225     /**
5226      * Returns whether the text is allowed to be wider than the View.
5227      * If false, the text will be wrapped to the width of the View.
5228      *
5229      * @attr ref android.R.styleable#TextView_scrollHorizontally
5230      * @hide
5231      */
5232     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
5233     public boolean getHorizontallyScrolling() {
5234         return mHorizontallyScrolling;
5235     }
5236
5237     /**
5238      * Sets the height of the TextView to be at least {@code minLines} tall.
5239      * <p>
5240      * This value is used for height calculation if LayoutParams does not force TextView to have an
5241      * exact height. Setting this value overrides other previous minimum height configurations such
5242      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
5243      * this value to 1.
5244      *
5245      * @param minLines the minimum height of TextView in terms of number of lines
5246      *
5247      * @see #getMinLines()
5248      * @see #setLines(int)
5249      *
5250      * @attr ref android.R.styleable#TextView_minLines
5251      */
5252     @android.view.RemotableViewMethod
5253     public void setMinLines(int minLines) {
5254         mMinimum = minLines;
5255         mMinMode = LINES;
5256
5257         requestLayout();
5258         invalidate();
5259     }
5260
5261     /**
5262      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
5263      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
5264      *
5265      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
5266      *         height is not defined in lines
5267      *
5268      * @see #setMinLines(int)
5269      * @see #setLines(int)
5270      *
5271      * @attr ref android.R.styleable#TextView_minLines
5272      */
5273     @InspectableProperty
5274     public int getMinLines() {
5275         return mMinMode == LINES ? mMinimum : -1;
5276     }
5277
5278     /**
5279      * Sets the height of the TextView to be at least {@code minPixels} tall.
5280      * <p>
5281      * This value is used for height calculation if LayoutParams does not force TextView to have an
5282      * exact height. Setting this value overrides previous minimum height configurations such as
5283      * {@link #setMinLines(int)} or {@link #setLines(int)}.
5284      * <p>
5285      * The value given here is different than {@link #setMinimumHeight(int)}. Between
5286      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
5287      * used to decide the final height.
5288      *
5289      * @param minPixels the minimum height of TextView in terms of pixels
5290      *
5291      * @see #getMinHeight()
5292      * @see #setHeight(int)
5293      *
5294      * @attr ref android.R.styleable#TextView_minHeight
5295      */
5296     @android.view.RemotableViewMethod
5297     public void setMinHeight(int minPixels) {
5298         mMinimum = minPixels;
5299         mMinMode = PIXELS;
5300
5301         requestLayout();
5302         invalidate();
5303     }
5304
5305     /**
5306      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
5307      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
5308      *
5309      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
5310      *         defined in pixels
5311      *
5312      * @see #setMinHeight(int)
5313      * @see #setHeight(int)
5314      *
5315      * @attr ref android.R.styleable#TextView_minHeight
5316      */
5317     public int getMinHeight() {
5318         return mMinMode == PIXELS ? mMinimum : -1;
5319     }
5320
5321     /**
5322      * Sets the height of the TextView to be at most {@code maxLines} tall.
5323      * <p>
5324      * This value is used for height calculation if LayoutParams does not force TextView to have an
5325      * exact height. Setting this value overrides previous maximum height configurations such as
5326      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
5327      *
5328      * @param maxLines the maximum height of TextView in terms of number of lines
5329      *
5330      * @see #getMaxLines()
5331      * @see #setLines(int)
5332      *
5333      * @attr ref android.R.styleable#TextView_maxLines
5334      */
5335     @android.view.RemotableViewMethod
5336     public void setMaxLines(int maxLines) {
5337         mMaximum = maxLines;
5338         mMaxMode = LINES;
5339
5340         requestLayout();
5341         invalidate();
5342     }
5343
5344     /**
5345      * Returns the maximum height of TextView in terms of number of lines or -1 if the
5346      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
5347      *
5348      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
5349      *         is not defined in lines.
5350      *
5351      * @see #setMaxLines(int)
5352      * @see #setLines(int)
5353      *
5354      * @attr ref android.R.styleable#TextView_maxLines
5355      */
5356     @InspectableProperty
5357     public int getMaxLines() {
5358         return mMaxMode == LINES ? mMaximum : -1;
5359     }
5360
5361     /**
5362      * Sets the height of the TextView to be at most {@code maxPixels} tall.
5363      * <p>
5364      * This value is used for height calculation if LayoutParams does not force TextView to have an
5365      * exact height. Setting this value overrides previous maximum height configurations such as
5366      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
5367      *
5368      * @param maxPixels the maximum height of TextView in terms of pixels
5369      *
5370      * @see #getMaxHeight()
5371      * @see #setHeight(int)
5372      *
5373      * @attr ref android.R.styleable#TextView_maxHeight
5374      */
5375     @android.view.RemotableViewMethod
5376     public void setMaxHeight(int maxPixels) {
5377         mMaximum = maxPixels;
5378         mMaxMode = PIXELS;
5379
5380         requestLayout();
5381         invalidate();
5382     }
5383
5384     /**
5385      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
5386      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
5387      *
5388      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
5389      *         is not defined in pixels
5390      *
5391      * @see #setMaxHeight(int)
5392      * @see #setHeight(int)
5393      *
5394      * @attr ref android.R.styleable#TextView_maxHeight
5395      */
5396     @InspectableProperty
5397     public int getMaxHeight() {
5398         return mMaxMode == PIXELS ? mMaximum : -1;
5399     }
5400
5401     /**
5402      * Sets the height of the TextView to be exactly {@code lines} tall.
5403      * <p>
5404      * This value is used for height calculation if LayoutParams does not force TextView to have an
5405      * exact height. Setting this value overrides previous minimum/maximum height configurations
5406      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
5407      * set this value to 1.
5408      *
5409      * @param lines the exact height of the TextView in terms of lines
5410      *
5411      * @see #setHeight(int)
5412      *
5413      * @attr ref android.R.styleable#TextView_lines
5414      */
5415     @android.view.RemotableViewMethod
5416     public void setLines(int lines) {
5417         mMaximum = mMinimum = lines;
5418         mMaxMode = mMinMode = LINES;
5419
5420         requestLayout();
5421         invalidate();
5422     }
5423
5424     /**
5425      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
5426      * <p>
5427      * This value is used for height calculation if LayoutParams does not force TextView to have an
5428      * exact height. Setting this value overrides previous minimum/maximum height configurations
5429      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
5430      *
5431      * @param pixels the exact height of the TextView in terms of pixels
5432      *
5433      * @see #setLines(int)
5434      *
5435      * @attr ref android.R.styleable#TextView_height
5436      */
5437     @android.view.RemotableViewMethod
5438     public void setHeight(int pixels) {
5439         mMaximum = mMinimum = pixels;
5440         mMaxMode = mMinMode = PIXELS;
5441
5442         requestLayout();
5443         invalidate();
5444     }
5445
5446     /**
5447      * Sets the width of the TextView to be at least {@code minEms} wide.
5448      * <p>
5449      * This value is used for width calculation if LayoutParams does not force TextView to have an
5450      * exact width. Setting this value overrides previous minimum width configurations such as
5451      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5452      *
5453      * @param minEms the minimum width of TextView in terms of ems
5454      *
5455      * @see #getMinEms()
5456      * @see #setEms(int)
5457      *
5458      * @attr ref android.R.styleable#TextView_minEms
5459      */
5460     @android.view.RemotableViewMethod
5461     public void setMinEms(int minEms) {
5462         mMinWidth = minEms;
5463         mMinWidthMode = EMS;
5464
5465         requestLayout();
5466         invalidate();
5467     }
5468
5469     /**
5470      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
5471      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
5472      *
5473      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
5474      *         defined in ems
5475      *
5476      * @see #setMinEms(int)
5477      * @see #setEms(int)
5478      *
5479      * @attr ref android.R.styleable#TextView_minEms
5480      */
5481     @InspectableProperty
5482     public int getMinEms() {
5483         return mMinWidthMode == EMS ? mMinWidth : -1;
5484     }
5485
5486     /**
5487      * Sets the width of the TextView to be at least {@code minPixels} wide.
5488      * <p>
5489      * This value is used for width calculation if LayoutParams does not force TextView to have an
5490      * exact width. Setting this value overrides previous minimum width configurations such as
5491      * {@link #setMinEms(int)} or {@link #setEms(int)}.
5492      * <p>
5493      * The value given here is different than {@link #setMinimumWidth(int)}. Between
5494      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
5495      * to decide the final width.
5496      *
5497      * @param minPixels the minimum width of TextView in terms of pixels
5498      *
5499      * @see #getMinWidth()
5500      * @see #setWidth(int)
5501      *
5502      * @attr ref android.R.styleable#TextView_minWidth
5503      */
5504     @android.view.RemotableViewMethod
5505     public void setMinWidth(int minPixels) {
5506         mMinWidth = minPixels;
5507         mMinWidthMode = PIXELS;
5508
5509         requestLayout();
5510         invalidate();
5511     }
5512
5513     /**
5514      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
5515      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
5516      *
5517      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
5518      *         defined in pixels
5519      *
5520      * @see #setMinWidth(int)
5521      * @see #setWidth(int)
5522      *
5523      * @attr ref android.R.styleable#TextView_minWidth
5524      */
5525     @InspectableProperty
5526     public int getMinWidth() {
5527         return mMinWidthMode == PIXELS ? mMinWidth : -1;
5528     }
5529
5530     /**
5531      * Sets the width of the TextView to be at most {@code maxEms} wide.
5532      * <p>
5533      * This value is used for width calculation if LayoutParams does not force TextView to have an
5534      * exact width. Setting this value overrides previous maximum width configurations such as
5535      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5536      *
5537      * @param maxEms the maximum width of TextView in terms of ems
5538      *
5539      * @see #getMaxEms()
5540      * @see #setEms(int)
5541      *
5542      * @attr ref android.R.styleable#TextView_maxEms
5543      */
5544     @android.view.RemotableViewMethod
5545     public void setMaxEms(int maxEms) {
5546         mMaxWidth = maxEms;
5547         mMaxWidthMode = EMS;
5548
5549         requestLayout();
5550         invalidate();
5551     }
5552
5553     /**
5554      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
5555      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
5556      *
5557      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
5558      *         defined in ems
5559      *
5560      * @see #setMaxEms(int)
5561      * @see #setEms(int)
5562      *
5563      * @attr ref android.R.styleable#TextView_maxEms
5564      */
5565     @InspectableProperty
5566     public int getMaxEms() {
5567         return mMaxWidthMode == EMS ? mMaxWidth : -1;
5568     }
5569
5570     /**
5571      * Sets the width of the TextView to be at most {@code maxPixels} wide.
5572      * <p>
5573      * This value is used for width calculation if LayoutParams does not force TextView to have an
5574      * exact width. Setting this value overrides previous maximum width configurations such as
5575      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
5576      *
5577      * @param maxPixels the maximum width of TextView in terms of pixels
5578      *
5579      * @see #getMaxWidth()
5580      * @see #setWidth(int)
5581      *
5582      * @attr ref android.R.styleable#TextView_maxWidth
5583      */
5584     @android.view.RemotableViewMethod
5585     public void setMaxWidth(int maxPixels) {
5586         mMaxWidth = maxPixels;
5587         mMaxWidthMode = PIXELS;
5588
5589         requestLayout();
5590         invalidate();
5591     }
5592
5593     /**
5594      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
5595      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
5596      *
5597      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
5598      *         defined in pixels
5599      *
5600      * @see #setMaxWidth(int)
5601      * @see #setWidth(int)
5602      *
5603      * @attr ref android.R.styleable#TextView_maxWidth
5604      */
5605     @InspectableProperty
5606     public int getMaxWidth() {
5607         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
5608     }
5609
5610     /**
5611      * Sets the width of the TextView to be exactly {@code ems} wide.
5612      *
5613      * This value is used for width calculation if LayoutParams does not force TextView to have an
5614      * exact width. Setting this value overrides previous minimum/maximum configurations such as
5615      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
5616      *
5617      * @param ems the exact width of the TextView in terms of ems
5618      *
5619      * @see #setWidth(int)
5620      *
5621      * @attr ref android.R.styleable#TextView_ems
5622      */
5623     @android.view.RemotableViewMethod
5624     public void setEms(int ems) {
5625         mMaxWidth = mMinWidth = ems;
5626         mMaxWidthMode = mMinWidthMode = EMS;
5627
5628         requestLayout();
5629         invalidate();
5630     }
5631
5632     /**
5633      * Sets the width of the TextView to be exactly {@code pixels} wide.
5634      * <p>
5635      * This value is used for width calculation if LayoutParams does not force TextView to have an
5636      * exact width. Setting this value overrides previous minimum/maximum width configurations
5637      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
5638      *
5639      * @param pixels the exact width of the TextView in terms of pixels
5640      *
5641      * @see #setEms(int)
5642      *
5643      * @attr ref android.R.styleable#TextView_width
5644      */
5645     @android.view.RemotableViewMethod
5646     public void setWidth(int pixels) {
5647         mMaxWidth = mMinWidth = pixels;
5648         mMaxWidthMode = mMinWidthMode = PIXELS;
5649
5650         requestLayout();
5651         invalidate();
5652     }
5653
5654     /**
5655      * Sets line spacing for this TextView.  Each line other than the last line will have its height
5656      * multiplied by {@code mult} and have {@code add} added to it.
5657      *
5658      * @param add The value in pixels that should be added to each line other than the last line.
5659      *            This will be applied after the multiplier
5660      * @param mult The value by which each line height other than the last line will be multiplied
5661      *             by
5662      *
5663      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5664      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5665      */
5666     public void setLineSpacing(float add, float mult) {
5667         if (mSpacingAdd != add || mSpacingMult != mult) {
5668             mSpacingAdd = add;
5669             mSpacingMult = mult;
5670
5671             if (mLayout != null) {
5672                 nullLayouts();
5673                 requestLayout();
5674                 invalidate();
5675             }
5676         }
5677     }
5678
5679     /**
5680      * Gets the line spacing multiplier
5681      *
5682      * @return the value by which each line's height is multiplied to get its actual height.
5683      *
5684      * @see #setLineSpacing(float, float)
5685      * @see #getLineSpacingExtra()
5686      *
5687      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
5688      */
5689     @InspectableProperty
5690     public float getLineSpacingMultiplier() {
5691         return mSpacingMult;
5692     }
5693
5694     /**
5695      * Gets the line spacing extra space
5696      *
5697      * @return the extra space that is added to the height of each lines of this TextView.
5698      *
5699      * @see #setLineSpacing(float, float)
5700      * @see #getLineSpacingMultiplier()
5701      *
5702      * @attr ref android.R.styleable#TextView_lineSpacingExtra
5703      */
5704     @InspectableProperty
5705     public float getLineSpacingExtra() {
5706         return mSpacingAdd;
5707     }
5708
5709     /**
5710      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
5711      * between subsequent baselines in the TextView.
5712      *
5713      * @param lineHeight the line height in pixels
5714      *
5715      * @see #setLineSpacing(float, float)
5716      * @see #getLineSpacingExtra()
5717      *
5718      * @attr ref android.R.styleable#TextView_lineHeight
5719      */
5720     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
5721         Preconditions.checkArgumentNonnegative(lineHeight);
5722
5723         final int fontHeight = getPaint().getFontMetricsInt(null);
5724         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
5725         if (lineHeight != fontHeight) {
5726             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
5727             setLineSpacing(lineHeight - fontHeight, 1f);
5728         }
5729     }
5730
5731     /**
5732      * Convenience method to append the specified text to the TextView's
5733      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5734      * if it was not already editable.
5735      *
5736      * @param text text to be appended to the already displayed text
5737      */
5738     public final void append(CharSequence text) {
5739         append(text, 0, text.length());
5740     }
5741
5742     /**
5743      * Convenience method to append the specified text slice to the TextView's
5744      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5745      * if it was not already editable.
5746      *
5747      * @param text text to be appended to the already displayed text
5748      * @param start the index of the first character in the {@code text}
5749      * @param end the index of the character following the last character in the {@code text}
5750      *
5751      * @see Appendable#append(CharSequence, int, int)
5752      */
5753     public void append(CharSequence text, int start, int end) {
5754         if (!(mText instanceof Editable)) {
5755             setText(mText, BufferType.EDITABLE);
5756         }
5757
5758         ((Editable) mText).append(text, start, end);
5759
5760         if (mAutoLinkMask != 0) {
5761             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
5762             // Do not change the movement method for text that support text selection as it
5763             // would prevent an arbitrary cursor displacement.
5764             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
5765                 setMovementMethod(LinkMovementMethod.getInstance());
5766             }
5767         }
5768     }
5769
5770     private void updateTextColors() {
5771         boolean inval = false;
5772         final int[] drawableState = getDrawableState();
5773         int color = mTextColor.getColorForState(drawableState, 0);
5774         if (color != mCurTextColor) {
5775             mCurTextColor = color;
5776             inval = true;
5777         }
5778         if (mLinkTextColor != null) {
5779             color = mLinkTextColor.getColorForState(drawableState, 0);
5780             if (color != mTextPaint.linkColor) {
5781                 mTextPaint.linkColor = color;
5782                 inval = true;
5783             }
5784         }
5785         if (mHintTextColor != null) {
5786             color = mHintTextColor.getColorForState(drawableState, 0);
5787             if (color != mCurHintTextColor) {
5788                 mCurHintTextColor = color;
5789                 if (mText.length() == 0) {
5790                     inval = true;
5791                 }
5792             }
5793         }
5794         if (inval) {
5795             // Text needs to be redrawn with the new color
5796             if (mEditor != null) mEditor.invalidateTextDisplayList();
5797             invalidate();
5798         }
5799     }
5800
5801     @Override
5802     protected void drawableStateChanged() {
5803         super.drawableStateChanged();
5804
5805         if (mTextColor != null && mTextColor.isStateful()
5806                 || (mHintTextColor != null && mHintTextColor.isStateful())
5807                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
5808             updateTextColors();
5809         }
5810
5811         if (mDrawables != null) {
5812             final int[] state = getDrawableState();
5813             for (Drawable dr : mDrawables.mShowing) {
5814                 if (dr != null && dr.isStateful() && dr.setState(state)) {
5815                     invalidateDrawable(dr);
5816                 }
5817             }
5818         }
5819     }
5820
5821     @Override
5822     public void drawableHotspotChanged(float x, float y) {
5823         super.drawableHotspotChanged(x, y);
5824
5825         if (mDrawables != null) {
5826             for (Drawable dr : mDrawables.mShowing) {
5827                 if (dr != null) {
5828                     dr.setHotspot(x, y);
5829                 }
5830             }
5831         }
5832     }
5833
5834     @Override
5835     public Parcelable onSaveInstanceState() {
5836         Parcelable superState = super.onSaveInstanceState();
5837
5838         // Save state if we are forced to
5839         final boolean freezesText = getFreezesText();
5840         boolean hasSelection = false;
5841         int start = -1;
5842         int end = -1;
5843
5844         if (mText != null) {
5845             start = getSelectionStart();
5846             end = getSelectionEnd();
5847             if (start >= 0 || end >= 0) {
5848                 // Or save state if there is a selection
5849                 hasSelection = true;
5850             }
5851         }
5852
5853         if (freezesText || hasSelection) {
5854             SavedState ss = new SavedState(superState);
5855
5856             if (freezesText) {
5857                 if (mText instanceof Spanned) {
5858                     final Spannable sp = new SpannableStringBuilder(mText);
5859
5860                     if (mEditor != null) {
5861                         removeMisspelledSpans(sp);
5862                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
5863                     }
5864
5865                     ss.text = sp;
5866                 } else {
5867                     ss.text = mText.toString();
5868                 }
5869             }
5870
5871             if (hasSelection) {
5872                 // XXX Should also save the current scroll position!
5873                 ss.selStart = start;
5874                 ss.selEnd = end;
5875             }
5876
5877             if (isFocused() && start >= 0 && end >= 0) {
5878                 ss.frozenWithFocus = true;
5879             }
5880
5881             ss.error = getError();
5882
5883             if (mEditor != null) {
5884                 ss.editorState = mEditor.saveInstanceState();
5885             }
5886             return ss;
5887         }
5888
5889         return superState;
5890     }
5891
5892     void removeMisspelledSpans(Spannable spannable) {
5893         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
5894                 SuggestionSpan.class);
5895         for (int i = 0; i < suggestionSpans.length; i++) {
5896             int flags = suggestionSpans[i].getFlags();
5897             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
5898                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
5899                 spannable.removeSpan(suggestionSpans[i]);
5900             }
5901         }
5902     }
5903
5904     @Override
5905     public void onRestoreInstanceState(Parcelable state) {
5906         if (!(state instanceof SavedState)) {
5907             super.onRestoreInstanceState(state);
5908             return;
5909         }
5910
5911         SavedState ss = (SavedState) state;
5912         super.onRestoreInstanceState(ss.getSuperState());
5913
5914         // XXX restore buffer type too, as well as lots of other stuff
5915         if (ss.text != null) {
5916             setText(ss.text);
5917         }
5918
5919         if (ss.selStart >= 0 && ss.selEnd >= 0) {
5920             if (mSpannable != null) {
5921                 int len = mText.length();
5922
5923                 if (ss.selStart > len || ss.selEnd > len) {
5924                     String restored = "";
5925
5926                     if (ss.text != null) {
5927                         restored = "(restored) ";
5928                     }
5929
5930                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
5931                             + " out of range for " + restored + "text " + mText);
5932                 } else {
5933                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
5934
5935                     if (ss.frozenWithFocus) {
5936                         createEditorIfNeeded();
5937                         mEditor.mFrozenWithFocus = true;
5938                     }
5939                 }
5940             }
5941         }
5942
5943         if (ss.error != null) {
5944             final CharSequence error = ss.error;
5945             // Display the error later, after the first layout pass
5946             post(new Runnable() {
5947                 public void run() {
5948                     if (mEditor == null || !mEditor.mErrorWasChanged) {
5949                         setError(error);
5950                     }
5951                 }
5952             });
5953         }
5954
5955         if (ss.editorState != null) {
5956             createEditorIfNeeded();
5957             mEditor.restoreInstanceState(ss.editorState);
5958         }
5959     }
5960
5961     /**
5962      * Control whether this text view saves its entire text contents when
5963      * freezing to an icicle, in addition to dynamic state such as cursor
5964      * position.  By default this is false, not saving the text.  Set to true
5965      * if the text in the text view is not being saved somewhere else in
5966      * persistent storage (such as in a content provider) so that if the
5967      * view is later thawed the user will not lose their data. For
5968      * {@link android.widget.EditText} it is always enabled, regardless of
5969      * the value of the attribute.
5970      *
5971      * @param freezesText Controls whether a frozen icicle should include the
5972      * entire text data: true to include it, false to not.
5973      *
5974      * @attr ref android.R.styleable#TextView_freezesText
5975      */
5976     @android.view.RemotableViewMethod
5977     public void setFreezesText(boolean freezesText) {
5978         mFreezesText = freezesText;
5979     }
5980
5981     /**
5982      * Return whether this text view is including its entire text contents
5983      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
5984      *
5985      * @return Returns true if text is included, false if it isn't.
5986      *
5987      * @see #setFreezesText
5988      */
5989     @InspectableProperty
5990     public boolean getFreezesText() {
5991         return mFreezesText;
5992     }
5993
5994     ///////////////////////////////////////////////////////////////////////////
5995
5996     /**
5997      * Sets the Factory used to create new {@link Editable Editables}.
5998      *
5999      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
6000      *
6001      * @see android.text.Editable.Factory
6002      * @see android.widget.TextView.BufferType#EDITABLE
6003      */
6004     public final void setEditableFactory(Editable.Factory factory) {
6005         mEditableFactory = factory;
6006         setText(mText);
6007     }
6008
6009     /**
6010      * Sets the Factory used to create new {@link Spannable Spannables}.
6011      *
6012      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
6013      *
6014      * @see android.text.Spannable.Factory
6015      * @see android.widget.TextView.BufferType#SPANNABLE
6016      */
6017     public final void setSpannableFactory(Spannable.Factory factory) {
6018         mSpannableFactory = factory;
6019         setText(mText);
6020     }
6021
6022     /**
6023      * Sets the text to be displayed. TextView <em>does not</em> accept
6024      * HTML-like formatting, which you can do with text strings in XML resource files.
6025      * To style your strings, attach android.text.style.* objects to a
6026      * {@link android.text.SpannableString}, or see the
6027      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
6028      * Available Resource Types</a> documentation for an example of setting
6029      * formatted text in the XML resource file.
6030      * <p/>
6031      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6032      * intermediate {@link Spannable Spannables}. Likewise it will use
6033      * {@link android.text.Editable.Factory} to create final or intermediate
6034      * {@link Editable Editables}.
6035      *
6036      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
6037      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
6038      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
6039      *
6040      * @param text text to be displayed
6041      *
6042      * @attr ref android.R.styleable#TextView_text
6043      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
6044      *                                  parameters used to create the PrecomputedText mismatches
6045      *                                  with this TextView.
6046      */
6047     @android.view.RemotableViewMethod
6048     public final void setText(CharSequence text) {
6049         setText(text, mBufferType);
6050     }
6051
6052     /**
6053      * Sets the text to be displayed but retains the cursor position. Same as
6054      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
6055      * new text.
6056      * <p/>
6057      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6058      * intermediate {@link Spannable Spannables}. Likewise it will use
6059      * {@link android.text.Editable.Factory} to create final or intermediate
6060      * {@link Editable Editables}.
6061      *
6062      * @param text text to be displayed
6063      *
6064      * @see #setText(CharSequence)
6065      */
6066     @android.view.RemotableViewMethod
6067     public final void setTextKeepState(CharSequence text) {
6068         setTextKeepState(text, mBufferType);
6069     }
6070
6071     /**
6072      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
6073      * <p/>
6074      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6075      * intermediate {@link Spannable Spannables}. Likewise it will use
6076      * {@link android.text.Editable.Factory} to create final or intermediate
6077      * {@link Editable Editables}.
6078      *
6079      * Subclasses overriding this method should ensure that the following post condition holds,
6080      * in order to guarantee the safety of the view's measurement and layout operations:
6081      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
6082      * will be different from {@code null}.
6083      *
6084      * @param text text to be displayed
6085      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6086      *              stored as a static text, styleable/spannable text, or editable text
6087      *
6088      * @see #setText(CharSequence)
6089      * @see android.widget.TextView.BufferType
6090      * @see #setSpannableFactory(Spannable.Factory)
6091      * @see #setEditableFactory(Editable.Factory)
6092      *
6093      * @attr ref android.R.styleable#TextView_text
6094      * @attr ref android.R.styleable#TextView_bufferType
6095      */
6096     public void setText(CharSequence text, BufferType type) {
6097         setText(text, type, true, 0);
6098
6099         if (mCharWrapper != null) {
6100             mCharWrapper.mChars = null;
6101         }
6102     }
6103
6104     @UnsupportedAppUsage
6105     private void setText(CharSequence text, BufferType type,
6106                          boolean notifyBefore, int oldlen) {
6107         mTextSetFromXmlOrResourceId = false;
6108         if (text == null) {
6109             text = "";
6110         }
6111
6112         // If suggestions are not enabled, remove the suggestion spans from the text
6113         if (!isSuggestionsEnabled()) {
6114             text = removeSuggestionSpans(text);
6115         }
6116
6117         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
6118
6119         if (text instanceof Spanned
6120                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
6121             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
6122                 setHorizontalFadingEdgeEnabled(true);
6123                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
6124             } else {
6125                 setHorizontalFadingEdgeEnabled(false);
6126                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
6127             }
6128             setEllipsize(TextUtils.TruncateAt.MARQUEE);
6129         }
6130
6131         int n = mFilters.length;
6132         for (int i = 0; i < n; i++) {
6133             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
6134             if (out != null) {
6135                 text = out;
6136             }
6137         }
6138
6139         if (notifyBefore) {
6140             if (mText != null) {
6141                 oldlen = mText.length();
6142                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
6143             } else {
6144                 sendBeforeTextChanged("", 0, 0, text.length());
6145             }
6146         }
6147
6148         boolean needEditableForNotification = false;
6149
6150         if (mListeners != null && mListeners.size() != 0) {
6151             needEditableForNotification = true;
6152         }
6153
6154         PrecomputedText precomputed =
6155                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
6156         if (type == BufferType.EDITABLE || getKeyListener() != null
6157                 || needEditableForNotification) {
6158             createEditorIfNeeded();
6159             mEditor.forgetUndoRedo();
6160             Editable t = mEditableFactory.newEditable(text);
6161             text = t;
6162             setFilters(t, mFilters);
6163             InputMethodManager imm = getInputMethodManager();
6164             if (imm != null) imm.restartInput(this);
6165         } else if (precomputed != null) {
6166             if (mTextDir == null) {
6167                 mTextDir = getTextDirectionHeuristic();
6168             }
6169             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
6170                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
6171                             mHyphenationFrequency);
6172             switch (checkResult) {
6173                 case PrecomputedText.Params.UNUSABLE:
6174                     throw new IllegalArgumentException(
6175                         "PrecomputedText's Parameters don't match the parameters of this TextView."
6176                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
6177                         + "to override the settings of this TextView: "
6178                         + "PrecomputedText: " + precomputed.getParams()
6179                         + "TextView: " + getTextMetricsParams());
6180                 case PrecomputedText.Params.NEED_RECOMPUTE:
6181                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
6182                     break;
6183                 case PrecomputedText.Params.USABLE:
6184                     // pass through
6185             }
6186         } else if (type == BufferType.SPANNABLE || mMovement != null) {
6187             text = mSpannableFactory.newSpannable(text);
6188         } else if (!(text instanceof CharWrapper)) {
6189             text = TextUtils.stringOrSpannedString(text);
6190         }
6191
6192         if (mAutoLinkMask != 0) {
6193             Spannable s2;
6194
6195             if (type == BufferType.EDITABLE || text instanceof Spannable) {
6196                 s2 = (Spannable) text;
6197             } else {
6198                 s2 = mSpannableFactory.newSpannable(text);
6199             }
6200
6201             if (Linkify.addLinks(s2, mAutoLinkMask)) {
6202                 text = s2;
6203                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
6204
6205                 /*
6206                  * We must go ahead and set the text before changing the
6207                  * movement method, because setMovementMethod() may call
6208                  * setText() again to try to upgrade the buffer type.
6209                  */
6210                 setTextInternal(text);
6211
6212                 // Do not change the movement method for text that support text selection as it
6213                 // would prevent an arbitrary cursor displacement.
6214                 if (mLinksClickable && !textCanBeSelected()) {
6215                     setMovementMethod(LinkMovementMethod.getInstance());
6216                 }
6217             }
6218         }
6219
6220         mBufferType = type;
6221         setTextInternal(text);
6222
6223         if (mTransformation == null) {
6224             mTransformed = text;
6225         } else {
6226             mTransformed = mTransformation.getTransformation(text, this);
6227         }
6228         if (mTransformed == null) {
6229             // Should not happen if the transformation method follows the non-null postcondition.
6230             mTransformed = "";
6231         }
6232
6233         final int textLength = text.length();
6234
6235         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
6236             Spannable sp = (Spannable) text;
6237
6238             // Remove any ChangeWatchers that might have come from other TextViews.
6239             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
6240             final int count = watchers.length;
6241             for (int i = 0; i < count; i++) {
6242                 sp.removeSpan(watchers[i]);
6243             }
6244
6245             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
6246
6247             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
6248                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
6249
6250             if (mEditor != null) mEditor.addSpanWatchers(sp);
6251
6252             if (mTransformation != null) {
6253                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
6254             }
6255
6256             if (mMovement != null) {
6257                 mMovement.initialize(this, (Spannable) text);
6258
6259                 /*
6260                  * Initializing the movement method will have set the
6261                  * selection, so reset mSelectionMoved to keep that from
6262                  * interfering with the normal on-focus selection-setting.
6263                  */
6264                 if (mEditor != null) mEditor.mSelectionMoved = false;
6265             }
6266         }
6267
6268         if (mLayout != null) {
6269             checkForRelayout();
6270         }
6271
6272         sendOnTextChanged(text, 0, oldlen, textLength);
6273         onTextChanged(text, 0, oldlen, textLength);
6274
6275         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
6276
6277         if (needEditableForNotification) {
6278             sendAfterTextChanged((Editable) text);
6279         } else {
6280             notifyListeningManagersAfterTextChanged();
6281         }
6282
6283         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
6284         if (mEditor != null) mEditor.prepareCursorControllers();
6285     }
6286
6287     /**
6288      * Sets the TextView to display the specified slice of the specified
6289      * char array. You must promise that you will not change the contents
6290      * of the array except for right before another call to setText(),
6291      * since the TextView has no way to know that the text
6292      * has changed and that it needs to invalidate and re-layout.
6293      *
6294      * @param text char array to be displayed
6295      * @param start start index in the char array
6296      * @param len length of char count after {@code start}
6297      */
6298     public final void setText(char[] text, int start, int len) {
6299         int oldlen = 0;
6300
6301         if (start < 0 || len < 0 || start + len > text.length) {
6302             throw new IndexOutOfBoundsException(start + ", " + len);
6303         }
6304
6305         /*
6306          * We must do the before-notification here ourselves because if
6307          * the old text is a CharWrapper we destroy it before calling
6308          * into the normal path.
6309          */
6310         if (mText != null) {
6311             oldlen = mText.length();
6312             sendBeforeTextChanged(mText, 0, oldlen, len);
6313         } else {
6314             sendBeforeTextChanged("", 0, 0, len);
6315         }
6316
6317         if (mCharWrapper == null) {
6318             mCharWrapper = new CharWrapper(text, start, len);
6319         } else {
6320             mCharWrapper.set(text, start, len);
6321         }
6322
6323         setText(mCharWrapper, mBufferType, false, oldlen);
6324     }
6325
6326     /**
6327      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
6328      * the cursor position. Same as
6329      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
6330      * position (if any) is retained in the new text.
6331      * <p/>
6332      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6333      * intermediate {@link Spannable Spannables}. Likewise it will use
6334      * {@link android.text.Editable.Factory} to create final or intermediate
6335      * {@link Editable Editables}.
6336      *
6337      * @param text text to be displayed
6338      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6339      *              stored as a static text, styleable/spannable text, or editable text
6340      *
6341      * @see #setText(CharSequence, android.widget.TextView.BufferType)
6342      */
6343     public final void setTextKeepState(CharSequence text, BufferType type) {
6344         int start = getSelectionStart();
6345         int end = getSelectionEnd();
6346         int len = text.length();
6347
6348         setText(text, type);
6349
6350         if (start >= 0 || end >= 0) {
6351             if (mSpannable != null) {
6352                 Selection.setSelection(mSpannable,
6353                                        Math.max(0, Math.min(start, len)),
6354                                        Math.max(0, Math.min(end, len)));
6355             }
6356         }
6357     }
6358
6359     /**
6360      * Sets the text to be displayed using a string resource identifier.
6361      *
6362      * @param resid the resource identifier of the string resource to be displayed
6363      *
6364      * @see #setText(CharSequence)
6365      *
6366      * @attr ref android.R.styleable#TextView_text
6367      */
6368     @android.view.RemotableViewMethod
6369     public final void setText(@StringRes int resid) {
6370         setText(getContext().getResources().getText(resid));
6371         mTextSetFromXmlOrResourceId = true;
6372         mTextId = resid;
6373     }
6374
6375     /**
6376      * Sets the text to be displayed using a string resource identifier and the
6377      * {@link android.widget.TextView.BufferType}.
6378      * <p/>
6379      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
6380      * intermediate {@link Spannable Spannables}. Likewise it will use
6381      * {@link android.text.Editable.Factory} to create final or intermediate
6382      * {@link Editable Editables}.
6383      *
6384      * @param resid the resource identifier of the string resource to be displayed
6385      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
6386      *              stored as a static text, styleable/spannable text, or editable text
6387      *
6388      * @see #setText(int)
6389      * @see #setText(CharSequence)
6390      * @see android.widget.TextView.BufferType
6391      * @see #setSpannableFactory(Spannable.Factory)
6392      * @see #setEditableFactory(Editable.Factory)
6393      *
6394      * @attr ref android.R.styleable#TextView_text
6395      * @attr ref android.R.styleable#TextView_bufferType
6396      */
6397     public final void setText(@StringRes int resid, BufferType type) {
6398         setText(getContext().getResources().getText(resid), type);
6399         mTextSetFromXmlOrResourceId = true;
6400         mTextId = resid;
6401     }
6402
6403     /**
6404      * Sets the text to be displayed when the text of the TextView is empty.
6405      * Null means to use the normal empty text. The hint does not currently
6406      * participate in determining the size of the view.
6407      *
6408      * @attr ref android.R.styleable#TextView_hint
6409      */
6410     @android.view.RemotableViewMethod
6411     public final void setHint(CharSequence hint) {
6412         setHintInternal(hint);
6413
6414         if (mEditor != null && isInputMethodTarget()) {
6415             mEditor.reportExtractedText();
6416         }
6417     }
6418
6419     private void setHintInternal(CharSequence hint) {
6420         mHint = TextUtils.stringOrSpannedString(hint);
6421
6422         if (mLayout != null) {
6423             checkForRelayout();
6424         }
6425
6426         if (mText.length() == 0) {
6427             invalidate();
6428         }
6429
6430         // Invalidate display list if hint is currently used
6431         if (mEditor != null && mText.length() == 0 && mHint != null) {
6432             mEditor.invalidateTextDisplayList();
6433         }
6434     }
6435
6436     /**
6437      * Sets the text to be displayed when the text of the TextView is empty,
6438      * from a resource.
6439      *
6440      * @attr ref android.R.styleable#TextView_hint
6441      */
6442     @android.view.RemotableViewMethod
6443     public final void setHint(@StringRes int resid) {
6444         setHint(getContext().getResources().getText(resid));
6445     }
6446
6447     /**
6448      * Returns the hint that is displayed when the text of the TextView
6449      * is empty.
6450      *
6451      * @attr ref android.R.styleable#TextView_hint
6452      */
6453     @InspectableProperty
6454     @ViewDebug.CapturedViewProperty
6455     public CharSequence getHint() {
6456         return mHint;
6457     }
6458
6459     /**
6460      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
6461      * line characters instead of letting it wrap onto multiple lines.
6462      *
6463      * @attr ref android.R.styleable#TextView_singleLine
6464      */
6465     @InspectableProperty
6466     public boolean isSingleLine() {
6467         return mSingleLine;
6468     }
6469
6470     private static boolean isMultilineInputType(int type) {
6471         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
6472                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
6473     }
6474
6475     /**
6476      * Removes the suggestion spans.
6477      */
6478     CharSequence removeSuggestionSpans(CharSequence text) {
6479         if (text instanceof Spanned) {
6480             Spannable spannable;
6481             if (text instanceof Spannable) {
6482                 spannable = (Spannable) text;
6483             } else {
6484                 spannable = mSpannableFactory.newSpannable(text);
6485             }
6486
6487             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
6488             if (spans.length == 0) {
6489                 return text;
6490             } else {
6491                 text = spannable;
6492             }
6493
6494             for (int i = 0; i < spans.length; i++) {
6495                 spannable.removeSpan(spans[i]);
6496             }
6497         }
6498         return text;
6499     }
6500
6501     /**
6502      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
6503      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
6504      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
6505      * then a soft keyboard will not be displayed for this text view.
6506      *
6507      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
6508      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
6509      * type.
6510      *
6511      * @see #getInputType()
6512      * @see #setRawInputType(int)
6513      * @see android.text.InputType
6514      * @attr ref android.R.styleable#TextView_inputType
6515      */
6516     public void setInputType(int type) {
6517         final boolean wasPassword = isPasswordInputType(getInputType());
6518         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
6519         setInputType(type, false);
6520         final boolean isPassword = isPasswordInputType(type);
6521         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
6522         boolean forceUpdate = false;
6523         if (isPassword) {
6524             setTransformationMethod(PasswordTransformationMethod.getInstance());
6525             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6526                     Typeface.NORMAL, -1 /* weight, not specifeid */);
6527         } else if (isVisiblePassword) {
6528             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6529                 forceUpdate = true;
6530             }
6531             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
6532                     Typeface.NORMAL, -1 /* weight, not specified */);
6533         } else if (wasPassword || wasVisiblePassword) {
6534             // not in password mode, clean up typeface and transformation
6535             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
6536                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
6537                     -1 /* weight, not specified */);
6538             if (mTransformation == PasswordTransformationMethod.getInstance()) {
6539                 forceUpdate = true;
6540             }
6541         }
6542
6543         boolean singleLine = !isMultilineInputType(type);
6544
6545         // We need to update the single line mode if it has changed or we
6546         // were previously in password mode.
6547         if (mSingleLine != singleLine || forceUpdate) {
6548             // Change single line mode, but only change the transformation if
6549             // we are not in password mode.
6550             applySingleLine(singleLine, !isPassword, true);
6551         }
6552
6553         if (!isSuggestionsEnabled()) {
6554             setTextInternal(removeSuggestionSpans(mText));
6555         }
6556
6557         InputMethodManager imm = getInputMethodManager();
6558         if (imm != null) imm.restartInput(this);
6559     }
6560
6561     /**
6562      * It would be better to rely on the input type for everything. A password inputType should have
6563      * a password transformation. We should hence use isPasswordInputType instead of this method.
6564      *
6565      * We should:
6566      * - Call setInputType in setKeyListener instead of changing the input type directly (which
6567      * would install the correct transformation).
6568      * - Refuse the installation of a non-password transformation in setTransformation if the input
6569      * type is password.
6570      *
6571      * However, this is like this for legacy reasons and we cannot break existing apps. This method
6572      * is useful since it matches what the user can see (obfuscated text or not).
6573      *
6574      * @return true if the current transformation method is of the password type.
6575      */
6576     boolean hasPasswordTransformationMethod() {
6577         return mTransformation instanceof PasswordTransformationMethod;
6578     }
6579
6580     static boolean isPasswordInputType(int inputType) {
6581         final int variation =
6582                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6583         return variation
6584                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
6585                 || variation
6586                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
6587                 || variation
6588                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
6589     }
6590
6591     private static boolean isVisiblePasswordInputType(int inputType) {
6592         final int variation =
6593                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
6594         return variation
6595                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
6596     }
6597
6598     /**
6599      * Directly change the content type integer of the text view, without
6600      * modifying any other state.
6601      * @see #setInputType(int)
6602      * @see android.text.InputType
6603      * @attr ref android.R.styleable#TextView_inputType
6604      */
6605     public void setRawInputType(int type) {
6606         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
6607         createEditorIfNeeded();
6608         mEditor.mInputType = type;
6609     }
6610
6611     /**
6612      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
6613      *         a {@code Locale} object that can be used to customize key various listeners.
6614      * @see DateKeyListener#getInstance(Locale)
6615      * @see DateTimeKeyListener#getInstance(Locale)
6616      * @see DigitsKeyListener#getInstance(Locale)
6617      * @see TimeKeyListener#getInstance(Locale)
6618      */
6619     @Nullable
6620     private Locale getCustomLocaleForKeyListenerOrNull() {
6621         if (!mUseInternationalizedInput) {
6622             // If the application does not target O, stick to the previous behavior.
6623             return null;
6624         }
6625         final LocaleList locales = getImeHintLocales();
6626         if (locales == null) {
6627             // If the application does not explicitly specify IME hint locale, also stick to the
6628             // previous behavior.
6629             return null;
6630         }
6631         return locales.get(0);
6632     }
6633
6634     @UnsupportedAppUsage
6635     private void setInputType(int type, boolean direct) {
6636         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
6637         KeyListener input;
6638         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
6639             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
6640             TextKeyListener.Capitalize cap;
6641             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
6642                 cap = TextKeyListener.Capitalize.CHARACTERS;
6643             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
6644                 cap = TextKeyListener.Capitalize.WORDS;
6645             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
6646                 cap = TextKeyListener.Capitalize.SENTENCES;
6647             } else {
6648                 cap = TextKeyListener.Capitalize.NONE;
6649             }
6650             input = TextKeyListener.getInstance(autotext, cap);
6651         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
6652             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6653             input = DigitsKeyListener.getInstance(
6654                     locale,
6655                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
6656                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
6657             if (locale != null) {
6658                 // Override type, if necessary for i18n.
6659                 int newType = input.getInputType();
6660                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
6661                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
6662                     // The class is different from the original class. So we need to override
6663                     // 'type'. But we want to keep the password flag if it's there.
6664                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
6665                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
6666                     }
6667                     type = newType;
6668                 }
6669             }
6670         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
6671             final Locale locale = getCustomLocaleForKeyListenerOrNull();
6672             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
6673                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
6674                     input = DateKeyListener.getInstance(locale);
6675                     break;
6676                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
6677                     input = TimeKeyListener.getInstance(locale);
6678                     break;
6679                 default:
6680                     input = DateTimeKeyListener.getInstance(locale);
6681                     break;
6682             }
6683             if (mUseInternationalizedInput) {
6684                 type = input.getInputType(); // Override type, if necessary for i18n.
6685             }
6686         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
6687             input = DialerKeyListener.getInstance();
6688         } else {
6689             input = TextKeyListener.getInstance();
6690         }
6691         setRawInputType(type);
6692         mListenerChanged = false;
6693         if (direct) {
6694             createEditorIfNeeded();
6695             mEditor.mKeyListener = input;
6696         } else {
6697             setKeyListenerOnly(input);
6698         }
6699     }
6700
6701     /**
6702      * Get the type of the editable content.
6703      *
6704      * @see #setInputType(int)
6705      * @see android.text.InputType
6706      */
6707     @InspectableProperty(flagMapping = {
6708             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
6709             @FlagEntry(
6710                     name = "text",
6711                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6712                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
6713             @FlagEntry(
6714                     name = "textUri",
6715                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6716                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
6717             @FlagEntry(
6718                     name = "textEmailAddress",
6719                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6720                     target = InputType.TYPE_CLASS_TEXT
6721                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
6722             @FlagEntry(
6723                     name = "textEmailSubject",
6724                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6725                     target = InputType.TYPE_CLASS_TEXT
6726                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
6727             @FlagEntry(
6728                     name = "textShortMessage",
6729                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6730                     target = InputType.TYPE_CLASS_TEXT
6731                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
6732             @FlagEntry(
6733                     name = "textLongMessage",
6734                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6735                     target = InputType.TYPE_CLASS_TEXT
6736                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
6737             @FlagEntry(
6738                     name = "textPersonName",
6739                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6740                     target = InputType.TYPE_CLASS_TEXT
6741                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
6742             @FlagEntry(
6743                     name = "textPostalAddress",
6744                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6745                     target = InputType.TYPE_CLASS_TEXT
6746                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
6747             @FlagEntry(
6748                     name = "textPassword",
6749                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6750                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
6751             @FlagEntry(
6752                     name = "textVisiblePassword",
6753                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6754                     target = InputType.TYPE_CLASS_TEXT
6755                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
6756             @FlagEntry(
6757                     name = "textWebEditText",
6758                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6759                     target = InputType.TYPE_CLASS_TEXT
6760                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
6761             @FlagEntry(
6762                     name = "textFilter",
6763                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6764                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
6765             @FlagEntry(
6766                     name = "textPhonetic",
6767                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6768                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
6769             @FlagEntry(
6770                     name = "textWebEmailAddress",
6771                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6772                     target = InputType.TYPE_CLASS_TEXT
6773                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
6774             @FlagEntry(
6775                     name = "textWebPassword",
6776                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6777                     target = InputType.TYPE_CLASS_TEXT
6778                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
6779             @FlagEntry(
6780                     name = "number",
6781                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6782                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
6783             @FlagEntry(
6784                     name = "numberPassword",
6785                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6786                     target = InputType.TYPE_CLASS_NUMBER
6787                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
6788             @FlagEntry(
6789                     name = "phone",
6790                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6791                     target = InputType.TYPE_CLASS_PHONE),
6792             @FlagEntry(
6793                     name = "datetime",
6794                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6795                     target = InputType.TYPE_CLASS_DATETIME
6796                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
6797             @FlagEntry(
6798                     name = "date",
6799                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6800                     target = InputType.TYPE_CLASS_DATETIME
6801                             | InputType.TYPE_DATETIME_VARIATION_DATE),
6802             @FlagEntry(
6803                     name = "time",
6804                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
6805                     target = InputType.TYPE_CLASS_DATETIME
6806                             | InputType.TYPE_DATETIME_VARIATION_TIME),
6807             @FlagEntry(
6808                     name = "textCapCharacters",
6809                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6810                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
6811             @FlagEntry(
6812                     name = "textCapWords",
6813                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6814                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
6815             @FlagEntry(
6816                     name = "textCapSentences",
6817                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6818                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
6819             @FlagEntry(
6820                     name = "textAutoCorrect",
6821                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6822                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
6823             @FlagEntry(
6824                     name = "textAutoComplete",
6825                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6826                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
6827             @FlagEntry(
6828                     name = "textMultiLine",
6829                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6830                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
6831             @FlagEntry(
6832                     name = "textImeMultiLine",
6833                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6834                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
6835             @FlagEntry(
6836                     name = "textNoSuggestions",
6837                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6838                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
6839             @FlagEntry(
6840                     name = "numberSigned",
6841                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6842                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
6843             @FlagEntry(
6844                     name = "numberDecimal",
6845                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
6846                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
6847     })
6848     public int getInputType() {
6849         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
6850     }
6851
6852     /**
6853      * Change the editor type integer associated with the text view, which
6854      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
6855      * when it has focus.
6856      * @see #getImeOptions
6857      * @see android.view.inputmethod.EditorInfo
6858      * @attr ref android.R.styleable#TextView_imeOptions
6859      */
6860     public void setImeOptions(int imeOptions) {
6861         createEditorIfNeeded();
6862         mEditor.createInputContentTypeIfNeeded();
6863         mEditor.mInputContentType.imeOptions = imeOptions;
6864     }
6865
6866     /**
6867      * Get the type of the Input Method Editor (IME).
6868      * @return the type of the IME
6869      * @see #setImeOptions(int)
6870      * @see EditorInfo
6871      */
6872     @InspectableProperty(flagMapping = {
6873             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
6874             @FlagEntry(
6875                     name = "actionUnspecified",
6876                     mask = EditorInfo.IME_MASK_ACTION,
6877                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
6878             @FlagEntry(
6879                     name = "actionNone",
6880                     mask = EditorInfo.IME_MASK_ACTION,
6881                     target = EditorInfo.IME_ACTION_NONE),
6882             @FlagEntry(
6883                     name = "actionGo",
6884                     mask = EditorInfo.IME_MASK_ACTION,
6885                     target = EditorInfo.IME_ACTION_GO),
6886             @FlagEntry(
6887                     name = "actionSearch",
6888                     mask = EditorInfo.IME_MASK_ACTION,
6889                     target = EditorInfo.IME_ACTION_SEARCH),
6890             @FlagEntry(
6891                     name = "actionSend",
6892                     mask = EditorInfo.IME_MASK_ACTION,
6893                     target = EditorInfo.IME_ACTION_SEND),
6894             @FlagEntry(
6895                     name = "actionNext",
6896                     mask = EditorInfo.IME_MASK_ACTION,
6897                     target = EditorInfo.IME_ACTION_NEXT),
6898             @FlagEntry(
6899                     name = "actionDone",
6900                     mask = EditorInfo.IME_MASK_ACTION,
6901                     target = EditorInfo.IME_ACTION_DONE),
6902             @FlagEntry(
6903                     name = "actionPrevious",
6904                     mask = EditorInfo.IME_MASK_ACTION,
6905                     target = EditorInfo.IME_ACTION_PREVIOUS),
6906             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
6907             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
6908             @FlagEntry(
6909                     name = "flagNavigatePrevious",
6910                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
6911             @FlagEntry(
6912                     name = "flagNoAccessoryAction",
6913                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
6914             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
6915             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
6916             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
6917             @FlagEntry(
6918                     name = "flagNoPersonalizedLearning",
6919                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
6920     })
6921     public int getImeOptions() {
6922         return mEditor != null && mEditor.mInputContentType != null
6923                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
6924     }
6925
6926     /**
6927      * Change the custom IME action associated with the text view, which
6928      * will be reported to an IME with {@link EditorInfo#actionLabel}
6929      * and {@link EditorInfo#actionId} when it has focus.
6930      * @see #getImeActionLabel
6931      * @see #getImeActionId
6932      * @see android.view.inputmethod.EditorInfo
6933      * @attr ref android.R.styleable#TextView_imeActionLabel
6934      * @attr ref android.R.styleable#TextView_imeActionId
6935      */
6936     public void setImeActionLabel(CharSequence label, int actionId) {
6937         createEditorIfNeeded();
6938         mEditor.createInputContentTypeIfNeeded();
6939         mEditor.mInputContentType.imeActionLabel = label;
6940         mEditor.mInputContentType.imeActionId = actionId;
6941     }
6942
6943     /**
6944      * Get the IME action label previous set with {@link #setImeActionLabel}.
6945      *
6946      * @see #setImeActionLabel
6947      * @see android.view.inputmethod.EditorInfo
6948      */
6949     @InspectableProperty
6950     public CharSequence getImeActionLabel() {
6951         return mEditor != null && mEditor.mInputContentType != null
6952                 ? mEditor.mInputContentType.imeActionLabel : null;
6953     }
6954
6955     /**
6956      * Get the IME action ID previous set with {@link #setImeActionLabel}.
6957      *
6958      * @see #setImeActionLabel
6959      * @see android.view.inputmethod.EditorInfo
6960      */
6961     @InspectableProperty
6962     public int getImeActionId() {
6963         return mEditor != null && mEditor.mInputContentType != null
6964                 ? mEditor.mInputContentType.imeActionId : 0;
6965     }
6966
6967     /**
6968      * Set a special listener to be called when an action is performed
6969      * on the text view.  This will be called when the enter key is pressed,
6970      * or when an action supplied to the IME is selected by the user.  Setting
6971      * this means that the normal hard key event will not insert a newline
6972      * into the text view, even if it is multi-line; holding down the ALT
6973      * modifier will, however, allow the user to insert a newline character.
6974      */
6975     public void setOnEditorActionListener(OnEditorActionListener l) {
6976         createEditorIfNeeded();
6977         mEditor.createInputContentTypeIfNeeded();
6978         mEditor.mInputContentType.onEditorActionListener = l;
6979     }
6980
6981     /**
6982      * Called when an attached input method calls
6983      * {@link InputConnection#performEditorAction(int)
6984      * InputConnection.performEditorAction()}
6985      * for this text view.  The default implementation will call your action
6986      * listener supplied to {@link #setOnEditorActionListener}, or perform
6987      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
6988      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
6989      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
6990      * EditorInfo.IME_ACTION_DONE}.
6991      *
6992      * <p>For backwards compatibility, if no IME options have been set and the
6993      * text view would not normally advance focus on enter, then
6994      * the NEXT and DONE actions received here will be turned into an enter
6995      * key down/up pair to go through the normal key handling.
6996      *
6997      * @param actionCode The code of the action being performed.
6998      *
6999      * @see #setOnEditorActionListener
7000      */
7001     public void onEditorAction(int actionCode) {
7002         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
7003         if (ict != null) {
7004             if (ict.onEditorActionListener != null) {
7005                 if (ict.onEditorActionListener.onEditorAction(this,
7006                         actionCode, null)) {
7007                     return;
7008                 }
7009             }
7010
7011             // This is the handling for some default action.
7012             // Note that for backwards compatibility we don't do this
7013             // default handling if explicit ime options have not been given,
7014             // instead turning this into the normal enter key codes that an
7015             // app may be expecting.
7016             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
7017                 View v = focusSearch(FOCUS_FORWARD);
7018                 if (v != null) {
7019                     if (!v.requestFocus(FOCUS_FORWARD)) {
7020                         throw new IllegalStateException("focus search returned a view "
7021                                 + "that wasn't able to take focus!");
7022                     }
7023                 }
7024                 return;
7025
7026             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
7027                 View v = focusSearch(FOCUS_BACKWARD);
7028                 if (v != null) {
7029                     if (!v.requestFocus(FOCUS_BACKWARD)) {
7030                         throw new IllegalStateException("focus search returned a view "
7031                                 + "that wasn't able to take focus!");
7032                     }
7033                 }
7034                 return;
7035
7036             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
7037                 InputMethodManager imm = getInputMethodManager();
7038                 if (imm != null && imm.isActive(this)) {
7039                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7040                 }
7041                 return;
7042             }
7043         }
7044
7045         ViewRootImpl viewRootImpl = getViewRootImpl();
7046         if (viewRootImpl != null) {
7047             long eventTime = SystemClock.uptimeMillis();
7048             viewRootImpl.dispatchKeyFromIme(
7049                     new KeyEvent(eventTime, eventTime,
7050                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
7051                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7052                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7053                     | KeyEvent.FLAG_EDITOR_ACTION));
7054             viewRootImpl.dispatchKeyFromIme(
7055                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
7056                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
7057                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
7058                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
7059                     | KeyEvent.FLAG_EDITOR_ACTION));
7060         }
7061     }
7062
7063     /**
7064      * Set the private content type of the text, which is the
7065      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
7066      * field that will be filled in when creating an input connection.
7067      *
7068      * @see #getPrivateImeOptions()
7069      * @see EditorInfo#privateImeOptions
7070      * @attr ref android.R.styleable#TextView_privateImeOptions
7071      */
7072     public void setPrivateImeOptions(String type) {
7073         createEditorIfNeeded();
7074         mEditor.createInputContentTypeIfNeeded();
7075         mEditor.mInputContentType.privateImeOptions = type;
7076     }
7077
7078     /**
7079      * Get the private type of the content.
7080      *
7081      * @see #setPrivateImeOptions(String)
7082      * @see EditorInfo#privateImeOptions
7083      */
7084     @InspectableProperty
7085     public String getPrivateImeOptions() {
7086         return mEditor != null && mEditor.mInputContentType != null
7087                 ? mEditor.mInputContentType.privateImeOptions : null;
7088     }
7089
7090     /**
7091      * Set the extra input data of the text, which is the
7092      * {@link EditorInfo#extras TextBoxAttribute.extras}
7093      * Bundle that will be filled in when creating an input connection.  The
7094      * given integer is the resource identifier of an XML resource holding an
7095      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
7096      *
7097      * @see #getInputExtras(boolean)
7098      * @see EditorInfo#extras
7099      * @attr ref android.R.styleable#TextView_editorExtras
7100      */
7101     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
7102         createEditorIfNeeded();
7103         XmlResourceParser parser = getResources().getXml(xmlResId);
7104         mEditor.createInputContentTypeIfNeeded();
7105         mEditor.mInputContentType.extras = new Bundle();
7106         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
7107     }
7108
7109     /**
7110      * Retrieve the input extras currently associated with the text view, which
7111      * can be viewed as well as modified.
7112      *
7113      * @param create If true, the extras will be created if they don't already
7114      * exist.  Otherwise, null will be returned if none have been created.
7115      * @see #setInputExtras(int)
7116      * @see EditorInfo#extras
7117      * @attr ref android.R.styleable#TextView_editorExtras
7118      */
7119     public Bundle getInputExtras(boolean create) {
7120         if (mEditor == null && !create) return null;
7121         createEditorIfNeeded();
7122         if (mEditor.mInputContentType == null) {
7123             if (!create) return null;
7124             mEditor.createInputContentTypeIfNeeded();
7125         }
7126         if (mEditor.mInputContentType.extras == null) {
7127             if (!create) return null;
7128             mEditor.mInputContentType.extras = new Bundle();
7129         }
7130         return mEditor.mInputContentType.extras;
7131     }
7132
7133     /**
7134      * Change "hint" locales associated with the text view, which will be reported to an IME with
7135      * {@link EditorInfo#hintLocales} when it has focus.
7136      *
7137      * Starting with Android O, this also causes internationalized listeners to be created (or
7138      * change locale) based on the first locale in the input locale list.
7139      *
7140      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
7141      * call {@link InputMethodManager#restartInput(View)}.</p>
7142      * @param hintLocales List of the languages that the user is supposed to switch to no matter
7143      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
7144      * @see #getImeHintLocales()
7145      * @see android.view.inputmethod.EditorInfo#hintLocales
7146      */
7147     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
7148         createEditorIfNeeded();
7149         mEditor.createInputContentTypeIfNeeded();
7150         mEditor.mInputContentType.imeHintLocales = hintLocales;
7151         if (mUseInternationalizedInput) {
7152             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
7153         }
7154     }
7155
7156     /**
7157      * @return The current languages list "hint". {@code null} when no "hint" is available.
7158      * @see #setImeHintLocales(LocaleList)
7159      * @see android.view.inputmethod.EditorInfo#hintLocales
7160      */
7161     @Nullable
7162     public LocaleList getImeHintLocales() {
7163         if (mEditor == null) {
7164             return null;
7165         }
7166         if (mEditor.mInputContentType == null) {
7167             return null;
7168         }
7169         return mEditor.mInputContentType.imeHintLocales;
7170     }
7171
7172     /**
7173      * Returns the error message that was set to be displayed with
7174      * {@link #setError}, or <code>null</code> if no error was set
7175      * or if it the error was cleared by the widget after user input.
7176      */
7177     public CharSequence getError() {
7178         return mEditor == null ? null : mEditor.mError;
7179     }
7180
7181     /**
7182      * Sets the right-hand compound drawable of the TextView to the "error"
7183      * icon and sets an error message that will be displayed in a popup when
7184      * the TextView has focus.  The icon and error message will be reset to
7185      * null when any key events cause changes to the TextView's text.  If the
7186      * <code>error</code> is <code>null</code>, the error message and icon
7187      * will be cleared.
7188      */
7189     @android.view.RemotableViewMethod
7190     public void setError(CharSequence error) {
7191         if (error == null) {
7192             setError(null, null);
7193         } else {
7194             Drawable dr = getContext().getDrawable(
7195                     com.android.internal.R.drawable.indicator_input_error);
7196
7197             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
7198             setError(error, dr);
7199         }
7200     }
7201
7202     /**
7203      * Sets the right-hand compound drawable of the TextView to the specified
7204      * icon and sets an error message that will be displayed in a popup when
7205      * the TextView has focus.  The icon and error message will be reset to
7206      * null when any key events cause changes to the TextView's text.  The
7207      * drawable must already have had {@link Drawable#setBounds} set on it.
7208      * If the <code>error</code> is <code>null</code>, the error message will
7209      * be cleared (and you should provide a <code>null</code> icon as well).
7210      */
7211     public void setError(CharSequence error, Drawable icon) {
7212         createEditorIfNeeded();
7213         mEditor.setError(error, icon);
7214         notifyViewAccessibilityStateChangedIfNeeded(
7215                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
7216     }
7217
7218     @Override
7219     protected boolean setFrame(int l, int t, int r, int b) {
7220         boolean result = super.setFrame(l, t, r, b);
7221
7222         if (mEditor != null) mEditor.setFrame();
7223
7224         restartMarqueeIfNeeded();
7225
7226         return result;
7227     }
7228
7229     private void restartMarqueeIfNeeded() {
7230         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7231             mRestartMarquee = false;
7232             startMarquee();
7233         }
7234     }
7235
7236     /**
7237      * Sets the list of input filters that will be used if the buffer is
7238      * Editable. Has no effect otherwise.
7239      *
7240      * @attr ref android.R.styleable#TextView_maxLength
7241      */
7242     public void setFilters(InputFilter[] filters) {
7243         if (filters == null) {
7244             throw new IllegalArgumentException();
7245         }
7246
7247         mFilters = filters;
7248
7249         if (mText instanceof Editable) {
7250             setFilters((Editable) mText, filters);
7251         }
7252     }
7253
7254     /**
7255      * Sets the list of input filters on the specified Editable,
7256      * and includes mInput in the list if it is an InputFilter.
7257      */
7258     private void setFilters(Editable e, InputFilter[] filters) {
7259         if (mEditor != null) {
7260             final boolean undoFilter = mEditor.mUndoInputFilter != null;
7261             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
7262             int num = 0;
7263             if (undoFilter) num++;
7264             if (keyFilter) num++;
7265             if (num > 0) {
7266                 InputFilter[] nf = new InputFilter[filters.length + num];
7267
7268                 System.arraycopy(filters, 0, nf, 0, filters.length);
7269                 num = 0;
7270                 if (undoFilter) {
7271                     nf[filters.length] = mEditor.mUndoInputFilter;
7272                     num++;
7273                 }
7274                 if (keyFilter) {
7275                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
7276                 }
7277
7278                 e.setFilters(nf);
7279                 return;
7280             }
7281         }
7282         e.setFilters(filters);
7283     }
7284
7285     /**
7286      * Returns the current list of input filters.
7287      *
7288      * @attr ref android.R.styleable#TextView_maxLength
7289      */
7290     public InputFilter[] getFilters() {
7291         return mFilters;
7292     }
7293
7294     /////////////////////////////////////////////////////////////////////////
7295
7296     private int getBoxHeight(Layout l) {
7297         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
7298         int padding = (l == mHintLayout)
7299                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
7300                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
7301         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
7302     }
7303
7304     @UnsupportedAppUsage
7305     int getVerticalOffset(boolean forceNormal) {
7306         int voffset = 0;
7307         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7308
7309         Layout l = mLayout;
7310         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7311             l = mHintLayout;
7312         }
7313
7314         if (gravity != Gravity.TOP) {
7315             int boxht = getBoxHeight(l);
7316             int textht = l.getHeight();
7317
7318             if (textht < boxht) {
7319                 if (gravity == Gravity.BOTTOM) {
7320                     voffset = boxht - textht;
7321                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7322                     voffset = (boxht - textht) >> 1;
7323                 }
7324             }
7325         }
7326         return voffset;
7327     }
7328
7329     private int getBottomVerticalOffset(boolean forceNormal) {
7330         int voffset = 0;
7331         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
7332
7333         Layout l = mLayout;
7334         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
7335             l = mHintLayout;
7336         }
7337
7338         if (gravity != Gravity.BOTTOM) {
7339             int boxht = getBoxHeight(l);
7340             int textht = l.getHeight();
7341
7342             if (textht < boxht) {
7343                 if (gravity == Gravity.TOP) {
7344                     voffset = boxht - textht;
7345                 } else { // (gravity == Gravity.CENTER_VERTICAL)
7346                     voffset = (boxht - textht) >> 1;
7347                 }
7348             }
7349         }
7350         return voffset;
7351     }
7352
7353     void invalidateCursorPath() {
7354         if (mHighlightPathBogus) {
7355             invalidateCursor();
7356         } else {
7357             final int horizontalPadding = getCompoundPaddingLeft();
7358             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7359
7360             if (mEditor.mDrawableForCursor == null) {
7361                 synchronized (TEMP_RECTF) {
7362                     /*
7363                      * The reason for this concern about the thickness of the
7364                      * cursor and doing the floor/ceil on the coordinates is that
7365                      * some EditTexts (notably textfields in the Browser) have
7366                      * anti-aliased text where not all the characters are
7367                      * necessarily at integer-multiple locations.  This should
7368                      * make sure the entire cursor gets invalidated instead of
7369                      * sometimes missing half a pixel.
7370                      */
7371                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
7372                     if (thick < 1.0f) {
7373                         thick = 1.0f;
7374                     }
7375
7376                     thick /= 2.0f;
7377
7378                     // mHighlightPath is guaranteed to be non null at that point.
7379                     mHighlightPath.computeBounds(TEMP_RECTF, false);
7380
7381                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
7382                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
7383                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
7384                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
7385                 }
7386             } else {
7387                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7388                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
7389                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
7390             }
7391         }
7392     }
7393
7394     void invalidateCursor() {
7395         int where = getSelectionEnd();
7396
7397         invalidateCursor(where, where, where);
7398     }
7399
7400     private void invalidateCursor(int a, int b, int c) {
7401         if (a >= 0 || b >= 0 || c >= 0) {
7402             int start = Math.min(Math.min(a, b), c);
7403             int end = Math.max(Math.max(a, b), c);
7404             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
7405         }
7406     }
7407
7408     /**
7409      * Invalidates the region of text enclosed between the start and end text offsets.
7410      */
7411     void invalidateRegion(int start, int end, boolean invalidateCursor) {
7412         if (mLayout == null) {
7413             invalidate();
7414         } else {
7415             int lineStart = mLayout.getLineForOffset(start);
7416             int top = mLayout.getLineTop(lineStart);
7417
7418             // This is ridiculous, but the descent from the line above
7419             // can hang down into the line we really want to redraw,
7420             // so we have to invalidate part of the line above to make
7421             // sure everything that needs to be redrawn really is.
7422             // (But not the whole line above, because that would cause
7423             // the same problem with the descenders on the line above it!)
7424             if (lineStart > 0) {
7425                 top -= mLayout.getLineDescent(lineStart - 1);
7426             }
7427
7428             int lineEnd;
7429
7430             if (start == end) {
7431                 lineEnd = lineStart;
7432             } else {
7433                 lineEnd = mLayout.getLineForOffset(end);
7434             }
7435
7436             int bottom = mLayout.getLineBottom(lineEnd);
7437
7438             // mEditor can be null in case selection is set programmatically.
7439             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
7440                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
7441                 top = Math.min(top, bounds.top);
7442                 bottom = Math.max(bottom, bounds.bottom);
7443             }
7444
7445             final int compoundPaddingLeft = getCompoundPaddingLeft();
7446             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
7447
7448             int left, right;
7449             if (lineStart == lineEnd && !invalidateCursor) {
7450                 left = (int) mLayout.getPrimaryHorizontal(start);
7451                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
7452                 left += compoundPaddingLeft;
7453                 right += compoundPaddingLeft;
7454             } else {
7455                 // Rectangle bounding box when the region spans several lines
7456                 left = compoundPaddingLeft;
7457                 right = getWidth() - getCompoundPaddingRight();
7458             }
7459
7460             invalidate(mScrollX + left, verticalPadding + top,
7461                     mScrollX + right, verticalPadding + bottom);
7462         }
7463     }
7464
7465     private void registerForPreDraw() {
7466         if (!mPreDrawRegistered) {
7467             getViewTreeObserver().addOnPreDrawListener(this);
7468             mPreDrawRegistered = true;
7469         }
7470     }
7471
7472     private void unregisterForPreDraw() {
7473         getViewTreeObserver().removeOnPreDrawListener(this);
7474         mPreDrawRegistered = false;
7475         mPreDrawListenerDetached = false;
7476     }
7477
7478     /**
7479      * {@inheritDoc}
7480      */
7481     @Override
7482     public boolean onPreDraw() {
7483         if (mLayout == null) {
7484             assumeLayout();
7485         }
7486
7487         if (mMovement != null) {
7488             /* This code also provides auto-scrolling when a cursor is moved using a
7489              * CursorController (insertion point or selection limits).
7490              * For selection, ensure start or end is visible depending on controller's state.
7491              */
7492             int curs = getSelectionEnd();
7493             // Do not create the controller if it is not already created.
7494             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
7495                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
7496                 curs = getSelectionStart();
7497             }
7498
7499             /*
7500              * TODO: This should really only keep the end in view if
7501              * it already was before the text changed.  I'm not sure
7502              * of a good way to tell from here if it was.
7503              */
7504             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7505                 curs = mText.length();
7506             }
7507
7508             if (curs >= 0) {
7509                 bringPointIntoView(curs);
7510             }
7511         } else {
7512             bringTextIntoView();
7513         }
7514
7515         // This has to be checked here since:
7516         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
7517         //   a screen rotation) since layout is not yet initialized at that point.
7518         if (mEditor != null && mEditor.mCreatedWithASelection) {
7519             mEditor.refreshTextActionMode();
7520             mEditor.mCreatedWithASelection = false;
7521         }
7522
7523         unregisterForPreDraw();
7524
7525         return true;
7526     }
7527
7528     @Override
7529     protected void onAttachedToWindow() {
7530         super.onAttachedToWindow();
7531
7532         if (mEditor != null) mEditor.onAttachedToWindow();
7533
7534         if (mPreDrawListenerDetached) {
7535             getViewTreeObserver().addOnPreDrawListener(this);
7536             mPreDrawListenerDetached = false;
7537         }
7538     }
7539
7540     /** @hide */
7541     @Override
7542     protected void onDetachedFromWindowInternal() {
7543         if (mPreDrawRegistered) {
7544             getViewTreeObserver().removeOnPreDrawListener(this);
7545             mPreDrawListenerDetached = true;
7546         }
7547
7548         resetResolvedDrawables();
7549
7550         if (mEditor != null) mEditor.onDetachedFromWindow();
7551
7552         super.onDetachedFromWindowInternal();
7553     }
7554
7555     @Override
7556     public void onScreenStateChanged(int screenState) {
7557         super.onScreenStateChanged(screenState);
7558         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
7559     }
7560
7561     @Override
7562     protected boolean isPaddingOffsetRequired() {
7563         return mShadowRadius != 0 || mDrawables != null;
7564     }
7565
7566     @Override
7567     protected int getLeftPaddingOffset() {
7568         return getCompoundPaddingLeft() - mPaddingLeft
7569                 + (int) Math.min(0, mShadowDx - mShadowRadius);
7570     }
7571
7572     @Override
7573     protected int getTopPaddingOffset() {
7574         return (int) Math.min(0, mShadowDy - mShadowRadius);
7575     }
7576
7577     @Override
7578     protected int getBottomPaddingOffset() {
7579         return (int) Math.max(0, mShadowDy + mShadowRadius);
7580     }
7581
7582     @Override
7583     protected int getRightPaddingOffset() {
7584         return -(getCompoundPaddingRight() - mPaddingRight)
7585                 + (int) Math.max(0, mShadowDx + mShadowRadius);
7586     }
7587
7588     @Override
7589     protected boolean verifyDrawable(@NonNull Drawable who) {
7590         final boolean verified = super.verifyDrawable(who);
7591         if (!verified && mDrawables != null) {
7592             for (Drawable dr : mDrawables.mShowing) {
7593                 if (who == dr) {
7594                     return true;
7595                 }
7596             }
7597         }
7598         return verified;
7599     }
7600
7601     @Override
7602     public void jumpDrawablesToCurrentState() {
7603         super.jumpDrawablesToCurrentState();
7604         if (mDrawables != null) {
7605             for (Drawable dr : mDrawables.mShowing) {
7606                 if (dr != null) {
7607                     dr.jumpToCurrentState();
7608                 }
7609             }
7610         }
7611     }
7612
7613     @Override
7614     public void invalidateDrawable(@NonNull Drawable drawable) {
7615         boolean handled = false;
7616
7617         if (verifyDrawable(drawable)) {
7618             final Rect dirty = drawable.getBounds();
7619             int scrollX = mScrollX;
7620             int scrollY = mScrollY;
7621
7622             // IMPORTANT: The coordinates below are based on the coordinates computed
7623             // for each compound drawable in onDraw(). Make sure to update each section
7624             // accordingly.
7625             final TextView.Drawables drawables = mDrawables;
7626             if (drawables != null) {
7627                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
7628                     final int compoundPaddingTop = getCompoundPaddingTop();
7629                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7630                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7631
7632                     scrollX += mPaddingLeft;
7633                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
7634                     handled = true;
7635                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
7636                     final int compoundPaddingTop = getCompoundPaddingTop();
7637                     final int compoundPaddingBottom = getCompoundPaddingBottom();
7638                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7639
7640                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
7641                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
7642                     handled = true;
7643                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
7644                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7645                     final int compoundPaddingRight = getCompoundPaddingRight();
7646                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7647
7648                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
7649                     scrollY += mPaddingTop;
7650                     handled = true;
7651                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
7652                     final int compoundPaddingLeft = getCompoundPaddingLeft();
7653                     final int compoundPaddingRight = getCompoundPaddingRight();
7654                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
7655
7656                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
7657                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
7658                     handled = true;
7659                 }
7660             }
7661
7662             if (handled) {
7663                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
7664                         dirty.right + scrollX, dirty.bottom + scrollY);
7665             }
7666         }
7667
7668         if (!handled) {
7669             super.invalidateDrawable(drawable);
7670         }
7671     }
7672
7673     @Override
7674     public boolean hasOverlappingRendering() {
7675         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
7676         return ((getBackground() != null && getBackground().getCurrent() != null)
7677                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
7678                 || mShadowColor != 0);
7679     }
7680
7681     /**
7682      *
7683      * Returns the state of the {@code textIsSelectable} flag (See
7684      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
7685      * to allow users to select and copy text in a non-editable TextView, the content of an
7686      * {@link EditText} can always be selected, independently of the value of this flag.
7687      * <p>
7688      *
7689      * @return True if the text displayed in this TextView can be selected by the user.
7690      *
7691      * @attr ref android.R.styleable#TextView_textIsSelectable
7692      */
7693     @InspectableProperty(name = "textIsSelectable")
7694     public boolean isTextSelectable() {
7695         return mEditor == null ? false : mEditor.mTextIsSelectable;
7696     }
7697
7698     /**
7699      * Sets whether the content of this view is selectable by the user. The default is
7700      * {@code false}, meaning that the content is not selectable.
7701      * <p>
7702      * When you use a TextView to display a useful piece of information to the user (such as a
7703      * contact's address), make it selectable, so that the user can select and copy its
7704      * content. You can also use set the XML attribute
7705      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
7706      * <p>
7707      * When you call this method to set the value of {@code textIsSelectable}, it sets
7708      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
7709      * and {@code longClickable} to the same value. These flags correspond to the attributes
7710      * {@link android.R.styleable#View_focusable android:focusable},
7711      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
7712      * {@link android.R.styleable#View_clickable android:clickable}, and
7713      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
7714      * flags to a state you had set previously, call one or more of the following methods:
7715      * {@link #setFocusable(boolean) setFocusable()},
7716      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
7717      * {@link #setClickable(boolean) setClickable()} or
7718      * {@link #setLongClickable(boolean) setLongClickable()}.
7719      *
7720      * @param selectable Whether the content of this TextView should be selectable.
7721      */
7722     public void setTextIsSelectable(boolean selectable) {
7723         if (!selectable && mEditor == null) return; // false is default value with no edit data
7724
7725         createEditorIfNeeded();
7726         if (mEditor.mTextIsSelectable == selectable) return;
7727
7728         mEditor.mTextIsSelectable = selectable;
7729         setFocusableInTouchMode(selectable);
7730         setFocusable(FOCUSABLE_AUTO);
7731         setClickable(selectable);
7732         setLongClickable(selectable);
7733
7734         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
7735
7736         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
7737         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
7738
7739         // Called by setText above, but safer in case of future code changes
7740         mEditor.prepareCursorControllers();
7741     }
7742
7743     @Override
7744     protected int[] onCreateDrawableState(int extraSpace) {
7745         final int[] drawableState;
7746
7747         if (mSingleLine) {
7748             drawableState = super.onCreateDrawableState(extraSpace);
7749         } else {
7750             drawableState = super.onCreateDrawableState(extraSpace + 1);
7751             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
7752         }
7753
7754         if (isTextSelectable()) {
7755             // Disable pressed state, which was introduced when TextView was made clickable.
7756             // Prevents text color change.
7757             // setClickable(false) would have a similar effect, but it also disables focus changes
7758             // and long press actions, which are both needed by text selection.
7759             final int length = drawableState.length;
7760             for (int i = 0; i < length; i++) {
7761                 if (drawableState[i] == R.attr.state_pressed) {
7762                     final int[] nonPressedState = new int[length - 1];
7763                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
7764                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
7765                     return nonPressedState;
7766                 }
7767             }
7768         }
7769
7770         return drawableState;
7771     }
7772
7773     @UnsupportedAppUsage
7774     private Path getUpdatedHighlightPath() {
7775         Path highlight = null;
7776         Paint highlightPaint = mHighlightPaint;
7777
7778         final int selStart = getSelectionStart();
7779         final int selEnd = getSelectionEnd();
7780         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
7781             if (selStart == selEnd) {
7782                 if (mEditor != null && mEditor.shouldRenderCursor()) {
7783                     if (mHighlightPathBogus) {
7784                         if (mHighlightPath == null) mHighlightPath = new Path();
7785                         mHighlightPath.reset();
7786                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
7787                         mEditor.updateCursorPosition();
7788                         mHighlightPathBogus = false;
7789                     }
7790
7791                     // XXX should pass to skin instead of drawing directly
7792                     highlightPaint.setColor(mCurTextColor);
7793                     highlightPaint.setStyle(Paint.Style.STROKE);
7794                     highlight = mHighlightPath;
7795                 }
7796             } else {
7797                 if (mHighlightPathBogus) {
7798                     if (mHighlightPath == null) mHighlightPath = new Path();
7799                     mHighlightPath.reset();
7800                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7801                     mHighlightPathBogus = false;
7802                 }
7803
7804                 // XXX should pass to skin instead of drawing directly
7805                 highlightPaint.setColor(mHighlightColor);
7806                 highlightPaint.setStyle(Paint.Style.FILL);
7807
7808                 highlight = mHighlightPath;
7809             }
7810         }
7811         return highlight;
7812     }
7813
7814     /**
7815      * @hide
7816      */
7817     public int getHorizontalOffsetForDrawables() {
7818         return 0;
7819     }
7820
7821     @Override
7822     protected void onDraw(Canvas canvas) {
7823         restartMarqueeIfNeeded();
7824
7825         // Draw the background for this view
7826         super.onDraw(canvas);
7827
7828         final int compoundPaddingLeft = getCompoundPaddingLeft();
7829         final int compoundPaddingTop = getCompoundPaddingTop();
7830         final int compoundPaddingRight = getCompoundPaddingRight();
7831         final int compoundPaddingBottom = getCompoundPaddingBottom();
7832         final int scrollX = mScrollX;
7833         final int scrollY = mScrollY;
7834         final int right = mRight;
7835         final int left = mLeft;
7836         final int bottom = mBottom;
7837         final int top = mTop;
7838         final boolean isLayoutRtl = isLayoutRtl();
7839         final int offset = getHorizontalOffsetForDrawables();
7840         final int leftOffset = isLayoutRtl ? 0 : offset;
7841         final int rightOffset = isLayoutRtl ? offset : 0;
7842
7843         final Drawables dr = mDrawables;
7844         if (dr != null) {
7845             /*
7846              * Compound, not extended, because the icon is not clipped
7847              * if the text height is smaller.
7848              */
7849
7850             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
7851             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
7852
7853             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7854             // Make sure to update invalidateDrawable() when changing this code.
7855             if (dr.mShowing[Drawables.LEFT] != null) {
7856                 canvas.save();
7857                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
7858                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
7859                 dr.mShowing[Drawables.LEFT].draw(canvas);
7860                 canvas.restore();
7861             }
7862
7863             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7864             // Make sure to update invalidateDrawable() when changing this code.
7865             if (dr.mShowing[Drawables.RIGHT] != null) {
7866                 canvas.save();
7867                 canvas.translate(scrollX + right - left - mPaddingRight
7868                         - dr.mDrawableSizeRight - rightOffset,
7869                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
7870                 dr.mShowing[Drawables.RIGHT].draw(canvas);
7871                 canvas.restore();
7872             }
7873
7874             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7875             // Make sure to update invalidateDrawable() when changing this code.
7876             if (dr.mShowing[Drawables.TOP] != null) {
7877                 canvas.save();
7878                 canvas.translate(scrollX + compoundPaddingLeft
7879                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
7880                 dr.mShowing[Drawables.TOP].draw(canvas);
7881                 canvas.restore();
7882             }
7883
7884             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7885             // Make sure to update invalidateDrawable() when changing this code.
7886             if (dr.mShowing[Drawables.BOTTOM] != null) {
7887                 canvas.save();
7888                 canvas.translate(scrollX + compoundPaddingLeft
7889                         + (hspace - dr.mDrawableWidthBottom) / 2,
7890                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
7891                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
7892                 canvas.restore();
7893             }
7894         }
7895
7896         int color = mCurTextColor;
7897
7898         if (mLayout == null) {
7899             assumeLayout();
7900         }
7901
7902         Layout layout = mLayout;
7903
7904         if (mHint != null && mText.length() == 0) {
7905             if (mHintTextColor != null) {
7906                 color = mCurHintTextColor;
7907             }
7908
7909             layout = mHintLayout;
7910         }
7911
7912         mTextPaint.setColor(color);
7913         mTextPaint.drawableState = getDrawableState();
7914
7915         canvas.save();
7916         /*  Would be faster if we didn't have to do this. Can we chop the
7917             (displayable) text so that we don't need to do this ever?
7918         */
7919
7920         int extendedPaddingTop = getExtendedPaddingTop();
7921         int extendedPaddingBottom = getExtendedPaddingBottom();
7922
7923         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7924         final int maxScrollY = mLayout.getHeight() - vspace;
7925
7926         float clipLeft = compoundPaddingLeft + scrollX;
7927         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
7928         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
7929         float clipBottom = bottom - top + scrollY
7930                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
7931
7932         if (mShadowRadius != 0) {
7933             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
7934             clipRight += Math.max(0, mShadowDx + mShadowRadius);
7935
7936             clipTop += Math.min(0, mShadowDy - mShadowRadius);
7937             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
7938         }
7939
7940         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
7941
7942         int voffsetText = 0;
7943         int voffsetCursor = 0;
7944
7945         // translate in by our padding
7946         /* shortcircuit calling getVerticaOffset() */
7947         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7948             voffsetText = getVerticalOffset(false);
7949             voffsetCursor = getVerticalOffset(true);
7950         }
7951         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
7952
7953         final int layoutDirection = getLayoutDirection();
7954         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7955         if (isMarqueeFadeEnabled()) {
7956             if (!mSingleLine && getLineCount() == 1 && canMarquee()
7957                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
7958                 final int width = mRight - mLeft;
7959                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
7960                 final float dx = mLayout.getLineRight(0) - (width - padding);
7961                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7962             }
7963
7964             if (mMarquee != null && mMarquee.isRunning()) {
7965                 final float dx = -mMarquee.getScroll();
7966                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7967             }
7968         }
7969
7970         final int cursorOffsetVertical = voffsetCursor - voffsetText;
7971
7972         Path highlight = getUpdatedHighlightPath();
7973         if (mEditor != null) {
7974             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
7975         } else {
7976             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7977         }
7978
7979         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
7980             final float dx = mMarquee.getGhostOffset();
7981             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7982             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7983         }
7984
7985         canvas.restore();
7986     }
7987
7988     @Override
7989     public void getFocusedRect(Rect r) {
7990         if (mLayout == null) {
7991             super.getFocusedRect(r);
7992             return;
7993         }
7994
7995         int selEnd = getSelectionEnd();
7996         if (selEnd < 0) {
7997             super.getFocusedRect(r);
7998             return;
7999         }
8000
8001         int selStart = getSelectionStart();
8002         if (selStart < 0 || selStart >= selEnd) {
8003             int line = mLayout.getLineForOffset(selEnd);
8004             r.top = mLayout.getLineTop(line);
8005             r.bottom = mLayout.getLineBottom(line);
8006             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
8007             r.right = r.left + 4;
8008         } else {
8009             int lineStart = mLayout.getLineForOffset(selStart);
8010             int lineEnd = mLayout.getLineForOffset(selEnd);
8011             r.top = mLayout.getLineTop(lineStart);
8012             r.bottom = mLayout.getLineBottom(lineEnd);
8013             if (lineStart == lineEnd) {
8014                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
8015                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
8016             } else {
8017                 // Selection extends across multiple lines -- make the focused
8018                 // rect cover the entire width.
8019                 if (mHighlightPathBogus) {
8020                     if (mHighlightPath == null) mHighlightPath = new Path();
8021                     mHighlightPath.reset();
8022                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
8023                     mHighlightPathBogus = false;
8024                 }
8025                 synchronized (TEMP_RECTF) {
8026                     mHighlightPath.computeBounds(TEMP_RECTF, true);
8027                     r.left = (int) TEMP_RECTF.left - 1;
8028                     r.right = (int) TEMP_RECTF.right + 1;
8029                 }
8030             }
8031         }
8032
8033         // Adjust for padding and gravity.
8034         int paddingLeft = getCompoundPaddingLeft();
8035         int paddingTop = getExtendedPaddingTop();
8036         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8037             paddingTop += getVerticalOffset(false);
8038         }
8039         r.offset(paddingLeft, paddingTop);
8040         int paddingBottom = getExtendedPaddingBottom();
8041         r.bottom += paddingBottom;
8042     }
8043
8044     /**
8045      * Return the number of lines of text, or 0 if the internal Layout has not
8046      * been built.
8047      */
8048     public int getLineCount() {
8049         return mLayout != null ? mLayout.getLineCount() : 0;
8050     }
8051
8052     /**
8053      * Return the baseline for the specified line (0...getLineCount() - 1)
8054      * If bounds is not null, return the top, left, right, bottom extents
8055      * of the specified line in it. If the internal Layout has not been built,
8056      * return 0 and set bounds to (0, 0, 0, 0)
8057      * @param line which line to examine (0..getLineCount() - 1)
8058      * @param bounds Optional. If not null, it returns the extent of the line
8059      * @return the Y-coordinate of the baseline
8060      */
8061     public int getLineBounds(int line, Rect bounds) {
8062         if (mLayout == null) {
8063             if (bounds != null) {
8064                 bounds.set(0, 0, 0, 0);
8065             }
8066             return 0;
8067         } else {
8068             int baseline = mLayout.getLineBounds(line, bounds);
8069
8070             int voffset = getExtendedPaddingTop();
8071             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8072                 voffset += getVerticalOffset(true);
8073             }
8074             if (bounds != null) {
8075                 bounds.offset(getCompoundPaddingLeft(), voffset);
8076             }
8077             return baseline + voffset;
8078         }
8079     }
8080
8081     @Override
8082     public int getBaseline() {
8083         if (mLayout == null) {
8084             return super.getBaseline();
8085         }
8086
8087         return getBaselineOffset() + mLayout.getLineBaseline(0);
8088     }
8089
8090     int getBaselineOffset() {
8091         int voffset = 0;
8092         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8093             voffset = getVerticalOffset(true);
8094         }
8095
8096         if (isLayoutModeOptical(mParent)) {
8097             voffset -= getOpticalInsets().top;
8098         }
8099
8100         return getExtendedPaddingTop() + voffset;
8101     }
8102
8103     /**
8104      * @hide
8105      */
8106     @Override
8107     protected int getFadeTop(boolean offsetRequired) {
8108         if (mLayout == null) return 0;
8109
8110         int voffset = 0;
8111         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8112             voffset = getVerticalOffset(true);
8113         }
8114
8115         if (offsetRequired) voffset += getTopPaddingOffset();
8116
8117         return getExtendedPaddingTop() + voffset;
8118     }
8119
8120     /**
8121      * @hide
8122      */
8123     @Override
8124     protected int getFadeHeight(boolean offsetRequired) {
8125         return mLayout != null ? mLayout.getHeight() : 0;
8126     }
8127
8128     @Override
8129     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
8130         if (mSpannable != null && mLinksClickable) {
8131             final float x = event.getX(pointerIndex);
8132             final float y = event.getY(pointerIndex);
8133             final int offset = getOffsetForPosition(x, y);
8134             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
8135                     ClickableSpan.class);
8136             if (clickables.length > 0) {
8137                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
8138             }
8139         }
8140         if (isTextSelectable() || isTextEditable()) {
8141             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
8142         }
8143         return super.onResolvePointerIcon(event, pointerIndex);
8144     }
8145
8146     @Override
8147     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
8148         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
8149         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
8150         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
8151         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
8152             return true;
8153         }
8154         return super.onKeyPreIme(keyCode, event);
8155     }
8156
8157     /**
8158      * @hide
8159      */
8160     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
8161         // Do nothing unless mEditor is in text action mode.
8162         if (mEditor == null || mEditor.getTextActionMode() == null) {
8163             return false;
8164         }
8165
8166         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
8167             KeyEvent.DispatcherState state = getKeyDispatcherState();
8168             if (state != null) {
8169                 state.startTracking(event, this);
8170             }
8171             return true;
8172         } else if (event.getAction() == KeyEvent.ACTION_UP) {
8173             KeyEvent.DispatcherState state = getKeyDispatcherState();
8174             if (state != null) {
8175                 state.handleUpEvent(event);
8176             }
8177             if (event.isTracking() && !event.isCanceled()) {
8178                 stopTextActionMode();
8179                 return true;
8180             }
8181         }
8182         return false;
8183     }
8184
8185     @Override
8186     public boolean onKeyDown(int keyCode, KeyEvent event) {
8187         final int which = doKeyDown(keyCode, event, null);
8188         if (which == KEY_EVENT_NOT_HANDLED) {
8189             return super.onKeyDown(keyCode, event);
8190         }
8191
8192         return true;
8193     }
8194
8195     @Override
8196     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
8197         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
8198         final int which = doKeyDown(keyCode, down, event);
8199         if (which == KEY_EVENT_NOT_HANDLED) {
8200             // Go through default dispatching.
8201             return super.onKeyMultiple(keyCode, repeatCount, event);
8202         }
8203         if (which == KEY_EVENT_HANDLED) {
8204             // Consumed the whole thing.
8205             return true;
8206         }
8207
8208         repeatCount--;
8209
8210         // We are going to dispatch the remaining events to either the input
8211         // or movement method.  To do this, we will just send a repeated stream
8212         // of down and up events until we have done the complete repeatCount.
8213         // It would be nice if those interfaces had an onKeyMultiple() method,
8214         // but adding that is a more complicated change.
8215         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
8216         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
8217             // mEditor and mEditor.mInput are not null from doKeyDown
8218             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8219             while (--repeatCount > 0) {
8220                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
8221                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
8222             }
8223             hideErrorIfUnchanged();
8224
8225         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
8226             // mMovement is not null from doKeyDown
8227             mMovement.onKeyUp(this, mSpannable, keyCode, up);
8228             while (--repeatCount > 0) {
8229                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
8230                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
8231             }
8232         }
8233
8234         return true;
8235     }
8236
8237     /**
8238      * Returns true if pressing ENTER in this field advances focus instead
8239      * of inserting the character.  This is true mostly in single-line fields,
8240      * but also in mail addresses and subjects which will display on multiple
8241      * lines but where it doesn't make sense to insert newlines.
8242      */
8243     private boolean shouldAdvanceFocusOnEnter() {
8244         if (getKeyListener() == null) {
8245             return false;
8246         }
8247
8248         if (mSingleLine) {
8249             return true;
8250         }
8251
8252         if (mEditor != null
8253                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8254                         == EditorInfo.TYPE_CLASS_TEXT) {
8255             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8256             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
8257                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
8258                 return true;
8259             }
8260         }
8261
8262         return false;
8263     }
8264
8265     /**
8266      * Returns true if pressing TAB in this field advances focus instead
8267      * of inserting the character.  Insert tabs only in multi-line editors.
8268      */
8269     private boolean shouldAdvanceFocusOnTab() {
8270         if (getKeyListener() != null && !mSingleLine && mEditor != null
8271                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8272                         == EditorInfo.TYPE_CLASS_TEXT) {
8273             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8274             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
8275                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
8276                 return false;
8277             }
8278         }
8279         return true;
8280     }
8281
8282     private boolean isDirectionalNavigationKey(int keyCode) {
8283         switch(keyCode) {
8284             case KeyEvent.KEYCODE_DPAD_UP:
8285             case KeyEvent.KEYCODE_DPAD_DOWN:
8286             case KeyEvent.KEYCODE_DPAD_LEFT:
8287             case KeyEvent.KEYCODE_DPAD_RIGHT:
8288                 return true;
8289         }
8290         return false;
8291     }
8292
8293     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
8294         if (!isEnabled()) {
8295             return KEY_EVENT_NOT_HANDLED;
8296         }
8297
8298         // If this is the initial keydown, we don't want to prevent a movement away from this view.
8299         // While this shouldn't be necessary because any time we're preventing default movement we
8300         // should be restricting the focus to remain within this view, thus we'll also receive
8301         // the key up event, occasionally key up events will get dropped and we don't want to
8302         // prevent the user from traversing out of this on the next key down.
8303         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8304             mPreventDefaultMovement = false;
8305         }
8306
8307         switch (keyCode) {
8308             case KeyEvent.KEYCODE_ENTER:
8309                 if (event.hasNoModifiers()) {
8310                     // When mInputContentType is set, we know that we are
8311                     // running in a "modern" cupcake environment, so don't need
8312                     // to worry about the application trying to capture
8313                     // enter key events.
8314                     if (mEditor != null && mEditor.mInputContentType != null) {
8315                         // If there is an action listener, given them a
8316                         // chance to consume the event.
8317                         if (mEditor.mInputContentType.onEditorActionListener != null
8318                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8319                                         this, EditorInfo.IME_NULL, event)) {
8320                             mEditor.mInputContentType.enterDown = true;
8321                             // We are consuming the enter key for them.
8322                             return KEY_EVENT_HANDLED;
8323                         }
8324                     }
8325
8326                     // If our editor should move focus when enter is pressed, or
8327                     // this is a generated event from an IME action button, then
8328                     // don't let it be inserted into the text.
8329                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8330                             || shouldAdvanceFocusOnEnter()) {
8331                         if (hasOnClickListeners()) {
8332                             return KEY_EVENT_NOT_HANDLED;
8333                         }
8334                         return KEY_EVENT_HANDLED;
8335                     }
8336                 }
8337                 break;
8338
8339             case KeyEvent.KEYCODE_DPAD_CENTER:
8340                 if (event.hasNoModifiers()) {
8341                     if (shouldAdvanceFocusOnEnter()) {
8342                         return KEY_EVENT_NOT_HANDLED;
8343                     }
8344                 }
8345                 break;
8346
8347             case KeyEvent.KEYCODE_TAB:
8348                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
8349                     if (shouldAdvanceFocusOnTab()) {
8350                         return KEY_EVENT_NOT_HANDLED;
8351                     }
8352                 }
8353                 break;
8354
8355                 // Has to be done on key down (and not on key up) to correctly be intercepted.
8356             case KeyEvent.KEYCODE_BACK:
8357                 if (mEditor != null && mEditor.getTextActionMode() != null) {
8358                     stopTextActionMode();
8359                     return KEY_EVENT_HANDLED;
8360                 }
8361                 break;
8362
8363             case KeyEvent.KEYCODE_CUT:
8364                 if (event.hasNoModifiers() && canCut()) {
8365                     if (onTextContextMenuItem(ID_CUT)) {
8366                         return KEY_EVENT_HANDLED;
8367                     }
8368                 }
8369                 break;
8370
8371             case KeyEvent.KEYCODE_COPY:
8372                 if (event.hasNoModifiers() && canCopy()) {
8373                     if (onTextContextMenuItem(ID_COPY)) {
8374                         return KEY_EVENT_HANDLED;
8375                     }
8376                 }
8377                 break;
8378
8379             case KeyEvent.KEYCODE_PASTE:
8380                 if (event.hasNoModifiers() && canPaste()) {
8381                     if (onTextContextMenuItem(ID_PASTE)) {
8382                         return KEY_EVENT_HANDLED;
8383                     }
8384                 }
8385                 break;
8386
8387             case KeyEvent.KEYCODE_FORWARD_DEL:
8388                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
8389                     if (onTextContextMenuItem(ID_CUT)) {
8390                         return KEY_EVENT_HANDLED;
8391                     }
8392                 }
8393                 break;
8394
8395             case KeyEvent.KEYCODE_INSERT:
8396                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
8397                     if (onTextContextMenuItem(ID_COPY)) {
8398                         return KEY_EVENT_HANDLED;
8399                     }
8400                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
8401                     if (onTextContextMenuItem(ID_PASTE)) {
8402                         return KEY_EVENT_HANDLED;
8403                     }
8404                 }
8405                 break;
8406         }
8407
8408         if (mEditor != null && mEditor.mKeyListener != null) {
8409             boolean doDown = true;
8410             if (otherEvent != null) {
8411                 try {
8412                     beginBatchEdit();
8413                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
8414                             otherEvent);
8415                     hideErrorIfUnchanged();
8416                     doDown = false;
8417                     if (handled) {
8418                         return KEY_EVENT_HANDLED;
8419                     }
8420                 } catch (AbstractMethodError e) {
8421                     // onKeyOther was added after 1.0, so if it isn't
8422                     // implemented we need to try to dispatch as a regular down.
8423                 } finally {
8424                     endBatchEdit();
8425                 }
8426             }
8427
8428             if (doDown) {
8429                 beginBatchEdit();
8430                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
8431                         keyCode, event);
8432                 endBatchEdit();
8433                 hideErrorIfUnchanged();
8434                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
8435             }
8436         }
8437
8438         // bug 650865: sometimes we get a key event before a layout.
8439         // don't try to move around if we don't know the layout.
8440
8441         if (mMovement != null && mLayout != null) {
8442             boolean doDown = true;
8443             if (otherEvent != null) {
8444                 try {
8445                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
8446                     doDown = false;
8447                     if (handled) {
8448                         return KEY_EVENT_HANDLED;
8449                     }
8450                 } catch (AbstractMethodError e) {
8451                     // onKeyOther was added after 1.0, so if it isn't
8452                     // implemented we need to try to dispatch as a regular down.
8453                 }
8454             }
8455             if (doDown) {
8456                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
8457                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
8458                         mPreventDefaultMovement = true;
8459                     }
8460                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
8461                 }
8462             }
8463             // Consume arrows from keyboard devices to prevent focus leaving the editor.
8464             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
8465             // to move focus with arrows.
8466             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
8467                     && isDirectionalNavigationKey(keyCode)) {
8468                 return KEY_EVENT_HANDLED;
8469             }
8470         }
8471
8472         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
8473                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
8474     }
8475
8476     /**
8477      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
8478      * can be recorded.
8479      * @hide
8480      */
8481     public void resetErrorChangedFlag() {
8482         /*
8483          * Keep track of what the error was before doing the input
8484          * so that if an input filter changed the error, we leave
8485          * that error showing.  Otherwise, we take down whatever
8486          * error was showing when the user types something.
8487          */
8488         if (mEditor != null) mEditor.mErrorWasChanged = false;
8489     }
8490
8491     /**
8492      * @hide
8493      */
8494     public void hideErrorIfUnchanged() {
8495         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
8496             setError(null, null);
8497         }
8498     }
8499
8500     @Override
8501     public boolean onKeyUp(int keyCode, KeyEvent event) {
8502         if (!isEnabled()) {
8503             return super.onKeyUp(keyCode, event);
8504         }
8505
8506         if (!KeyEvent.isModifierKey(keyCode)) {
8507             mPreventDefaultMovement = false;
8508         }
8509
8510         switch (keyCode) {
8511             case KeyEvent.KEYCODE_DPAD_CENTER:
8512                 if (event.hasNoModifiers()) {
8513                     /*
8514                      * If there is a click listener, just call through to
8515                      * super, which will invoke it.
8516                      *
8517                      * If there isn't a click listener, try to show the soft
8518                      * input method.  (It will also
8519                      * call performClick(), but that won't do anything in
8520                      * this case.)
8521                      */
8522                     if (!hasOnClickListeners()) {
8523                         if (mMovement != null && mText instanceof Editable
8524                                 && mLayout != null && onCheckIsTextEditor()) {
8525                             InputMethodManager imm = getInputMethodManager();
8526                             viewClicked(imm);
8527                             if (imm != null && getShowSoftInputOnFocus()) {
8528                                 imm.showSoftInput(this, 0);
8529                             }
8530                         }
8531                     }
8532                 }
8533                 return super.onKeyUp(keyCode, event);
8534
8535             case KeyEvent.KEYCODE_ENTER:
8536                 if (event.hasNoModifiers()) {
8537                     if (mEditor != null && mEditor.mInputContentType != null
8538                             && mEditor.mInputContentType.onEditorActionListener != null
8539                             && mEditor.mInputContentType.enterDown) {
8540                         mEditor.mInputContentType.enterDown = false;
8541                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
8542                                 this, EditorInfo.IME_NULL, event)) {
8543                             return true;
8544                         }
8545                     }
8546
8547                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
8548                             || shouldAdvanceFocusOnEnter()) {
8549                         /*
8550                          * If there is a click listener, just call through to
8551                          * super, which will invoke it.
8552                          *
8553                          * If there isn't a click listener, try to advance focus,
8554                          * but still call through to super, which will reset the
8555                          * pressed state and longpress state.  (It will also
8556                          * call performClick(), but that won't do anything in
8557                          * this case.)
8558                          */
8559                         if (!hasOnClickListeners()) {
8560                             View v = focusSearch(FOCUS_DOWN);
8561
8562                             if (v != null) {
8563                                 if (!v.requestFocus(FOCUS_DOWN)) {
8564                                     throw new IllegalStateException("focus search returned a view "
8565                                             + "that wasn't able to take focus!");
8566                                 }
8567
8568                                 /*
8569                                  * Return true because we handled the key; super
8570                                  * will return false because there was no click
8571                                  * listener.
8572                                  */
8573                                 super.onKeyUp(keyCode, event);
8574                                 return true;
8575                             } else if ((event.getFlags()
8576                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
8577                                 // No target for next focus, but make sure the IME
8578                                 // if this came from it.
8579                                 InputMethodManager imm = getInputMethodManager();
8580                                 if (imm != null && imm.isActive(this)) {
8581                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
8582                                 }
8583                             }
8584                         }
8585                     }
8586                     return super.onKeyUp(keyCode, event);
8587                 }
8588                 break;
8589         }
8590
8591         if (mEditor != null && mEditor.mKeyListener != null) {
8592             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
8593                 return true;
8594             }
8595         }
8596
8597         if (mMovement != null && mLayout != null) {
8598             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
8599                 return true;
8600             }
8601         }
8602
8603         return super.onKeyUp(keyCode, event);
8604     }
8605
8606     @Override
8607     public boolean onCheckIsTextEditor() {
8608         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
8609     }
8610
8611     @Override
8612     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
8613         if (onCheckIsTextEditor() && isEnabled()) {
8614             mEditor.createInputMethodStateIfNeeded();
8615             outAttrs.inputType = getInputType();
8616             if (mEditor.mInputContentType != null) {
8617                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
8618                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
8619                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
8620                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
8621                 outAttrs.extras = mEditor.mInputContentType.extras;
8622                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
8623             } else {
8624                 outAttrs.imeOptions = EditorInfo.IME_NULL;
8625                 outAttrs.hintLocales = null;
8626             }
8627             if (focusSearch(FOCUS_DOWN) != null) {
8628                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
8629             }
8630             if (focusSearch(FOCUS_UP) != null) {
8631                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
8632             }
8633             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
8634                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
8635                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
8636                     // An action has not been set, but the enter key will move to
8637                     // the next focus, so set the action to that.
8638                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
8639                 } else {
8640                     // An action has not been set, and there is no focus to move
8641                     // to, so let's just supply a "done" action.
8642                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
8643                 }
8644                 if (!shouldAdvanceFocusOnEnter()) {
8645                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8646                 }
8647             }
8648             if (isMultilineInputType(outAttrs.inputType)) {
8649                 // Multi-line text editors should always show an enter key.
8650                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
8651             }
8652             outAttrs.hintText = mHint;
8653             outAttrs.targetInputMethodUser = mTextOperationUser;
8654             if (mText instanceof Editable) {
8655                 InputConnection ic = new EditableInputConnection(this);
8656                 outAttrs.initialSelStart = getSelectionStart();
8657                 outAttrs.initialSelEnd = getSelectionEnd();
8658                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
8659                 return ic;
8660             }
8661         }
8662         return null;
8663     }
8664
8665     /**
8666      * If this TextView contains editable content, extract a portion of it
8667      * based on the information in <var>request</var> in to <var>outText</var>.
8668      * @return Returns true if the text was successfully extracted, else false.
8669      */
8670     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
8671         createEditorIfNeeded();
8672         return mEditor.extractText(request, outText);
8673     }
8674
8675     /**
8676      * This is used to remove all style-impacting spans from text before new
8677      * extracted text is being replaced into it, so that we don't have any
8678      * lingering spans applied during the replace.
8679      */
8680     static void removeParcelableSpans(Spannable spannable, int start, int end) {
8681         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
8682         int i = spans.length;
8683         while (i > 0) {
8684             i--;
8685             spannable.removeSpan(spans[i]);
8686         }
8687     }
8688
8689     /**
8690      * Apply to this text view the given extracted text, as previously
8691      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
8692      */
8693     public void setExtractedText(ExtractedText text) {
8694         Editable content = getEditableText();
8695         if (text.text != null) {
8696             if (content == null) {
8697                 setText(text.text, TextView.BufferType.EDITABLE);
8698             } else {
8699                 int start = 0;
8700                 int end = content.length();
8701
8702                 if (text.partialStartOffset >= 0) {
8703                     final int N = content.length();
8704                     start = text.partialStartOffset;
8705                     if (start > N) start = N;
8706                     end = text.partialEndOffset;
8707                     if (end > N) end = N;
8708                 }
8709
8710                 removeParcelableSpans(content, start, end);
8711                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
8712                     if (text.text instanceof Spanned) {
8713                         // OK to copy spans only.
8714                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
8715                                 Object.class, content, start);
8716                     }
8717                 } else {
8718                     content.replace(start, end, text.text);
8719                 }
8720             }
8721         }
8722
8723         // Now set the selection position...  make sure it is in range, to
8724         // avoid crashes.  If this is a partial update, it is possible that
8725         // the underlying text may have changed, causing us problems here.
8726         // Also we just don't want to trust clients to do the right thing.
8727         Spannable sp = (Spannable) getText();
8728         final int N = sp.length();
8729         int start = text.selectionStart;
8730         if (start < 0) {
8731             start = 0;
8732         } else if (start > N) {
8733             start = N;
8734         }
8735         int end = text.selectionEnd;
8736         if (end < 0) {
8737             end = 0;
8738         } else if (end > N) {
8739             end = N;
8740         }
8741         Selection.setSelection(sp, start, end);
8742
8743         // Finally, update the selection mode.
8744         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
8745             MetaKeyKeyListener.startSelecting(this, sp);
8746         } else {
8747             MetaKeyKeyListener.stopSelecting(this, sp);
8748         }
8749
8750         setHintInternal(text.hint);
8751     }
8752
8753     /**
8754      * @hide
8755      */
8756     public void setExtracting(ExtractedTextRequest req) {
8757         if (mEditor.mInputMethodState != null) {
8758             mEditor.mInputMethodState.mExtractedTextRequest = req;
8759         }
8760         // This would stop a possible selection mode, but no such mode is started in case
8761         // extracted mode will start. Some text is selected though, and will trigger an action mode
8762         // in the extracted view.
8763         mEditor.hideCursorAndSpanControllers();
8764         stopTextActionMode();
8765         if (mEditor.mSelectionModifierCursorController != null) {
8766             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
8767         }
8768     }
8769
8770     /**
8771      * Called by the framework in response to a text completion from
8772      * the current input method, provided by it calling
8773      * {@link InputConnection#commitCompletion
8774      * InputConnection.commitCompletion()}.  The default implementation does
8775      * nothing; text views that are supporting auto-completion should override
8776      * this to do their desired behavior.
8777      *
8778      * @param text The auto complete text the user has selected.
8779      */
8780     public void onCommitCompletion(CompletionInfo text) {
8781         // intentionally empty
8782     }
8783
8784     /**
8785      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
8786      * dictionary) from the current input method, provided by it calling
8787      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
8788      * The default implementation flashes the background of the corrected word to provide
8789      * feedback to the user.
8790      *
8791      * @param info The auto correct info about the text that was corrected.
8792      */
8793     public void onCommitCorrection(CorrectionInfo info) {
8794         if (mEditor != null) mEditor.onCommitCorrection(info);
8795     }
8796
8797     public void beginBatchEdit() {
8798         if (mEditor != null) mEditor.beginBatchEdit();
8799     }
8800
8801     public void endBatchEdit() {
8802         if (mEditor != null) mEditor.endBatchEdit();
8803     }
8804
8805     /**
8806      * Called by the framework in response to a request to begin a batch
8807      * of edit operations through a call to link {@link #beginBatchEdit()}.
8808      */
8809     public void onBeginBatchEdit() {
8810         // intentionally empty
8811     }
8812
8813     /**
8814      * Called by the framework in response to a request to end a batch
8815      * of edit operations through a call to link {@link #endBatchEdit}.
8816      */
8817     public void onEndBatchEdit() {
8818         // intentionally empty
8819     }
8820
8821     /**
8822      * Called by the framework in response to a private command from the
8823      * current method, provided by it calling
8824      * {@link InputConnection#performPrivateCommand
8825      * InputConnection.performPrivateCommand()}.
8826      *
8827      * @param action The action name of the command.
8828      * @param data Any additional data for the command.  This may be null.
8829      * @return Return true if you handled the command, else false.
8830      */
8831     public boolean onPrivateIMECommand(String action, Bundle data) {
8832         return false;
8833     }
8834
8835     /** @hide */
8836     @VisibleForTesting
8837     @UnsupportedAppUsage
8838     public void nullLayouts() {
8839         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
8840             mSavedLayout = (BoringLayout) mLayout;
8841         }
8842         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
8843             mSavedHintLayout = (BoringLayout) mHintLayout;
8844         }
8845
8846         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
8847
8848         mBoring = mHintBoring = null;
8849
8850         // Since it depends on the value of mLayout
8851         if (mEditor != null) mEditor.prepareCursorControllers();
8852     }
8853
8854     /**
8855      * Make a new Layout based on the already-measured size of the view,
8856      * on the assumption that it was measured correctly at some point.
8857      */
8858     @UnsupportedAppUsage
8859     private void assumeLayout() {
8860         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8861
8862         if (width < 1) {
8863             width = 0;
8864         }
8865
8866         int physicalWidth = width;
8867
8868         if (mHorizontallyScrolling) {
8869             width = VERY_WIDE;
8870         }
8871
8872         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
8873                       physicalWidth, false);
8874     }
8875
8876     @UnsupportedAppUsage
8877     private Layout.Alignment getLayoutAlignment() {
8878         Layout.Alignment alignment;
8879         switch (getTextAlignment()) {
8880             case TEXT_ALIGNMENT_GRAVITY:
8881                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
8882                     case Gravity.START:
8883                         alignment = Layout.Alignment.ALIGN_NORMAL;
8884                         break;
8885                     case Gravity.END:
8886                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
8887                         break;
8888                     case Gravity.LEFT:
8889                         alignment = Layout.Alignment.ALIGN_LEFT;
8890                         break;
8891                     case Gravity.RIGHT:
8892                         alignment = Layout.Alignment.ALIGN_RIGHT;
8893                         break;
8894                     case Gravity.CENTER_HORIZONTAL:
8895                         alignment = Layout.Alignment.ALIGN_CENTER;
8896                         break;
8897                     default:
8898                         alignment = Layout.Alignment.ALIGN_NORMAL;
8899                         break;
8900                 }
8901                 break;
8902             case TEXT_ALIGNMENT_TEXT_START:
8903                 alignment = Layout.Alignment.ALIGN_NORMAL;
8904                 break;
8905             case TEXT_ALIGNMENT_TEXT_END:
8906                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
8907                 break;
8908             case TEXT_ALIGNMENT_CENTER:
8909                 alignment = Layout.Alignment.ALIGN_CENTER;
8910                 break;
8911             case TEXT_ALIGNMENT_VIEW_START:
8912                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8913                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8914                 break;
8915             case TEXT_ALIGNMENT_VIEW_END:
8916                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8917                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8918                 break;
8919             case TEXT_ALIGNMENT_INHERIT:
8920                 // This should never happen as we have already resolved the text alignment
8921                 // but better safe than sorry so we just fall through
8922             default:
8923                 alignment = Layout.Alignment.ALIGN_NORMAL;
8924                 break;
8925         }
8926         return alignment;
8927     }
8928
8929     /**
8930      * The width passed in is now the desired layout width,
8931      * not the full view width with padding.
8932      * {@hide}
8933      */
8934     @VisibleForTesting
8935     @UnsupportedAppUsage
8936     public void makeNewLayout(int wantWidth, int hintWidth,
8937                                  BoringLayout.Metrics boring,
8938                                  BoringLayout.Metrics hintBoring,
8939                                  int ellipsisWidth, boolean bringIntoView) {
8940         stopMarquee();
8941
8942         // Update "old" cached values
8943         mOldMaximum = mMaximum;
8944         mOldMaxMode = mMaxMode;
8945
8946         mHighlightPathBogus = true;
8947
8948         if (wantWidth < 0) {
8949             wantWidth = 0;
8950         }
8951         if (hintWidth < 0) {
8952             hintWidth = 0;
8953         }
8954
8955         Layout.Alignment alignment = getLayoutAlignment();
8956         final boolean testDirChange = mSingleLine && mLayout != null
8957                 && (alignment == Layout.Alignment.ALIGN_NORMAL
8958                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
8959         int oldDir = 0;
8960         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
8961         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
8962         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
8963                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
8964         TruncateAt effectiveEllipsize = mEllipsize;
8965         if (mEllipsize == TruncateAt.MARQUEE
8966                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8967             effectiveEllipsize = TruncateAt.END_SMALL;
8968         }
8969
8970         if (mTextDir == null) {
8971             mTextDir = getTextDirectionHeuristic();
8972         }
8973
8974         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
8975                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
8976         if (switchEllipsize) {
8977             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
8978                     ? TruncateAt.END : TruncateAt.MARQUEE;
8979             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
8980                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
8981         }
8982
8983         shouldEllipsize = mEllipsize != null;
8984         mHintLayout = null;
8985
8986         if (mHint != null) {
8987             if (shouldEllipsize) hintWidth = wantWidth;
8988
8989             if (hintBoring == UNKNOWN_BORING) {
8990                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
8991                                                    mHintBoring);
8992                 if (hintBoring != null) {
8993                     mHintBoring = hintBoring;
8994                 }
8995             }
8996
8997             if (hintBoring != null) {
8998                 if (hintBoring.width <= hintWidth
8999                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
9000                     if (mSavedHintLayout != null) {
9001                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9002                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9003                                 hintBoring, mIncludePad);
9004                     } else {
9005                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9006                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9007                                 hintBoring, mIncludePad);
9008                     }
9009
9010                     mSavedHintLayout = (BoringLayout) mHintLayout;
9011                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
9012                     if (mSavedHintLayout != null) {
9013                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
9014                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9015                                 hintBoring, mIncludePad, mEllipsize,
9016                                 ellipsisWidth);
9017                     } else {
9018                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
9019                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
9020                                 hintBoring, mIncludePad, mEllipsize,
9021                                 ellipsisWidth);
9022                     }
9023                 }
9024             }
9025             // TODO: code duplication with makeSingleLayout()
9026             if (mHintLayout == null) {
9027                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
9028                         mHint.length(), mTextPaint, hintWidth)
9029                         .setAlignment(alignment)
9030                         .setTextDirection(mTextDir)
9031                         .setLineSpacing(mSpacingAdd, mSpacingMult)
9032                         .setIncludePad(mIncludePad)
9033                         .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9034                         .setBreakStrategy(mBreakStrategy)
9035                         .setHyphenationFrequency(mHyphenationFrequency)
9036                         .setJustificationMode(mJustificationMode)
9037                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9038                 if (shouldEllipsize) {
9039                     builder.setEllipsize(mEllipsize)
9040                             .setEllipsizedWidth(ellipsisWidth);
9041                 }
9042                 mHintLayout = builder.build();
9043             }
9044         }
9045
9046         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
9047             registerForPreDraw();
9048         }
9049
9050         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9051             if (!compressText(ellipsisWidth)) {
9052                 final int height = mLayoutParams.height;
9053                 // If the size of the view does not depend on the size of the text, try to
9054                 // start the marquee immediately
9055                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
9056                     startMarquee();
9057                 } else {
9058                     // Defer the start of the marquee until we know our width (see setFrame())
9059                     mRestartMarquee = true;
9060                 }
9061             }
9062         }
9063
9064         // CursorControllers need a non-null mLayout
9065         if (mEditor != null) mEditor.prepareCursorControllers();
9066     }
9067
9068     /**
9069      * Returns true if DynamicLayout is required
9070      *
9071      * @hide
9072      */
9073     @VisibleForTesting
9074     public boolean useDynamicLayout() {
9075         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
9076     }
9077
9078     /**
9079      * @hide
9080      */
9081     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
9082             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
9083             boolean useSaved) {
9084         Layout result = null;
9085         if (useDynamicLayout()) {
9086             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
9087                     wantWidth)
9088                     .setDisplayText(mTransformed)
9089                     .setAlignment(alignment)
9090                     .setTextDirection(mTextDir)
9091                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9092                     .setIncludePad(mIncludePad)
9093                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9094                     .setBreakStrategy(mBreakStrategy)
9095                     .setHyphenationFrequency(mHyphenationFrequency)
9096                     .setJustificationMode(mJustificationMode)
9097                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
9098                     .setEllipsizedWidth(ellipsisWidth);
9099             result = builder.build();
9100         } else {
9101             if (boring == UNKNOWN_BORING) {
9102                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9103                 if (boring != null) {
9104                     mBoring = boring;
9105                 }
9106             }
9107
9108             if (boring != null) {
9109                 if (boring.width <= wantWidth
9110                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
9111                     if (useSaved && mSavedLayout != null) {
9112                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9113                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9114                                 boring, mIncludePad);
9115                     } else {
9116                         result = BoringLayout.make(mTransformed, mTextPaint,
9117                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9118                                 boring, mIncludePad);
9119                     }
9120
9121                     if (useSaved) {
9122                         mSavedLayout = (BoringLayout) result;
9123                     }
9124                 } else if (shouldEllipsize && boring.width <= wantWidth) {
9125                     if (useSaved && mSavedLayout != null) {
9126                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
9127                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9128                                 boring, mIncludePad, effectiveEllipsize,
9129                                 ellipsisWidth);
9130                     } else {
9131                         result = BoringLayout.make(mTransformed, mTextPaint,
9132                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
9133                                 boring, mIncludePad, effectiveEllipsize,
9134                                 ellipsisWidth);
9135                     }
9136                 }
9137             }
9138         }
9139         if (result == null) {
9140             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
9141                     0, mTransformed.length(), mTextPaint, wantWidth)
9142                     .setAlignment(alignment)
9143                     .setTextDirection(mTextDir)
9144                     .setLineSpacing(mSpacingAdd, mSpacingMult)
9145                     .setIncludePad(mIncludePad)
9146                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9147                     .setBreakStrategy(mBreakStrategy)
9148                     .setHyphenationFrequency(mHyphenationFrequency)
9149                     .setJustificationMode(mJustificationMode)
9150                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
9151             if (shouldEllipsize) {
9152                 builder.setEllipsize(effectiveEllipsize)
9153                         .setEllipsizedWidth(ellipsisWidth);
9154             }
9155             result = builder.build();
9156         }
9157         return result;
9158     }
9159
9160     @UnsupportedAppUsage
9161     private boolean compressText(float width) {
9162         if (isHardwareAccelerated()) return false;
9163
9164         // Only compress the text if it hasn't been compressed by the previous pass
9165         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
9166                 && mTextPaint.getTextScaleX() == 1.0f) {
9167             final float textWidth = mLayout.getLineWidth(0);
9168             final float overflow = (textWidth + 1.0f - width) / width;
9169             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
9170                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
9171                 post(new Runnable() {
9172                     public void run() {
9173                         requestLayout();
9174                     }
9175                 });
9176                 return true;
9177             }
9178         }
9179
9180         return false;
9181     }
9182
9183     private static int desired(Layout layout) {
9184         int n = layout.getLineCount();
9185         CharSequence text = layout.getText();
9186         float max = 0;
9187
9188         // if any line was wrapped, we can't use it.
9189         // but it's ok for the last line not to have a newline
9190
9191         for (int i = 0; i < n - 1; i++) {
9192             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
9193                 return -1;
9194             }
9195         }
9196
9197         for (int i = 0; i < n; i++) {
9198             max = Math.max(max, layout.getLineWidth(i));
9199         }
9200
9201         return (int) Math.ceil(max);
9202     }
9203
9204     /**
9205      * Set whether the TextView includes extra top and bottom padding to make
9206      * room for accents that go above the normal ascent and descent.
9207      * The default is true.
9208      *
9209      * @see #getIncludeFontPadding()
9210      *
9211      * @attr ref android.R.styleable#TextView_includeFontPadding
9212      */
9213     public void setIncludeFontPadding(boolean includepad) {
9214         if (mIncludePad != includepad) {
9215             mIncludePad = includepad;
9216
9217             if (mLayout != null) {
9218                 nullLayouts();
9219                 requestLayout();
9220                 invalidate();
9221             }
9222         }
9223     }
9224
9225     /**
9226      * Gets whether the TextView includes extra top and bottom padding to make
9227      * room for accents that go above the normal ascent and descent.
9228      *
9229      * @see #setIncludeFontPadding(boolean)
9230      *
9231      * @attr ref android.R.styleable#TextView_includeFontPadding
9232      */
9233     @InspectableProperty
9234     public boolean getIncludeFontPadding() {
9235         return mIncludePad;
9236     }
9237
9238     /** @hide */
9239     @VisibleForTesting
9240     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
9241
9242     @Override
9243     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
9244         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
9245         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
9246         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
9247         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
9248
9249         int width;
9250         int height;
9251
9252         BoringLayout.Metrics boring = UNKNOWN_BORING;
9253         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
9254
9255         if (mTextDir == null) {
9256             mTextDir = getTextDirectionHeuristic();
9257         }
9258
9259         int des = -1;
9260         boolean fromexisting = false;
9261         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
9262                 ?  (float) widthSize : Float.MAX_VALUE;
9263
9264         if (widthMode == MeasureSpec.EXACTLY) {
9265             // Parent has told us how big to be. So be it.
9266             width = widthSize;
9267         } else {
9268             if (mLayout != null && mEllipsize == null) {
9269                 des = desired(mLayout);
9270             }
9271
9272             if (des < 0) {
9273                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
9274                 if (boring != null) {
9275                     mBoring = boring;
9276                 }
9277             } else {
9278                 fromexisting = true;
9279             }
9280
9281             if (boring == null || boring == UNKNOWN_BORING) {
9282                 if (des < 0) {
9283                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
9284                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
9285                 }
9286                 width = des;
9287             } else {
9288                 width = boring.width;
9289             }
9290
9291             final Drawables dr = mDrawables;
9292             if (dr != null) {
9293                 width = Math.max(width, dr.mDrawableWidthTop);
9294                 width = Math.max(width, dr.mDrawableWidthBottom);
9295             }
9296
9297             if (mHint != null) {
9298                 int hintDes = -1;
9299                 int hintWidth;
9300
9301                 if (mHintLayout != null && mEllipsize == null) {
9302                     hintDes = desired(mHintLayout);
9303                 }
9304
9305                 if (hintDes < 0) {
9306                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
9307                     if (hintBoring != null) {
9308                         mHintBoring = hintBoring;
9309                     }
9310                 }
9311
9312                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
9313                     if (hintDes < 0) {
9314                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
9315                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
9316                     }
9317                     hintWidth = hintDes;
9318                 } else {
9319                     hintWidth = hintBoring.width;
9320                 }
9321
9322                 if (hintWidth > width) {
9323                     width = hintWidth;
9324                 }
9325             }
9326
9327             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
9328
9329             if (mMaxWidthMode == EMS) {
9330                 width = Math.min(width, mMaxWidth * getLineHeight());
9331             } else {
9332                 width = Math.min(width, mMaxWidth);
9333             }
9334
9335             if (mMinWidthMode == EMS) {
9336                 width = Math.max(width, mMinWidth * getLineHeight());
9337             } else {
9338                 width = Math.max(width, mMinWidth);
9339             }
9340
9341             // Check against our minimum width
9342             width = Math.max(width, getSuggestedMinimumWidth());
9343
9344             if (widthMode == MeasureSpec.AT_MOST) {
9345                 width = Math.min(widthSize, width);
9346             }
9347         }
9348
9349         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
9350         int unpaddedWidth = want;
9351
9352         if (mHorizontallyScrolling) want = VERY_WIDE;
9353
9354         int hintWant = want;
9355         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
9356
9357         if (mLayout == null) {
9358             makeNewLayout(want, hintWant, boring, hintBoring,
9359                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9360         } else {
9361             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
9362                     || (mLayout.getEllipsizedWidth()
9363                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
9364
9365             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
9366                     && (want > mLayout.getWidth())
9367                     && (mLayout instanceof BoringLayout
9368                             || (fromexisting && des >= 0 && des <= want));
9369
9370             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
9371
9372             if (layoutChanged || maximumChanged) {
9373                 if (!maximumChanged && widthChanged) {
9374                     mLayout.increaseWidthTo(want);
9375                 } else {
9376                     makeNewLayout(want, hintWant, boring, hintBoring,
9377                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
9378                 }
9379             } else {
9380                 // Nothing has changed
9381             }
9382         }
9383
9384         if (heightMode == MeasureSpec.EXACTLY) {
9385             // Parent has told us how big to be. So be it.
9386             height = heightSize;
9387             mDesiredHeightAtMeasure = -1;
9388         } else {
9389             int desired = getDesiredHeight();
9390
9391             height = desired;
9392             mDesiredHeightAtMeasure = desired;
9393
9394             if (heightMode == MeasureSpec.AT_MOST) {
9395                 height = Math.min(desired, heightSize);
9396             }
9397         }
9398
9399         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
9400         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
9401             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
9402         }
9403
9404         /*
9405          * We didn't let makeNewLayout() register to bring the cursor into view,
9406          * so do it here if there is any possibility that it is needed.
9407          */
9408         if (mMovement != null
9409                 || mLayout.getWidth() > unpaddedWidth
9410                 || mLayout.getHeight() > unpaddedHeight) {
9411             registerForPreDraw();
9412         } else {
9413             scrollTo(0, 0);
9414         }
9415
9416         setMeasuredDimension(width, height);
9417     }
9418
9419     /**
9420      * Automatically computes and sets the text size.
9421      */
9422     private void autoSizeText() {
9423         if (!isAutoSizeEnabled()) {
9424             return;
9425         }
9426
9427         if (mNeedsAutoSizeText) {
9428             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
9429                 return;
9430             }
9431
9432             final int availableWidth = mHorizontallyScrolling
9433                     ? VERY_WIDE
9434                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
9435             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
9436                     - getExtendedPaddingTop();
9437
9438             if (availableWidth <= 0 || availableHeight <= 0) {
9439                 return;
9440             }
9441
9442             synchronized (TEMP_RECTF) {
9443                 TEMP_RECTF.setEmpty();
9444                 TEMP_RECTF.right = availableWidth;
9445                 TEMP_RECTF.bottom = availableHeight;
9446                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
9447
9448                 if (optimalTextSize != getTextSize()) {
9449                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
9450                             false /* shouldRequestLayout */);
9451
9452                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
9453                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9454                             false /* bringIntoView */);
9455                 }
9456             }
9457         }
9458         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
9459         // after the next layout pass should set this to false.
9460         mNeedsAutoSizeText = true;
9461     }
9462
9463     /**
9464      * Performs a binary search to find the largest text size that will still fit within the size
9465      * available to this view.
9466      */
9467     private int findLargestTextSizeWhichFits(RectF availableSpace) {
9468         final int sizesCount = mAutoSizeTextSizesInPx.length;
9469         if (sizesCount == 0) {
9470             throw new IllegalStateException("No available text sizes to choose from.");
9471         }
9472
9473         int bestSizeIndex = 0;
9474         int lowIndex = bestSizeIndex + 1;
9475         int highIndex = sizesCount - 1;
9476         int sizeToTryIndex;
9477         while (lowIndex <= highIndex) {
9478             sizeToTryIndex = (lowIndex + highIndex) / 2;
9479             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
9480                 bestSizeIndex = lowIndex;
9481                 lowIndex = sizeToTryIndex + 1;
9482             } else {
9483                 highIndex = sizeToTryIndex - 1;
9484                 bestSizeIndex = highIndex;
9485             }
9486         }
9487
9488         return mAutoSizeTextSizesInPx[bestSizeIndex];
9489     }
9490
9491     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
9492         final CharSequence text = mTransformed != null
9493                 ? mTransformed
9494                 : getText();
9495         final int maxLines = getMaxLines();
9496         if (mTempTextPaint == null) {
9497             mTempTextPaint = new TextPaint();
9498         } else {
9499             mTempTextPaint.reset();
9500         }
9501         mTempTextPaint.set(getPaint());
9502         mTempTextPaint.setTextSize(suggestedSizeInPx);
9503
9504         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
9505                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
9506
9507         layoutBuilder.setAlignment(getLayoutAlignment())
9508                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
9509                 .setIncludePad(getIncludeFontPadding())
9510                 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
9511                 .setBreakStrategy(getBreakStrategy())
9512                 .setHyphenationFrequency(getHyphenationFrequency())
9513                 .setJustificationMode(getJustificationMode())
9514                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
9515                 .setTextDirection(getTextDirectionHeuristic());
9516
9517         final StaticLayout layout = layoutBuilder.build();
9518
9519         // Lines overflow.
9520         if (maxLines != -1 && layout.getLineCount() > maxLines) {
9521             return false;
9522         }
9523
9524         // Height overflow.
9525         if (layout.getHeight() > availableSpace.bottom) {
9526             return false;
9527         }
9528
9529         return true;
9530     }
9531
9532     private int getDesiredHeight() {
9533         return Math.max(
9534                 getDesiredHeight(mLayout, true),
9535                 getDesiredHeight(mHintLayout, mEllipsize != null));
9536     }
9537
9538     private int getDesiredHeight(Layout layout, boolean cap) {
9539         if (layout == null) {
9540             return 0;
9541         }
9542
9543         /*
9544         * Don't cap the hint to a certain number of lines.
9545         * (Do cap it, though, if we have a maximum pixel height.)
9546         */
9547         int desired = layout.getHeight(cap);
9548
9549         final Drawables dr = mDrawables;
9550         if (dr != null) {
9551             desired = Math.max(desired, dr.mDrawableHeightLeft);
9552             desired = Math.max(desired, dr.mDrawableHeightRight);
9553         }
9554
9555         int linecount = layout.getLineCount();
9556         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
9557         desired += padding;
9558
9559         if (mMaxMode != LINES) {
9560             desired = Math.min(desired, mMaximum);
9561         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
9562                 || layout instanceof BoringLayout)) {
9563             desired = layout.getLineTop(mMaximum);
9564
9565             if (dr != null) {
9566                 desired = Math.max(desired, dr.mDrawableHeightLeft);
9567                 desired = Math.max(desired, dr.mDrawableHeightRight);
9568             }
9569
9570             desired += padding;
9571             linecount = mMaximum;
9572         }
9573
9574         if (mMinMode == LINES) {
9575             if (linecount < mMinimum) {
9576                 desired += getLineHeight() * (mMinimum - linecount);
9577             }
9578         } else {
9579             desired = Math.max(desired, mMinimum);
9580         }
9581
9582         // Check against our minimum height
9583         desired = Math.max(desired, getSuggestedMinimumHeight());
9584
9585         return desired;
9586     }
9587
9588     /**
9589      * Check whether a change to the existing text layout requires a
9590      * new view layout.
9591      */
9592     private void checkForResize() {
9593         boolean sizeChanged = false;
9594
9595         if (mLayout != null) {
9596             // Check if our width changed
9597             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
9598                 sizeChanged = true;
9599                 invalidate();
9600             }
9601
9602             // Check if our height changed
9603             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
9604                 int desiredHeight = getDesiredHeight();
9605
9606                 if (desiredHeight != this.getHeight()) {
9607                     sizeChanged = true;
9608                 }
9609             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
9610                 if (mDesiredHeightAtMeasure >= 0) {
9611                     int desiredHeight = getDesiredHeight();
9612
9613                     if (desiredHeight != mDesiredHeightAtMeasure) {
9614                         sizeChanged = true;
9615                     }
9616                 }
9617             }
9618         }
9619
9620         if (sizeChanged) {
9621             requestLayout();
9622             // caller will have already invalidated
9623         }
9624     }
9625
9626     /**
9627      * Check whether entirely new text requires a new view layout
9628      * or merely a new text layout.
9629      */
9630     @UnsupportedAppUsage
9631     private void checkForRelayout() {
9632         // If we have a fixed width, we can just swap in a new text layout
9633         // if the text height stays the same or if the view height is fixed.
9634
9635         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
9636                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
9637                 && (mHint == null || mHintLayout != null)
9638                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
9639             // Static width, so try making a new text layout.
9640
9641             int oldht = mLayout.getHeight();
9642             int want = mLayout.getWidth();
9643             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
9644
9645             /*
9646              * No need to bring the text into view, since the size is not
9647              * changing (unless we do the requestLayout(), in which case it
9648              * will happen at measure).
9649              */
9650             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
9651                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
9652                           false);
9653
9654             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
9655                 // In a fixed-height view, so use our new text layout.
9656                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
9657                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
9658                     autoSizeText();
9659                     invalidate();
9660                     return;
9661                 }
9662
9663                 // Dynamic height, but height has stayed the same,
9664                 // so use our new text layout.
9665                 if (mLayout.getHeight() == oldht
9666                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
9667                     autoSizeText();
9668                     invalidate();
9669                     return;
9670                 }
9671             }
9672
9673             // We lose: the height has changed and we have a dynamic height.
9674             // Request a new view layout using our new text layout.
9675             requestLayout();
9676             invalidate();
9677         } else {
9678             // Dynamic width, so we have no choice but to request a new
9679             // view layout with a new text layout.
9680             nullLayouts();
9681             requestLayout();
9682             invalidate();
9683         }
9684     }
9685
9686     @Override
9687     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
9688         super.onLayout(changed, left, top, right, bottom);
9689         if (mDeferScroll >= 0) {
9690             int curs = mDeferScroll;
9691             mDeferScroll = -1;
9692             bringPointIntoView(Math.min(curs, mText.length()));
9693         }
9694         // Call auto-size after the width and height have been calculated.
9695         autoSizeText();
9696     }
9697
9698     private boolean isShowingHint() {
9699         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
9700     }
9701
9702     /**
9703      * Returns true if anything changed.
9704      */
9705     @UnsupportedAppUsage
9706     private boolean bringTextIntoView() {
9707         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9708         int line = 0;
9709         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9710             line = layout.getLineCount() - 1;
9711         }
9712
9713         Layout.Alignment a = layout.getParagraphAlignment(line);
9714         int dir = layout.getParagraphDirection(line);
9715         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9716         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9717         int ht = layout.getHeight();
9718
9719         int scrollx, scrolly;
9720
9721         // Convert to left, center, or right alignment.
9722         if (a == Layout.Alignment.ALIGN_NORMAL) {
9723             a = dir == Layout.DIR_LEFT_TO_RIGHT
9724                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
9725         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
9726             a = dir == Layout.DIR_LEFT_TO_RIGHT
9727                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
9728         }
9729
9730         if (a == Layout.Alignment.ALIGN_CENTER) {
9731             /*
9732              * Keep centered if possible, or, if it is too wide to fit,
9733              * keep leading edge in view.
9734              */
9735
9736             int left = (int) Math.floor(layout.getLineLeft(line));
9737             int right = (int) Math.ceil(layout.getLineRight(line));
9738
9739             if (right - left < hspace) {
9740                 scrollx = (right + left) / 2 - hspace / 2;
9741             } else {
9742                 if (dir < 0) {
9743                     scrollx = right - hspace;
9744                 } else {
9745                     scrollx = left;
9746                 }
9747             }
9748         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
9749             int right = (int) Math.ceil(layout.getLineRight(line));
9750             scrollx = right - hspace;
9751         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
9752             scrollx = (int) Math.floor(layout.getLineLeft(line));
9753         }
9754
9755         if (ht < vspace) {
9756             scrolly = 0;
9757         } else {
9758             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9759                 scrolly = ht - vspace;
9760             } else {
9761                 scrolly = 0;
9762             }
9763         }
9764
9765         if (scrollx != mScrollX || scrolly != mScrollY) {
9766             scrollTo(scrollx, scrolly);
9767             return true;
9768         } else {
9769             return false;
9770         }
9771     }
9772
9773     /**
9774      * Move the point, specified by the offset, into the view if it is needed.
9775      * This has to be called after layout. Returns true if anything changed.
9776      */
9777     public boolean bringPointIntoView(int offset) {
9778         if (isLayoutRequested()) {
9779             mDeferScroll = offset;
9780             return false;
9781         }
9782         boolean changed = false;
9783
9784         Layout layout = isShowingHint() ? mHintLayout : mLayout;
9785
9786         if (layout == null) return changed;
9787
9788         int line = layout.getLineForOffset(offset);
9789
9790         int grav;
9791
9792         switch (layout.getParagraphAlignment(line)) {
9793             case ALIGN_LEFT:
9794                 grav = 1;
9795                 break;
9796             case ALIGN_RIGHT:
9797                 grav = -1;
9798                 break;
9799             case ALIGN_NORMAL:
9800                 grav = layout.getParagraphDirection(line);
9801                 break;
9802             case ALIGN_OPPOSITE:
9803                 grav = -layout.getParagraphDirection(line);
9804                 break;
9805             case ALIGN_CENTER:
9806             default:
9807                 grav = 0;
9808                 break;
9809         }
9810
9811         // We only want to clamp the cursor to fit within the layout width
9812         // in left-to-right modes, because in a right to left alignment,
9813         // we want to scroll to keep the line-right on the screen, as other
9814         // lines are likely to have text flush with the right margin, which
9815         // we want to keep visible.
9816         // A better long-term solution would probably be to measure both
9817         // the full line and a blank-trimmed version, and, for example, use
9818         // the latter measurement for centering and right alignment, but for
9819         // the time being we only implement the cursor clamping in left to
9820         // right where it is most likely to be annoying.
9821         final boolean clamped = grav > 0;
9822         // FIXME: Is it okay to truncate this, or should we round?
9823         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
9824         final int top = layout.getLineTop(line);
9825         final int bottom = layout.getLineTop(line + 1);
9826
9827         int left = (int) Math.floor(layout.getLineLeft(line));
9828         int right = (int) Math.ceil(layout.getLineRight(line));
9829         int ht = layout.getHeight();
9830
9831         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9832         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9833         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
9834             // If cursor has been clamped, make sure we don't scroll.
9835             right = Math.max(x, left + hspace);
9836         }
9837
9838         int hslack = (bottom - top) / 2;
9839         int vslack = hslack;
9840
9841         if (vslack > vspace / 4) {
9842             vslack = vspace / 4;
9843         }
9844         if (hslack > hspace / 4) {
9845             hslack = hspace / 4;
9846         }
9847
9848         int hs = mScrollX;
9849         int vs = mScrollY;
9850
9851         if (top - vs < vslack) {
9852             vs = top - vslack;
9853         }
9854         if (bottom - vs > vspace - vslack) {
9855             vs = bottom - (vspace - vslack);
9856         }
9857         if (ht - vs < vspace) {
9858             vs = ht - vspace;
9859         }
9860         if (0 - vs > 0) {
9861             vs = 0;
9862         }
9863
9864         if (grav != 0) {
9865             if (x - hs < hslack) {
9866                 hs = x - hslack;
9867             }
9868             if (x - hs > hspace - hslack) {
9869                 hs = x - (hspace - hslack);
9870             }
9871         }
9872
9873         if (grav < 0) {
9874             if (left - hs > 0) {
9875                 hs = left;
9876             }
9877             if (right - hs < hspace) {
9878                 hs = right - hspace;
9879             }
9880         } else if (grav > 0) {
9881             if (right - hs < hspace) {
9882                 hs = right - hspace;
9883             }
9884             if (left - hs > 0) {
9885                 hs = left;
9886             }
9887         } else /* grav == 0 */ {
9888             if (right - left <= hspace) {
9889                 /*
9890                  * If the entire text fits, center it exactly.
9891                  */
9892                 hs = left - (hspace - (right - left)) / 2;
9893             } else if (x > right - hslack) {
9894                 /*
9895                  * If we are near the right edge, keep the right edge
9896                  * at the edge of the view.
9897                  */
9898                 hs = right - hspace;
9899             } else if (x < left + hslack) {
9900                 /*
9901                  * If we are near the left edge, keep the left edge
9902                  * at the edge of the view.
9903                  */
9904                 hs = left;
9905             } else if (left > hs) {
9906                 /*
9907                  * Is there whitespace visible at the left?  Fix it if so.
9908                  */
9909                 hs = left;
9910             } else if (right < hs + hspace) {
9911                 /*
9912                  * Is there whitespace visible at the right?  Fix it if so.
9913                  */
9914                 hs = right - hspace;
9915             } else {
9916                 /*
9917                  * Otherwise, float as needed.
9918                  */
9919                 if (x - hs < hslack) {
9920                     hs = x - hslack;
9921                 }
9922                 if (x - hs > hspace - hslack) {
9923                     hs = x - (hspace - hslack);
9924                 }
9925             }
9926         }
9927
9928         if (hs != mScrollX || vs != mScrollY) {
9929             if (mScroller == null) {
9930                 scrollTo(hs, vs);
9931             } else {
9932                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
9933                 int dx = hs - mScrollX;
9934                 int dy = vs - mScrollY;
9935
9936                 if (duration > ANIMATED_SCROLL_GAP) {
9937                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
9938                     awakenScrollBars(mScroller.getDuration());
9939                     invalidate();
9940                 } else {
9941                     if (!mScroller.isFinished()) {
9942                         mScroller.abortAnimation();
9943                     }
9944
9945                     scrollBy(dx, dy);
9946                 }
9947
9948                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
9949             }
9950
9951             changed = true;
9952         }
9953
9954         if (isFocused()) {
9955             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
9956             // requestRectangleOnScreen() is in terms of content coordinates.
9957
9958             // The offsets here are to ensure the rectangle we are using is
9959             // within our view bounds, in case the cursor is on the far left
9960             // or right.  If it isn't withing the bounds, then this request
9961             // will be ignored.
9962             if (mTempRect == null) mTempRect = new Rect();
9963             mTempRect.set(x - 2, top, x + 2, bottom);
9964             getInterestingRect(mTempRect, line);
9965             mTempRect.offset(mScrollX, mScrollY);
9966
9967             if (requestRectangleOnScreen(mTempRect)) {
9968                 changed = true;
9969             }
9970         }
9971
9972         return changed;
9973     }
9974
9975     /**
9976      * Move the cursor, if needed, so that it is at an offset that is visible
9977      * to the user.  This will not move the cursor if it represents more than
9978      * one character (a selection range).  This will only work if the
9979      * TextView contains spannable text; otherwise it will do nothing.
9980      *
9981      * @return True if the cursor was actually moved, false otherwise.
9982      */
9983     public boolean moveCursorToVisibleOffset() {
9984         if (!(mText instanceof Spannable)) {
9985             return false;
9986         }
9987         int start = getSelectionStart();
9988         int end = getSelectionEnd();
9989         if (start != end) {
9990             return false;
9991         }
9992
9993         // First: make sure the line is visible on screen:
9994
9995         int line = mLayout.getLineForOffset(start);
9996
9997         final int top = mLayout.getLineTop(line);
9998         final int bottom = mLayout.getLineTop(line + 1);
9999         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
10000         int vslack = (bottom - top) / 2;
10001         if (vslack > vspace / 4) {
10002             vslack = vspace / 4;
10003         }
10004         final int vs = mScrollY;
10005
10006         if (top < (vs + vslack)) {
10007             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
10008         } else if (bottom > (vspace + vs - vslack)) {
10009             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
10010         }
10011
10012         // Next: make sure the character is visible on screen:
10013
10014         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10015         final int hs = mScrollX;
10016         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
10017         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
10018
10019         // line might contain bidirectional text
10020         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
10021         final int highChar = leftChar > rightChar ? leftChar : rightChar;
10022
10023         int newStart = start;
10024         if (newStart < lowChar) {
10025             newStart = lowChar;
10026         } else if (newStart > highChar) {
10027             newStart = highChar;
10028         }
10029
10030         if (newStart != start) {
10031             Selection.setSelection(mSpannable, newStart);
10032             return true;
10033         }
10034
10035         return false;
10036     }
10037
10038     @Override
10039     public void computeScroll() {
10040         if (mScroller != null) {
10041             if (mScroller.computeScrollOffset()) {
10042                 mScrollX = mScroller.getCurrX();
10043                 mScrollY = mScroller.getCurrY();
10044                 invalidateParentCaches();
10045                 postInvalidate();  // So we draw again
10046             }
10047         }
10048     }
10049
10050     private void getInterestingRect(Rect r, int line) {
10051         convertFromViewportToContentCoordinates(r);
10052
10053         // Rectangle can can be expanded on first and last line to take
10054         // padding into account.
10055         // TODO Take left/right padding into account too?
10056         if (line == 0) r.top -= getExtendedPaddingTop();
10057         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
10058     }
10059
10060     private void convertFromViewportToContentCoordinates(Rect r) {
10061         final int horizontalOffset = viewportToContentHorizontalOffset();
10062         r.left += horizontalOffset;
10063         r.right += horizontalOffset;
10064
10065         final int verticalOffset = viewportToContentVerticalOffset();
10066         r.top += verticalOffset;
10067         r.bottom += verticalOffset;
10068     }
10069
10070     int viewportToContentHorizontalOffset() {
10071         return getCompoundPaddingLeft() - mScrollX;
10072     }
10073
10074     @UnsupportedAppUsage
10075     int viewportToContentVerticalOffset() {
10076         int offset = getExtendedPaddingTop() - mScrollY;
10077         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
10078             offset += getVerticalOffset(false);
10079         }
10080         return offset;
10081     }
10082
10083     @Override
10084     public void debug(int depth) {
10085         super.debug(depth);
10086
10087         String output = debugIndent(depth);
10088         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
10089                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
10090                 + "} ";
10091
10092         if (mText != null) {
10093
10094             output += "mText=\"" + mText + "\" ";
10095             if (mLayout != null) {
10096                 output += "mLayout width=" + mLayout.getWidth()
10097                         + " height=" + mLayout.getHeight();
10098             }
10099         } else {
10100             output += "mText=NULL";
10101         }
10102         Log.d(VIEW_LOG_TAG, output);
10103     }
10104
10105     /**
10106      * Convenience for {@link Selection#getSelectionStart}.
10107      */
10108     @ViewDebug.ExportedProperty(category = "text")
10109     public int getSelectionStart() {
10110         return Selection.getSelectionStart(getText());
10111     }
10112
10113     /**
10114      * Convenience for {@link Selection#getSelectionEnd}.
10115      */
10116     @ViewDebug.ExportedProperty(category = "text")
10117     public int getSelectionEnd() {
10118         return Selection.getSelectionEnd(getText());
10119     }
10120
10121     /**
10122      * Return true iff there is a selection of nonzero length inside this text view.
10123      */
10124     public boolean hasSelection() {
10125         final int selectionStart = getSelectionStart();
10126         final int selectionEnd = getSelectionEnd();
10127
10128         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
10129     }
10130
10131     String getSelectedText() {
10132         if (!hasSelection()) {
10133             return null;
10134         }
10135
10136         final int start = getSelectionStart();
10137         final int end = getSelectionEnd();
10138         return String.valueOf(
10139                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
10140     }
10141
10142     /**
10143      * Sets the properties of this field (lines, horizontally scrolling,
10144      * transformation method) to be for a single-line input.
10145      *
10146      * @attr ref android.R.styleable#TextView_singleLine
10147      */
10148     public void setSingleLine() {
10149         setSingleLine(true);
10150     }
10151
10152     /**
10153      * Sets the properties of this field to transform input to ALL CAPS
10154      * display. This may use a "small caps" formatting if available.
10155      * This setting will be ignored if this field is editable or selectable.
10156      *
10157      * This call replaces the current transformation method. Disabling this
10158      * will not necessarily restore the previous behavior from before this
10159      * was enabled.
10160      *
10161      * @see #setTransformationMethod(TransformationMethod)
10162      * @attr ref android.R.styleable#TextView_textAllCaps
10163      */
10164     public void setAllCaps(boolean allCaps) {
10165         if (allCaps) {
10166             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
10167         } else {
10168             setTransformationMethod(null);
10169         }
10170     }
10171
10172     /**
10173      *
10174      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
10175      * @return Whether the current transformation method is for ALL CAPS.
10176      *
10177      * @see #setAllCaps(boolean)
10178      * @see #setTransformationMethod(TransformationMethod)
10179      */
10180     @InspectableProperty(name = "textAllCaps")
10181     public boolean isAllCaps() {
10182         final TransformationMethod method = getTransformationMethod();
10183         return method != null && method instanceof AllCapsTransformationMethod;
10184     }
10185
10186     /**
10187      * If true, sets the properties of this field (number of lines, horizontally scrolling,
10188      * transformation method) to be for a single-line input; if false, restores these to the default
10189      * conditions.
10190      *
10191      * Note that the default conditions are not necessarily those that were in effect prior this
10192      * method, and you may want to reset these properties to your custom values.
10193      *
10194      * @attr ref android.R.styleable#TextView_singleLine
10195      */
10196     @android.view.RemotableViewMethod
10197     public void setSingleLine(boolean singleLine) {
10198         // Could be used, but may break backward compatibility.
10199         // if (mSingleLine == singleLine) return;
10200         setInputTypeSingleLine(singleLine);
10201         applySingleLine(singleLine, true, true);
10202     }
10203
10204     /**
10205      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
10206      * @param singleLine
10207      */
10208     private void setInputTypeSingleLine(boolean singleLine) {
10209         if (mEditor != null
10210                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
10211                         == EditorInfo.TYPE_CLASS_TEXT) {
10212             if (singleLine) {
10213                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10214             } else {
10215                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
10216             }
10217         }
10218     }
10219
10220     private void applySingleLine(boolean singleLine, boolean applyTransformation,
10221             boolean changeMaxLines) {
10222         mSingleLine = singleLine;
10223         if (singleLine) {
10224             setLines(1);
10225             setHorizontallyScrolling(true);
10226             if (applyTransformation) {
10227                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
10228             }
10229         } else {
10230             if (changeMaxLines) {
10231                 setMaxLines(Integer.MAX_VALUE);
10232             }
10233             setHorizontallyScrolling(false);
10234             if (applyTransformation) {
10235                 setTransformationMethod(null);
10236             }
10237         }
10238     }
10239
10240     /**
10241      * Causes words in the text that are longer than the view's width
10242      * to be ellipsized instead of broken in the middle.  You may also
10243      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
10244      * to constrain the text to a single line.  Use <code>null</code>
10245      * to turn off ellipsizing.
10246      *
10247      * If {@link #setMaxLines} has been used to set two or more lines,
10248      * only {@link android.text.TextUtils.TruncateAt#END} and
10249      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
10250      * (other ellipsizing types will not do anything).
10251      *
10252      * @attr ref android.R.styleable#TextView_ellipsize
10253      */
10254     public void setEllipsize(TextUtils.TruncateAt where) {
10255         // TruncateAt is an enum. != comparison is ok between these singleton objects.
10256         if (mEllipsize != where) {
10257             mEllipsize = where;
10258
10259             if (mLayout != null) {
10260                 nullLayouts();
10261                 requestLayout();
10262                 invalidate();
10263             }
10264         }
10265     }
10266
10267     /**
10268      * Sets how many times to repeat the marquee animation. Only applied if the
10269      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
10270      *
10271      * @see #getMarqueeRepeatLimit()
10272      *
10273      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10274      */
10275     public void setMarqueeRepeatLimit(int marqueeLimit) {
10276         mMarqueeRepeatLimit = marqueeLimit;
10277     }
10278
10279     /**
10280      * Gets the number of times the marquee animation is repeated. Only meaningful if the
10281      * TextView has marquee enabled.
10282      *
10283      * @return the number of times the marquee animation is repeated. -1 if the animation
10284      * repeats indefinitely
10285      *
10286      * @see #setMarqueeRepeatLimit(int)
10287      *
10288      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
10289      */
10290     @InspectableProperty
10291     public int getMarqueeRepeatLimit() {
10292         return mMarqueeRepeatLimit;
10293     }
10294
10295     /**
10296      * Returns where, if anywhere, words that are longer than the view
10297      * is wide should be ellipsized.
10298      */
10299     @InspectableProperty
10300     @ViewDebug.ExportedProperty
10301     public TextUtils.TruncateAt getEllipsize() {
10302         return mEllipsize;
10303     }
10304
10305     /**
10306      * Set the TextView so that when it takes focus, all the text is
10307      * selected.
10308      *
10309      * @attr ref android.R.styleable#TextView_selectAllOnFocus
10310      */
10311     @android.view.RemotableViewMethod
10312     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
10313         createEditorIfNeeded();
10314         mEditor.mSelectAllOnFocus = selectAllOnFocus;
10315
10316         if (selectAllOnFocus && !(mText instanceof Spannable)) {
10317             setText(mText, BufferType.SPANNABLE);
10318         }
10319     }
10320
10321     /**
10322      * Set whether the cursor is visible. The default is true. Note that this property only
10323      * makes sense for editable TextView.
10324      *
10325      * @see #isCursorVisible()
10326      *
10327      * @attr ref android.R.styleable#TextView_cursorVisible
10328      */
10329     @android.view.RemotableViewMethod
10330     public void setCursorVisible(boolean visible) {
10331         if (visible && mEditor == null) return; // visible is the default value with no edit data
10332         createEditorIfNeeded();
10333         if (mEditor.mCursorVisible != visible) {
10334             mEditor.mCursorVisible = visible;
10335             invalidate();
10336
10337             mEditor.makeBlink();
10338
10339             // InsertionPointCursorController depends on mCursorVisible
10340             mEditor.prepareCursorControllers();
10341         }
10342     }
10343
10344     /**
10345      * @return whether or not the cursor is visible (assuming this TextView is editable)
10346      *
10347      * @see #setCursorVisible(boolean)
10348      *
10349      * @attr ref android.R.styleable#TextView_cursorVisible
10350      */
10351     @InspectableProperty
10352     public boolean isCursorVisible() {
10353         // true is the default value
10354         return mEditor == null ? true : mEditor.mCursorVisible;
10355     }
10356
10357     private boolean canMarquee() {
10358         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
10359         return width > 0 && (mLayout.getLineWidth(0) > width
10360                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
10361                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
10362     }
10363
10364     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
10365     private void startMarquee() {
10366         // Do not ellipsize EditText
10367         if (getKeyListener() != null) return;
10368
10369         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
10370             return;
10371         }
10372
10373         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
10374                 && getLineCount() == 1 && canMarquee()) {
10375
10376             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
10377                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
10378                 final Layout tmp = mLayout;
10379                 mLayout = mSavedMarqueeModeLayout;
10380                 mSavedMarqueeModeLayout = tmp;
10381                 setHorizontalFadingEdgeEnabled(true);
10382                 requestLayout();
10383                 invalidate();
10384             }
10385
10386             if (mMarquee == null) mMarquee = new Marquee(this);
10387             mMarquee.start(mMarqueeRepeatLimit);
10388         }
10389     }
10390
10391     private void stopMarquee() {
10392         if (mMarquee != null && !mMarquee.isStopped()) {
10393             mMarquee.stop();
10394         }
10395
10396         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
10397             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10398             final Layout tmp = mSavedMarqueeModeLayout;
10399             mSavedMarqueeModeLayout = mLayout;
10400             mLayout = tmp;
10401             setHorizontalFadingEdgeEnabled(false);
10402             requestLayout();
10403             invalidate();
10404         }
10405     }
10406
10407     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
10408     private void startStopMarquee(boolean start) {
10409         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10410             if (start) {
10411                 startMarquee();
10412             } else {
10413                 stopMarquee();
10414             }
10415         }
10416     }
10417
10418     /**
10419      * This method is called when the text is changed, in case any subclasses
10420      * would like to know.
10421      *
10422      * Within <code>text</code>, the <code>lengthAfter</code> characters
10423      * beginning at <code>start</code> have just replaced old text that had
10424      * length <code>lengthBefore</code>. It is an error to attempt to make
10425      * changes to <code>text</code> from this callback.
10426      *
10427      * @param text The text the TextView is displaying
10428      * @param start The offset of the start of the range of the text that was
10429      * modified
10430      * @param lengthBefore The length of the former text that has been replaced
10431      * @param lengthAfter The length of the replacement modified text
10432      */
10433     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
10434         // intentionally empty, template pattern method can be overridden by subclasses
10435     }
10436
10437     /**
10438      * This method is called when the selection has changed, in case any
10439      * subclasses would like to know.
10440      *
10441      * @param selStart The new selection start location.
10442      * @param selEnd The new selection end location.
10443      */
10444     protected void onSelectionChanged(int selStart, int selEnd) {
10445         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
10446     }
10447
10448     /**
10449      * Adds a TextWatcher to the list of those whose methods are called
10450      * whenever this TextView's text changes.
10451      * <p>
10452      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
10453      * not called after {@link #setText} calls.  Now, doing {@link #setText}
10454      * if there are any text changed listeners forces the buffer type to
10455      * Editable if it would not otherwise be and does call this method.
10456      */
10457     public void addTextChangedListener(TextWatcher watcher) {
10458         if (mListeners == null) {
10459             mListeners = new ArrayList<TextWatcher>();
10460         }
10461
10462         mListeners.add(watcher);
10463     }
10464
10465     /**
10466      * Removes the specified TextWatcher from the list of those whose
10467      * methods are called
10468      * whenever this TextView's text changes.
10469      */
10470     public void removeTextChangedListener(TextWatcher watcher) {
10471         if (mListeners != null) {
10472             int i = mListeners.indexOf(watcher);
10473
10474             if (i >= 0) {
10475                 mListeners.remove(i);
10476             }
10477         }
10478     }
10479
10480     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
10481         if (mListeners != null) {
10482             final ArrayList<TextWatcher> list = mListeners;
10483             final int count = list.size();
10484             for (int i = 0; i < count; i++) {
10485                 list.get(i).beforeTextChanged(text, start, before, after);
10486             }
10487         }
10488
10489         // The spans that are inside or intersect the modified region no longer make sense
10490         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
10491         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
10492     }
10493
10494     // Removes all spans that are inside or actually overlap the start..end range
10495     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
10496         if (!(mText instanceof Editable)) return;
10497         Editable text = (Editable) mText;
10498
10499         T[] spans = text.getSpans(start, end, type);
10500         final int length = spans.length;
10501         for (int i = 0; i < length; i++) {
10502             final int spanStart = text.getSpanStart(spans[i]);
10503             final int spanEnd = text.getSpanEnd(spans[i]);
10504             if (spanEnd == start || spanStart == end) break;
10505             text.removeSpan(spans[i]);
10506         }
10507     }
10508
10509     void removeAdjacentSuggestionSpans(final int pos) {
10510         if (!(mText instanceof Editable)) return;
10511         final Editable text = (Editable) mText;
10512
10513         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
10514         final int length = spans.length;
10515         for (int i = 0; i < length; i++) {
10516             final int spanStart = text.getSpanStart(spans[i]);
10517             final int spanEnd = text.getSpanEnd(spans[i]);
10518             if (spanEnd == pos || spanStart == pos) {
10519                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
10520                     text.removeSpan(spans[i]);
10521                 }
10522             }
10523         }
10524     }
10525
10526     /**
10527      * Not private so it can be called from an inner class without going
10528      * through a thunk.
10529      */
10530     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
10531         if (mListeners != null) {
10532             final ArrayList<TextWatcher> list = mListeners;
10533             final int count = list.size();
10534             for (int i = 0; i < count; i++) {
10535                 list.get(i).onTextChanged(text, start, before, after);
10536             }
10537         }
10538
10539         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
10540     }
10541
10542     /**
10543      * Not private so it can be called from an inner class without going
10544      * through a thunk.
10545      */
10546     void sendAfterTextChanged(Editable text) {
10547         if (mListeners != null) {
10548             final ArrayList<TextWatcher> list = mListeners;
10549             final int count = list.size();
10550             for (int i = 0; i < count; i++) {
10551                 list.get(i).afterTextChanged(text);
10552             }
10553         }
10554
10555         notifyListeningManagersAfterTextChanged();
10556
10557         hideErrorIfUnchanged();
10558     }
10559
10560     /**
10561      * Notify managers (such as {@link AutofillManager}) that are interested in text changes.
10562      */
10563     private void notifyListeningManagersAfterTextChanged() {
10564
10565         // Autofill
10566         if (isAutofillable()) {
10567             // It is important to not check whether the view is important for autofill
10568             // since the user can trigger autofill manually on not important views.
10569             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10570             if (afm != null) {
10571                 if (android.view.autofill.Helper.sVerbose) {
10572                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
10573                 }
10574                 afm.notifyValueChanged(TextView.this);
10575             }
10576         }
10577     }
10578
10579     private boolean isAutofillable() {
10580         // It is important to not check whether the view is important for autofill
10581         // since the user can trigger autofill manually on not important views.
10582         return getAutofillType() != AUTOFILL_TYPE_NONE;
10583     }
10584
10585     void updateAfterEdit() {
10586         invalidate();
10587         int curs = getSelectionStart();
10588
10589         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
10590             registerForPreDraw();
10591         }
10592
10593         checkForResize();
10594
10595         if (curs >= 0) {
10596             mHighlightPathBogus = true;
10597             if (mEditor != null) mEditor.makeBlink();
10598             bringPointIntoView(curs);
10599         }
10600     }
10601
10602     /**
10603      * Not private so it can be called from an inner class without going
10604      * through a thunk.
10605      */
10606     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
10607         sLastCutCopyOrTextChangedTime = 0;
10608
10609         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10610         if (ims == null || ims.mBatchEditNesting == 0) {
10611             updateAfterEdit();
10612         }
10613         if (ims != null) {
10614             ims.mContentChanged = true;
10615             if (ims.mChangedStart < 0) {
10616                 ims.mChangedStart = start;
10617                 ims.mChangedEnd = start + before;
10618             } else {
10619                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
10620                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
10621             }
10622             ims.mChangedDelta += after - before;
10623         }
10624         resetErrorChangedFlag();
10625         sendOnTextChanged(buffer, start, before, after);
10626         onTextChanged(buffer, start, before, after);
10627     }
10628
10629     /**
10630      * Not private so it can be called from an inner class without going
10631      * through a thunk.
10632      */
10633     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
10634         // XXX Make the start and end move together if this ends up
10635         // spending too much time invalidating.
10636
10637         boolean selChanged = false;
10638         int newSelStart = -1, newSelEnd = -1;
10639
10640         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
10641
10642         if (what == Selection.SELECTION_END) {
10643             selChanged = true;
10644             newSelEnd = newStart;
10645
10646             if (oldStart >= 0 || newStart >= 0) {
10647                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
10648                 checkForResize();
10649                 registerForPreDraw();
10650                 if (mEditor != null) mEditor.makeBlink();
10651             }
10652         }
10653
10654         if (what == Selection.SELECTION_START) {
10655             selChanged = true;
10656             newSelStart = newStart;
10657
10658             if (oldStart >= 0 || newStart >= 0) {
10659                 int end = Selection.getSelectionEnd(buf);
10660                 invalidateCursor(end, oldStart, newStart);
10661             }
10662         }
10663
10664         if (selChanged) {
10665             mHighlightPathBogus = true;
10666             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
10667
10668             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
10669                 if (newSelStart < 0) {
10670                     newSelStart = Selection.getSelectionStart(buf);
10671                 }
10672                 if (newSelEnd < 0) {
10673                     newSelEnd = Selection.getSelectionEnd(buf);
10674                 }
10675
10676                 if (mEditor != null) {
10677                     mEditor.refreshTextActionMode();
10678                     if (!hasSelection()
10679                             && mEditor.getTextActionMode() == null && hasTransientState()) {
10680                         // User generated selection has been removed.
10681                         setHasTransientState(false);
10682                     }
10683                 }
10684                 onSelectionChanged(newSelStart, newSelEnd);
10685             }
10686         }
10687
10688         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
10689                 || what instanceof CharacterStyle) {
10690             if (ims == null || ims.mBatchEditNesting == 0) {
10691                 invalidate();
10692                 mHighlightPathBogus = true;
10693                 checkForResize();
10694             } else {
10695                 ims.mContentChanged = true;
10696             }
10697             if (mEditor != null) {
10698                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
10699                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
10700                 mEditor.invalidateHandlesAndActionMode();
10701             }
10702         }
10703
10704         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
10705             mHighlightPathBogus = true;
10706             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
10707                 ims.mSelectionModeChanged = true;
10708             }
10709
10710             if (Selection.getSelectionStart(buf) >= 0) {
10711                 if (ims == null || ims.mBatchEditNesting == 0) {
10712                     invalidateCursor();
10713                 } else {
10714                     ims.mCursorChanged = true;
10715                 }
10716             }
10717         }
10718
10719         if (what instanceof ParcelableSpan) {
10720             // If this is a span that can be sent to a remote process,
10721             // the current extract editor would be interested in it.
10722             if (ims != null && ims.mExtractedTextRequest != null) {
10723                 if (ims.mBatchEditNesting != 0) {
10724                     if (oldStart >= 0) {
10725                         if (ims.mChangedStart > oldStart) {
10726                             ims.mChangedStart = oldStart;
10727                         }
10728                         if (ims.mChangedStart > oldEnd) {
10729                             ims.mChangedStart = oldEnd;
10730                         }
10731                     }
10732                     if (newStart >= 0) {
10733                         if (ims.mChangedStart > newStart) {
10734                             ims.mChangedStart = newStart;
10735                         }
10736                         if (ims.mChangedStart > newEnd) {
10737                             ims.mChangedStart = newEnd;
10738                         }
10739                     }
10740                 } else {
10741                     if (DEBUG_EXTRACT) {
10742                         Log.v(LOG_TAG, "Span change outside of batch: "
10743                                 + oldStart + "-" + oldEnd + ","
10744                                 + newStart + "-" + newEnd + " " + what);
10745                     }
10746                     ims.mContentChanged = true;
10747                 }
10748             }
10749         }
10750
10751         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
10752                 && what instanceof SpellCheckSpan) {
10753             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
10754         }
10755     }
10756
10757     @Override
10758     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
10759         if (isTemporarilyDetached()) {
10760             // If we are temporarily in the detach state, then do nothing.
10761             super.onFocusChanged(focused, direction, previouslyFocusedRect);
10762             return;
10763         }
10764
10765         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
10766
10767         if (focused) {
10768             if (mSpannable != null) {
10769                 MetaKeyKeyListener.resetMetaState(mSpannable);
10770             }
10771         }
10772
10773         startStopMarquee(focused);
10774
10775         if (mTransformation != null) {
10776             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
10777         }
10778
10779         super.onFocusChanged(focused, direction, previouslyFocusedRect);
10780     }
10781
10782     @Override
10783     public void onWindowFocusChanged(boolean hasWindowFocus) {
10784         super.onWindowFocusChanged(hasWindowFocus);
10785
10786         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
10787
10788         startStopMarquee(hasWindowFocus);
10789     }
10790
10791     @Override
10792     protected void onVisibilityChanged(View changedView, int visibility) {
10793         super.onVisibilityChanged(changedView, visibility);
10794         if (mEditor != null && visibility != VISIBLE) {
10795             mEditor.hideCursorAndSpanControllers();
10796             stopTextActionMode();
10797         }
10798     }
10799
10800     /**
10801      * Use {@link BaseInputConnection#removeComposingSpans
10802      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
10803      * state from this text view.
10804      */
10805     public void clearComposingText() {
10806         if (mText instanceof Spannable) {
10807             BaseInputConnection.removeComposingSpans(mSpannable);
10808         }
10809     }
10810
10811     @Override
10812     public void setSelected(boolean selected) {
10813         boolean wasSelected = isSelected();
10814
10815         super.setSelected(selected);
10816
10817         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
10818             if (selected) {
10819                 startMarquee();
10820             } else {
10821                 stopMarquee();
10822             }
10823         }
10824     }
10825
10826     @Override
10827     public boolean onTouchEvent(MotionEvent event) {
10828         final int action = event.getActionMasked();
10829         if (mEditor != null) {
10830             mEditor.onTouchEvent(event);
10831
10832             if (mEditor.mSelectionModifierCursorController != null
10833                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
10834                 return true;
10835             }
10836         }
10837
10838         final boolean superResult = super.onTouchEvent(event);
10839
10840         /*
10841          * Don't handle the release after a long press, because it will move the selection away from
10842          * whatever the menu action was trying to affect. If the long press should have triggered an
10843          * insertion action mode, we can now actually show it.
10844          */
10845         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
10846             mEditor.mDiscardNextActionUp = false;
10847
10848             if (mEditor.mIsInsertionActionModeStartPending) {
10849                 mEditor.startInsertionActionMode();
10850                 mEditor.mIsInsertionActionModeStartPending = false;
10851             }
10852             return superResult;
10853         }
10854
10855         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
10856                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
10857
10858         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
10859                 && mText instanceof Spannable && mLayout != null) {
10860             boolean handled = false;
10861
10862             if (mMovement != null) {
10863                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
10864             }
10865
10866             final boolean textIsSelectable = isTextSelectable();
10867             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
10868                 // The LinkMovementMethod which should handle taps on links has not been installed
10869                 // on non editable text that support text selection.
10870                 // We reproduce its behavior here to open links for these.
10871                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
10872                     getSelectionEnd(), ClickableSpan.class);
10873
10874                 if (links.length > 0) {
10875                     links[0].onClick(this);
10876                     handled = true;
10877                 }
10878             }
10879
10880             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
10881                 // Show the IME, except when selecting in read-only text.
10882                 final InputMethodManager imm = getInputMethodManager();
10883                 viewClicked(imm);
10884                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10885                     imm.showSoftInput(this, 0);
10886                 }
10887
10888                 // The above condition ensures that the mEditor is not null
10889                 mEditor.onTouchUpEvent(event);
10890
10891                 handled = true;
10892             }
10893
10894             if (handled) {
10895                 return true;
10896             }
10897         }
10898
10899         return superResult;
10900     }
10901
10902     @Override
10903     public boolean onGenericMotionEvent(MotionEvent event) {
10904         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
10905             try {
10906                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
10907                     return true;
10908                 }
10909             } catch (AbstractMethodError ex) {
10910                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
10911                 // Ignore its absence in case third party applications implemented the
10912                 // interface directly.
10913             }
10914         }
10915         return super.onGenericMotionEvent(event);
10916     }
10917
10918     @Override
10919     protected void onCreateContextMenu(ContextMenu menu) {
10920         if (mEditor != null) {
10921             mEditor.onCreateContextMenu(menu);
10922         }
10923     }
10924
10925     @Override
10926     public boolean showContextMenu() {
10927         if (mEditor != null) {
10928             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
10929         }
10930         return super.showContextMenu();
10931     }
10932
10933     @Override
10934     public boolean showContextMenu(float x, float y) {
10935         if (mEditor != null) {
10936             mEditor.setContextMenuAnchor(x, y);
10937         }
10938         return super.showContextMenu(x, y);
10939     }
10940
10941     /**
10942      * @return True iff this TextView contains a text that can be edited, or if this is
10943      * a selectable TextView.
10944      */
10945     @UnsupportedAppUsage
10946     boolean isTextEditable() {
10947         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
10948     }
10949
10950     /**
10951      * Returns true, only while processing a touch gesture, if the initial
10952      * touch down event caused focus to move to the text view and as a result
10953      * its selection changed.  Only valid while processing the touch gesture
10954      * of interest, in an editable text view.
10955      */
10956     public boolean didTouchFocusSelect() {
10957         return mEditor != null && mEditor.mTouchFocusSelected;
10958     }
10959
10960     @Override
10961     public void cancelLongPress() {
10962         super.cancelLongPress();
10963         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
10964     }
10965
10966     @Override
10967     public boolean onTrackballEvent(MotionEvent event) {
10968         if (mMovement != null && mSpannable != null && mLayout != null) {
10969             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
10970                 return true;
10971             }
10972         }
10973
10974         return super.onTrackballEvent(event);
10975     }
10976
10977     /**
10978      * Sets the Scroller used for producing a scrolling animation
10979      *
10980      * @param s A Scroller instance
10981      */
10982     public void setScroller(Scroller s) {
10983         mScroller = s;
10984     }
10985
10986     @Override
10987     protected float getLeftFadingEdgeStrength() {
10988         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
10989             final Marquee marquee = mMarquee;
10990             if (marquee.shouldDrawLeftFade()) {
10991                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
10992             } else {
10993                 return 0.0f;
10994             }
10995         } else if (getLineCount() == 1) {
10996             final float lineLeft = getLayout().getLineLeft(0);
10997             if (lineLeft > mScrollX) return 0.0f;
10998             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
10999         }
11000         return super.getLeftFadingEdgeStrength();
11001     }
11002
11003     @Override
11004     protected float getRightFadingEdgeStrength() {
11005         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
11006             final Marquee marquee = mMarquee;
11007             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
11008         } else if (getLineCount() == 1) {
11009             final float rightEdge = mScrollX +
11010                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
11011             final float lineRight = getLayout().getLineRight(0);
11012             if (lineRight < rightEdge) return 0.0f;
11013             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
11014         }
11015         return super.getRightFadingEdgeStrength();
11016     }
11017
11018     /**
11019      * Calculates the fading edge strength as the ratio of the distance between two
11020      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
11021      * value for the distance calculation.
11022      *
11023      * @param position1 A horizontal position.
11024      * @param position2 A horizontal position.
11025      * @return Fading edge strength between [0.0f, 1.0f].
11026      */
11027     @FloatRange(from = 0.0, to = 1.0)
11028     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
11029         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
11030         if (horizontalFadingEdgeLength == 0) return 0.0f;
11031         final float diff = Math.abs(position1 - position2);
11032         if (diff > horizontalFadingEdgeLength) return 1.0f;
11033         return diff / horizontalFadingEdgeLength;
11034     }
11035
11036     private boolean isMarqueeFadeEnabled() {
11037         return mEllipsize == TextUtils.TruncateAt.MARQUEE
11038                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
11039     }
11040
11041     @Override
11042     protected int computeHorizontalScrollRange() {
11043         if (mLayout != null) {
11044             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
11045                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
11046         }
11047
11048         return super.computeHorizontalScrollRange();
11049     }
11050
11051     @Override
11052     protected int computeVerticalScrollRange() {
11053         if (mLayout != null) {
11054             return mLayout.getHeight();
11055         }
11056         return super.computeVerticalScrollRange();
11057     }
11058
11059     @Override
11060     protected int computeVerticalScrollExtent() {
11061         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
11062     }
11063
11064     @Override
11065     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
11066         super.findViewsWithText(outViews, searched, flags);
11067         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
11068                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
11069             String searchedLowerCase = searched.toString().toLowerCase();
11070             String textLowerCase = mText.toString().toLowerCase();
11071             if (textLowerCase.contains(searchedLowerCase)) {
11072                 outViews.add(this);
11073             }
11074         }
11075     }
11076
11077     /**
11078      * Type of the text buffer that defines the characteristics of the text such as static,
11079      * styleable, or editable.
11080      */
11081     public enum BufferType {
11082         NORMAL, SPANNABLE, EDITABLE
11083     }
11084
11085     /**
11086      * Returns the TextView_textColor attribute from the TypedArray, if set, or
11087      * the TextAppearance_textColor from the TextView_textAppearance attribute,
11088      * if TextView_textColor was not set directly.
11089      *
11090      * @removed
11091      */
11092     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
11093         if (attrs == null) {
11094             // Preserve behavior prior to removal of this API.
11095             throw new NullPointerException();
11096         }
11097
11098         // It's not safe to use this method from apps. The parameter 'attrs'
11099         // must have been obtained using the TextView filter array which is not
11100         // available to the SDK. As such, we grab a default TypedArray with the
11101         // right filter instead here.
11102         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
11103         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
11104         if (colors == null) {
11105             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
11106             if (ap != 0) {
11107                 final TypedArray appearance = context.obtainStyledAttributes(
11108                         ap, R.styleable.TextAppearance);
11109                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
11110                 appearance.recycle();
11111             }
11112         }
11113         a.recycle();
11114
11115         return colors;
11116     }
11117
11118     /**
11119      * Returns the default color from the TextView_textColor attribute from the
11120      * AttributeSet, if set, or the default color from the
11121      * TextAppearance_textColor from the TextView_textAppearance attribute, if
11122      * TextView_textColor was not set directly.
11123      *
11124      * @removed
11125      */
11126     public static int getTextColor(Context context, TypedArray attrs, int def) {
11127         final ColorStateList colors = getTextColors(context, attrs);
11128         if (colors == null) {
11129             return def;
11130         } else {
11131             return colors.getDefaultColor();
11132         }
11133     }
11134
11135     @Override
11136     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
11137         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
11138             // Handle Ctrl-only shortcuts.
11139             switch (keyCode) {
11140                 case KeyEvent.KEYCODE_A:
11141                     if (canSelectText()) {
11142                         return onTextContextMenuItem(ID_SELECT_ALL);
11143                     }
11144                     break;
11145                 case KeyEvent.KEYCODE_Z:
11146                     if (canUndo()) {
11147                         return onTextContextMenuItem(ID_UNDO);
11148                     }
11149                     break;
11150                 case KeyEvent.KEYCODE_X:
11151                     if (canCut()) {
11152                         return onTextContextMenuItem(ID_CUT);
11153                     }
11154                     break;
11155                 case KeyEvent.KEYCODE_C:
11156                     if (canCopy()) {
11157                         return onTextContextMenuItem(ID_COPY);
11158                     }
11159                     break;
11160                 case KeyEvent.KEYCODE_V:
11161                     if (canPaste()) {
11162                         return onTextContextMenuItem(ID_PASTE);
11163                     }
11164                     break;
11165             }
11166         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
11167             // Handle Ctrl-Shift shortcuts.
11168             switch (keyCode) {
11169                 case KeyEvent.KEYCODE_Z:
11170                     if (canRedo()) {
11171                         return onTextContextMenuItem(ID_REDO);
11172                     }
11173                     break;
11174                 case KeyEvent.KEYCODE_V:
11175                     if (canPaste()) {
11176                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
11177                     }
11178             }
11179         }
11180         return super.onKeyShortcut(keyCode, event);
11181     }
11182
11183     /**
11184      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
11185      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
11186      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
11187      * sufficient.
11188      */
11189     boolean canSelectText() {
11190         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
11191     }
11192
11193     /**
11194      * Test based on the <i>intrinsic</i> charateristics of the TextView.
11195      * The text must be spannable and the movement method must allow for arbitary selection.
11196      *
11197      * See also {@link #canSelectText()}.
11198      */
11199     boolean textCanBeSelected() {
11200         // prepareCursorController() relies on this method.
11201         // If you change this condition, make sure prepareCursorController is called anywhere
11202         // the value of this condition might be changed.
11203         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
11204         return isTextEditable()
11205                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
11206     }
11207
11208     @UnsupportedAppUsage
11209     private Locale getTextServicesLocale(boolean allowNullLocale) {
11210         // Start fetching the text services locale asynchronously.
11211         updateTextServicesLocaleAsync();
11212         // If !allowNullLocale and there is no cached text services locale, just return the default
11213         // locale.
11214         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
11215                 : mCurrentSpellCheckerLocaleCache;
11216     }
11217
11218     /**
11219      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
11220      * this {@link TextView}.
11221      *
11222      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
11223      * other apps may need to set this so that the system can user right user's resources and
11224      * services such as input methods and spell checkers.</p>
11225      *
11226      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
11227      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
11228      * @hide
11229      */
11230     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
11231     public final void setTextOperationUser(@Nullable UserHandle user) {
11232         if (Objects.equals(mTextOperationUser, user)) {
11233             return;
11234         }
11235         if (user != null && !Process.myUserHandle().equals(user)) {
11236             // Just for preventing people from accidentally using this hidden API without
11237             // the required permission.  The same permission is also checked in the system server.
11238             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
11239                     != PackageManager.PERMISSION_GRANTED) {
11240                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
11241                         + " userId=" + user.getIdentifier()
11242                         + " callingUserId" + UserHandle.myUserId());
11243             }
11244         }
11245         mTextOperationUser = user;
11246         // Invalidate some resources
11247         mCurrentSpellCheckerLocaleCache = null;
11248         if (mEditor != null) {
11249             mEditor.onTextOperationUserChanged();
11250         }
11251     }
11252
11253     @Nullable
11254     final TextServicesManager getTextServicesManagerForUser() {
11255         return getServiceManagerForUser("android", TextServicesManager.class);
11256     }
11257
11258     @Nullable
11259     final ClipboardManager getClipboardManagerForUser() {
11260         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
11261     }
11262
11263     @Nullable
11264     final TextClassificationManager getTextClassificationManagerForUser() {
11265         return getServiceManagerForUser(
11266                 getContext().getPackageName(), TextClassificationManager.class);
11267     }
11268
11269     @Nullable
11270     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
11271         if (mTextOperationUser == null) {
11272             return getContext().getSystemService(managerClazz);
11273         }
11274         try {
11275             Context context = getContext().createPackageContextAsUser(
11276                     packageName, 0 /* flags */, mTextOperationUser);
11277             return context.getSystemService(managerClazz);
11278         } catch (PackageManager.NameNotFoundException e) {
11279             return null;
11280         }
11281     }
11282
11283     /**
11284      * Starts {@link Activity} as a text-operation user if it is specified with
11285      * {@link #setTextOperationUser(UserHandle)}.
11286      *
11287      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
11288      *
11289      * @param intent The description of the activity to start.
11290      */
11291     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
11292         if (mTextOperationUser != null) {
11293             getContext().startActivityAsUser(intent, mTextOperationUser);
11294         } else {
11295             getContext().startActivity(intent);
11296         }
11297     }
11298
11299     /**
11300      * This is a temporary method. Future versions may support multi-locale text.
11301      * Caveat: This method may not return the latest text services locale, but this should be
11302      * acceptable and it's more important to make this method asynchronous.
11303      *
11304      * @return The locale that should be used for a word iterator
11305      * in this TextView, based on the current spell checker settings,
11306      * the current IME's locale, or the system default locale.
11307      * Please note that a word iterator in this TextView is different from another word iterator
11308      * used by SpellChecker.java of TextView. This method should be used for the former.
11309      * @hide
11310      */
11311     // TODO: Support multi-locale
11312     // TODO: Update the text services locale immediately after the keyboard locale is switched
11313     // by catching intent of keyboard switch event
11314     public Locale getTextServicesLocale() {
11315         return getTextServicesLocale(false /* allowNullLocale */);
11316     }
11317
11318     /**
11319      * @return {@code true} if this TextView is specialized for showing and interacting with the
11320      * extracted text in a full-screen input method.
11321      * @hide
11322      */
11323     public boolean isInExtractedMode() {
11324         return false;
11325     }
11326
11327     /**
11328      * @return {@code true} if this widget supports auto-sizing text and has been configured to
11329      * auto-size.
11330      */
11331     private boolean isAutoSizeEnabled() {
11332         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
11333     }
11334
11335     /**
11336      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
11337      * @hide
11338      */
11339     protected boolean supportsAutoSizeText() {
11340         return true;
11341     }
11342
11343     /**
11344      * This is a temporary method. Future versions may support multi-locale text.
11345      * Caveat: This method may not return the latest spell checker locale, but this should be
11346      * acceptable and it's more important to make this method asynchronous.
11347      *
11348      * @return The locale that should be used for a spell checker in this TextView,
11349      * based on the current spell checker settings, the current IME's locale, or the system default
11350      * locale.
11351      * @hide
11352      */
11353     public Locale getSpellCheckerLocale() {
11354         return getTextServicesLocale(true /* allowNullLocale */);
11355     }
11356
11357     private void updateTextServicesLocaleAsync() {
11358         // AsyncTask.execute() uses a serial executor which means we don't have
11359         // to lock around updateTextServicesLocaleLocked() to prevent it from
11360         // being executed n times in parallel.
11361         AsyncTask.execute(new Runnable() {
11362             @Override
11363             public void run() {
11364                 updateTextServicesLocaleLocked();
11365             }
11366         });
11367     }
11368
11369     @UnsupportedAppUsage
11370     private void updateTextServicesLocaleLocked() {
11371         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
11372         if (textServicesManager == null) {
11373             return;
11374         }
11375         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
11376         final Locale locale;
11377         if (subtype != null) {
11378             locale = subtype.getLocaleObject();
11379         } else {
11380             locale = null;
11381         }
11382         mCurrentSpellCheckerLocaleCache = locale;
11383     }
11384
11385     void onLocaleChanged() {
11386         mEditor.onLocaleChanged();
11387     }
11388
11389     /**
11390      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
11391      * Made available to achieve a consistent behavior.
11392      * @hide
11393      */
11394     public WordIterator getWordIterator() {
11395         if (mEditor != null) {
11396             return mEditor.getWordIterator();
11397         } else {
11398             return null;
11399         }
11400     }
11401
11402     /** @hide */
11403     @Override
11404     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
11405         super.onPopulateAccessibilityEventInternal(event);
11406
11407         final CharSequence text = getTextForAccessibility();
11408         if (!TextUtils.isEmpty(text)) {
11409             event.getText().add(text);
11410         }
11411     }
11412
11413     @Override
11414     public CharSequence getAccessibilityClassName() {
11415         return TextView.class.getName();
11416     }
11417
11418     /** @hide */
11419     @Override
11420     protected void onProvideStructure(@NonNull ViewStructure structure,
11421             @ViewStructureType int viewFor, int flags) {
11422         super.onProvideStructure(structure, viewFor, flags);
11423
11424         final boolean isPassword = hasPasswordTransformationMethod()
11425                 || isPasswordInputType(getInputType());
11426         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11427             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11428                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
11429             }
11430             if (mTextId != Resources.ID_NULL) {
11431                 try {
11432                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
11433                 } catch (Resources.NotFoundException e) {
11434                     if (android.view.autofill.Helper.sVerbose) {
11435                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
11436                                 + mTextId + ": " + e.getMessage());
11437                     }
11438                 }
11439             }
11440         }
11441
11442         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11443             if (mLayout == null) {
11444                 assumeLayout();
11445             }
11446             Layout layout = mLayout;
11447             final int lineCount = layout.getLineCount();
11448             if (lineCount <= 1) {
11449                 // Simple case: this is a single line.
11450                 final CharSequence text = getText();
11451                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11452                     structure.setText(text);
11453                 } else {
11454                     structure.setText(text, getSelectionStart(), getSelectionEnd());
11455                 }
11456             } else {
11457                 // Complex case: multi-line, could be scrolled or within a scroll container
11458                 // so some lines are not visible.
11459                 final int[] tmpCords = new int[2];
11460                 getLocationInWindow(tmpCords);
11461                 final int topWindowLocation = tmpCords[1];
11462                 View root = this;
11463                 ViewParent viewParent = getParent();
11464                 while (viewParent instanceof View) {
11465                     root = (View) viewParent;
11466                     viewParent = root.getParent();
11467                 }
11468                 final int windowHeight = root.getHeight();
11469                 final int topLine;
11470                 final int bottomLine;
11471                 if (topWindowLocation >= 0) {
11472                     // The top of the view is fully within its window; start text at line 0.
11473                     topLine = getLineAtCoordinateUnclamped(0);
11474                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
11475                 } else {
11476                     // The top of hte window has scrolled off the top of the window; figure out
11477                     // the starting line for this.
11478                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
11479                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
11480                 }
11481                 // We want to return some contextual lines above/below the lines that are
11482                 // actually visible.
11483                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
11484                 if (expandedTopLine < 0) {
11485                     expandedTopLine = 0;
11486                 }
11487                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
11488                 if (expandedBottomLine >= lineCount) {
11489                     expandedBottomLine = lineCount - 1;
11490                 }
11491
11492                 // Convert lines into character offsets.
11493                 int expandedTopChar = layout.getLineStart(expandedTopLine);
11494                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
11495
11496                 // Take into account selection -- if there is a selection, we need to expand
11497                 // the text we are returning to include that selection.
11498                 final int selStart = getSelectionStart();
11499                 final int selEnd = getSelectionEnd();
11500                 if (selStart < selEnd) {
11501                     if (selStart < expandedTopChar) {
11502                         expandedTopChar = selStart;
11503                     }
11504                     if (selEnd > expandedBottomChar) {
11505                         expandedBottomChar = selEnd;
11506                     }
11507                 }
11508
11509                 // Get the text and trim it to the range we are reporting.
11510                 CharSequence text = getText();
11511                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
11512                     text = text.subSequence(expandedTopChar, expandedBottomChar);
11513                 }
11514
11515                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11516                     structure.setText(text);
11517                 } else {
11518                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
11519
11520                     final int[] lineOffsets = new int[bottomLine - topLine + 1];
11521                     final int[] lineBaselines = new int[bottomLine - topLine + 1];
11522                     final int baselineOffset = getBaselineOffset();
11523                     for (int i = topLine; i <= bottomLine; i++) {
11524                         lineOffsets[i - topLine] = layout.getLineStart(i);
11525                         lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
11526                     }
11527                     structure.setTextLines(lineOffsets, lineBaselines);
11528                 }
11529             }
11530
11531             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST) {
11532                 // Extract style information that applies to the TextView as a whole.
11533                 int style = 0;
11534                 int typefaceStyle = getTypefaceStyle();
11535                 if ((typefaceStyle & Typeface.BOLD) != 0) {
11536                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11537                 }
11538                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
11539                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
11540                 }
11541
11542                 // Global styles can also be set via TextView.setPaintFlags().
11543                 int paintFlags = mTextPaint.getFlags();
11544                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
11545                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
11546                 }
11547                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
11548                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
11549                 }
11550                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
11551                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
11552                 }
11553
11554                 // TextView does not have its own text background color. A background is either part
11555                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
11556                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
11557                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
11558             }
11559             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
11560                 structure.setMinTextEms(getMinEms());
11561                 structure.setMaxTextEms(getMaxEms());
11562                 int maxLength = -1;
11563                 for (InputFilter filter: getFilters()) {
11564                     if (filter instanceof InputFilter.LengthFilter) {
11565                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
11566                         break;
11567                     }
11568                 }
11569                 structure.setMaxTextLength(maxLength);
11570             }
11571         }
11572         structure.setHint(getHint());
11573         structure.setInputType(getInputType());
11574     }
11575
11576     boolean canRequestAutofill() {
11577         if (!isAutofillable()) {
11578             return false;
11579         }
11580         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11581         if (afm != null) {
11582             return afm.isEnabled();
11583         }
11584         return false;
11585     }
11586
11587     private void requestAutofill() {
11588         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
11589         if (afm != null) {
11590             afm.requestAutofill(this);
11591         }
11592     }
11593
11594     @Override
11595     public void autofill(AutofillValue value) {
11596         if (!value.isText() || !isTextEditable()) {
11597             Log.w(LOG_TAG, value + " could not be autofilled into " + this);
11598             return;
11599         }
11600
11601         final CharSequence autofilledValue = value.getTextValue();
11602
11603         // First autofill it...
11604         setText(autofilledValue, mBufferType, true, 0);
11605
11606         // ...then move cursor to the end.
11607         final CharSequence text = getText();
11608         if ((text instanceof Spannable)) {
11609             Selection.setSelection((Spannable) text, text.length());
11610         }
11611     }
11612
11613     @Override
11614     public @AutofillType int getAutofillType() {
11615         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
11616     }
11617
11618     /**
11619      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
11620      * {@code char}s if longer.
11621      *
11622      * @return current text, {@code null} if the text is not editable
11623      *
11624      * @see View#getAutofillValue()
11625      */
11626     @Override
11627     @Nullable
11628     public AutofillValue getAutofillValue() {
11629         if (isTextEditable()) {
11630             final CharSequence text = TextUtils.trimToParcelableSize(getText());
11631             return AutofillValue.forText(text);
11632         }
11633         return null;
11634     }
11635
11636     /** @hide */
11637     @Override
11638     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
11639         super.onInitializeAccessibilityEventInternal(event);
11640
11641         final boolean isPassword = hasPasswordTransformationMethod();
11642         event.setPassword(isPassword);
11643
11644         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
11645             event.setFromIndex(Selection.getSelectionStart(mText));
11646             event.setToIndex(Selection.getSelectionEnd(mText));
11647             event.setItemCount(mText.length());
11648         }
11649     }
11650
11651     /** @hide */
11652     @Override
11653     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
11654         super.onInitializeAccessibilityNodeInfoInternal(info);
11655
11656         final boolean isPassword = hasPasswordTransformationMethod();
11657         info.setPassword(isPassword);
11658         info.setText(getTextForAccessibility());
11659         info.setHintText(mHint);
11660         info.setShowingHintText(isShowingHint());
11661
11662         if (mBufferType == BufferType.EDITABLE) {
11663             info.setEditable(true);
11664             if (isEnabled()) {
11665                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
11666             }
11667         }
11668
11669         if (mEditor != null) {
11670             info.setInputType(mEditor.mInputType);
11671
11672             if (mEditor.mError != null) {
11673                 info.setContentInvalid(true);
11674                 info.setError(mEditor.mError);
11675             }
11676         }
11677
11678         if (!TextUtils.isEmpty(mText)) {
11679             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
11680             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
11681             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
11682                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
11683                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
11684                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
11685                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
11686             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
11687             info.setAvailableExtraData(
11688                     Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
11689         }
11690
11691         if (isFocused()) {
11692             if (canCopy()) {
11693                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
11694             }
11695             if (canPaste()) {
11696                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
11697             }
11698             if (canCut()) {
11699                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
11700             }
11701             if (canShare()) {
11702                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
11703                         ACCESSIBILITY_ACTION_SHARE,
11704                         getResources().getString(com.android.internal.R.string.share)));
11705             }
11706             if (canProcessText()) {  // also implies mEditor is not null.
11707                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
11708             }
11709         }
11710
11711         // Check for known input filter types.
11712         final int numFilters = mFilters.length;
11713         for (int i = 0; i < numFilters; i++) {
11714             final InputFilter filter = mFilters[i];
11715             if (filter instanceof InputFilter.LengthFilter) {
11716                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
11717             }
11718         }
11719
11720         if (!isSingleLine()) {
11721             info.setMultiLine(true);
11722         }
11723     }
11724
11725     @Override
11726     public void addExtraDataToAccessibilityNodeInfo(
11727             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
11728         // The only extra data we support requires arguments.
11729         if (arguments == null) {
11730             return;
11731         }
11732         if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
11733             int positionInfoStartIndex = arguments.getInt(
11734                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
11735             int positionInfoLength = arguments.getInt(
11736                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
11737             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
11738                     || (positionInfoStartIndex >= mText.length())) {
11739                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
11740                 return;
11741             }
11742             RectF[] boundingRects = new RectF[positionInfoLength];
11743             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
11744             populateCharacterBounds(builder, positionInfoStartIndex,
11745                     positionInfoStartIndex + positionInfoLength,
11746                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
11747             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
11748             for (int i = 0; i < positionInfoLength; i++) {
11749                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
11750                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
11751                     RectF bounds = cursorAnchorInfo
11752                             .getCharacterBounds(positionInfoStartIndex + i);
11753                     if (bounds != null) {
11754                         mapRectFromViewToScreenCoords(bounds, true);
11755                         boundingRects[i] = bounds;
11756                     }
11757                 }
11758             }
11759             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
11760         }
11761     }
11762
11763     /**
11764      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
11765      *
11766      * @param builder The builder to populate
11767      * @param startIndex The starting character index to populate
11768      * @param endIndex The ending character index to populate
11769      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
11770      * content
11771      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
11772      * @hide
11773      */
11774     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
11775             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
11776             float viewportToContentVerticalOffset) {
11777         final int minLine = mLayout.getLineForOffset(startIndex);
11778         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
11779         for (int line = minLine; line <= maxLine; ++line) {
11780             final int lineStart = mLayout.getLineStart(line);
11781             final int lineEnd = mLayout.getLineEnd(line);
11782             final int offsetStart = Math.max(lineStart, startIndex);
11783             final int offsetEnd = Math.min(lineEnd, endIndex);
11784             final boolean ltrLine =
11785                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
11786             final float[] widths = new float[offsetEnd - offsetStart];
11787             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
11788             final float top = mLayout.getLineTop(line);
11789             final float bottom = mLayout.getLineBottom(line);
11790             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
11791                 final float charWidth = widths[offset - offsetStart];
11792                 final boolean isRtl = mLayout.isRtlCharAt(offset);
11793                 final float primary = mLayout.getPrimaryHorizontal(offset);
11794                 final float secondary = mLayout.getSecondaryHorizontal(offset);
11795                 // TODO: This doesn't work perfectly for text with custom styles and
11796                 // TAB chars.
11797                 final float left;
11798                 final float right;
11799                 if (ltrLine) {
11800                     if (isRtl) {
11801                         left = secondary - charWidth;
11802                         right = secondary;
11803                     } else {
11804                         left = primary;
11805                         right = primary + charWidth;
11806                     }
11807                 } else {
11808                     if (!isRtl) {
11809                         left = secondary;
11810                         right = secondary + charWidth;
11811                     } else {
11812                         left = primary - charWidth;
11813                         right = primary;
11814                     }
11815                 }
11816                 // TODO: Check top-right and bottom-left as well.
11817                 final float localLeft = left + viewportToContentHorizontalOffset;
11818                 final float localRight = right + viewportToContentHorizontalOffset;
11819                 final float localTop = top + viewportToContentVerticalOffset;
11820                 final float localBottom = bottom + viewportToContentVerticalOffset;
11821                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
11822                 final boolean isBottomRightVisible =
11823                         isPositionVisible(localRight, localBottom);
11824                 int characterBoundsFlags = 0;
11825                 if (isTopLeftVisible || isBottomRightVisible) {
11826                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
11827                 }
11828                 if (!isTopLeftVisible || !isBottomRightVisible) {
11829                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
11830                 }
11831                 if (isRtl) {
11832                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
11833                 }
11834                 // Here offset is the index in Java chars.
11835                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
11836                         localBottom, characterBoundsFlags);
11837             }
11838         }
11839     }
11840
11841     /**
11842      * @hide
11843      */
11844     public boolean isPositionVisible(final float positionX, final float positionY) {
11845         synchronized (TEMP_POSITION) {
11846             final float[] position = TEMP_POSITION;
11847             position[0] = positionX;
11848             position[1] = positionY;
11849             View view = this;
11850
11851             while (view != null) {
11852                 if (view != this) {
11853                     // Local scroll is already taken into account in positionX/Y
11854                     position[0] -= view.getScrollX();
11855                     position[1] -= view.getScrollY();
11856                 }
11857
11858                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
11859                         || position[1] > view.getHeight()) {
11860                     return false;
11861                 }
11862
11863                 if (!view.getMatrix().isIdentity()) {
11864                     view.getMatrix().mapPoints(position);
11865                 }
11866
11867                 position[0] += view.getLeft();
11868                 position[1] += view.getTop();
11869
11870                 final ViewParent parent = view.getParent();
11871                 if (parent instanceof View) {
11872                     view = (View) parent;
11873                 } else {
11874                     // We've reached the ViewRoot, stop iterating
11875                     view = null;
11876                 }
11877             }
11878         }
11879
11880         // We've been able to walk up the view hierarchy and the position was never clipped
11881         return true;
11882     }
11883
11884     /**
11885      * Performs an accessibility action after it has been offered to the
11886      * delegate.
11887      *
11888      * @hide
11889      */
11890     @Override
11891     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
11892         if (mEditor != null
11893                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
11894             return true;
11895         }
11896         switch (action) {
11897             case AccessibilityNodeInfo.ACTION_CLICK: {
11898                 return performAccessibilityActionClick(arguments);
11899             }
11900             case AccessibilityNodeInfo.ACTION_COPY: {
11901                 if (isFocused() && canCopy()) {
11902                     if (onTextContextMenuItem(ID_COPY)) {
11903                         return true;
11904                     }
11905                 }
11906             } return false;
11907             case AccessibilityNodeInfo.ACTION_PASTE: {
11908                 if (isFocused() && canPaste()) {
11909                     if (onTextContextMenuItem(ID_PASTE)) {
11910                         return true;
11911                     }
11912                 }
11913             } return false;
11914             case AccessibilityNodeInfo.ACTION_CUT: {
11915                 if (isFocused() && canCut()) {
11916                     if (onTextContextMenuItem(ID_CUT)) {
11917                         return true;
11918                     }
11919                 }
11920             } return false;
11921             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
11922                 ensureIterableTextForAccessibilitySelectable();
11923                 CharSequence text = getIterableTextForAccessibility();
11924                 if (text == null) {
11925                     return false;
11926                 }
11927                 final int start = (arguments != null) ? arguments.getInt(
11928                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
11929                 final int end = (arguments != null) ? arguments.getInt(
11930                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
11931                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
11932                     // No arguments clears the selection.
11933                     if (start == end && end == -1) {
11934                         Selection.removeSelection((Spannable) text);
11935                         return true;
11936                     }
11937                     if (start >= 0 && start <= end && end <= text.length()) {
11938                         Selection.setSelection((Spannable) text, start, end);
11939                         // Make sure selection mode is engaged.
11940                         if (mEditor != null) {
11941                             mEditor.startSelectionActionModeAsync(false);
11942                         }
11943                         return true;
11944                     }
11945                 }
11946             } return false;
11947             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
11948             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
11949                 ensureIterableTextForAccessibilitySelectable();
11950                 return super.performAccessibilityActionInternal(action, arguments);
11951             }
11952             case ACCESSIBILITY_ACTION_SHARE: {
11953                 if (isFocused() && canShare()) {
11954                     if (onTextContextMenuItem(ID_SHARE)) {
11955                         return true;
11956                     }
11957                 }
11958             } return false;
11959             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
11960                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
11961                     return false;
11962                 }
11963                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
11964                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
11965                 setText(text);
11966                 if (mText != null) {
11967                     int updatedTextLength = mText.length();
11968                     if (updatedTextLength > 0) {
11969                         Selection.setSelection(mSpannable, updatedTextLength);
11970                     }
11971                 }
11972             } return true;
11973             default: {
11974                 return super.performAccessibilityActionInternal(action, arguments);
11975             }
11976         }
11977     }
11978
11979     private boolean performAccessibilityActionClick(Bundle arguments) {
11980         boolean handled = false;
11981
11982         if (!isEnabled()) {
11983             return false;
11984         }
11985
11986         if (isClickable() || isLongClickable()) {
11987             // Simulate View.onTouchEvent for an ACTION_UP event
11988             if (isFocusable() && !isFocused()) {
11989                 requestFocus();
11990             }
11991
11992             performClick();
11993             handled = true;
11994         }
11995
11996         // Show the IME, except when selecting in read-only text.
11997         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
11998                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
11999             final InputMethodManager imm = getInputMethodManager();
12000             viewClicked(imm);
12001             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
12002                 handled |= imm.showSoftInput(this, 0);
12003             }
12004         }
12005
12006         return handled;
12007     }
12008
12009     private boolean hasSpannableText() {
12010         return mText != null && mText instanceof Spannable;
12011     }
12012
12013     /** @hide */
12014     @Override
12015     public void sendAccessibilityEventInternal(int eventType) {
12016         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
12017             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
12018         }
12019
12020         super.sendAccessibilityEventInternal(eventType);
12021     }
12022
12023     @Override
12024     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
12025         // Do not send scroll events since first they are not interesting for
12026         // accessibility and second such events a generated too frequently.
12027         // For details see the implementation of bringTextIntoView().
12028         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
12029             return;
12030         }
12031         super.sendAccessibilityEventUnchecked(event);
12032     }
12033
12034     /**
12035      * Returns the text that should be exposed to accessibility services.
12036      * <p>
12037      * This approximates what is displayed visually. If the user has specified
12038      * that accessibility services should speak passwords, this method will
12039      * bypass any password transformation method and return unobscured text.
12040      *
12041      * @return the text that should be exposed to accessibility services, may
12042      *         be {@code null} if no text is set
12043      */
12044     @Nullable
12045     @UnsupportedAppUsage
12046     private CharSequence getTextForAccessibility() {
12047         // If the text is empty, we must be showing the hint text.
12048         if (TextUtils.isEmpty(mText)) {
12049             return mHint;
12050         }
12051
12052         // Otherwise, return whatever text is being displayed.
12053         return TextUtils.trimToParcelableSize(mTransformed);
12054     }
12055
12056     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
12057             int fromIndex, int removedCount, int addedCount) {
12058         AccessibilityEvent event =
12059                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
12060         event.setFromIndex(fromIndex);
12061         event.setRemovedCount(removedCount);
12062         event.setAddedCount(addedCount);
12063         event.setBeforeText(beforeText);
12064         sendAccessibilityEventUnchecked(event);
12065     }
12066
12067     private InputMethodManager getInputMethodManager() {
12068         return getContext().getSystemService(InputMethodManager.class);
12069     }
12070
12071     /**
12072      * Returns whether this text view is a current input method target.  The
12073      * default implementation just checks with {@link InputMethodManager}.
12074      * @return True if the TextView is a current input method target; false otherwise.
12075      */
12076     public boolean isInputMethodTarget() {
12077         InputMethodManager imm = getInputMethodManager();
12078         return imm != null && imm.isActive(this);
12079     }
12080
12081     static final int ID_SELECT_ALL = android.R.id.selectAll;
12082     static final int ID_UNDO = android.R.id.undo;
12083     static final int ID_REDO = android.R.id.redo;
12084     static final int ID_CUT = android.R.id.cut;
12085     static final int ID_COPY = android.R.id.copy;
12086     static final int ID_PASTE = android.R.id.paste;
12087     static final int ID_SHARE = android.R.id.shareText;
12088     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
12089     static final int ID_REPLACE = android.R.id.replaceText;
12090     static final int ID_ASSIST = android.R.id.textAssist;
12091     static final int ID_AUTOFILL = android.R.id.autofill;
12092
12093     /**
12094      * Called when a context menu option for the text view is selected.  Currently
12095      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
12096      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
12097      *
12098      * @return true if the context menu item action was performed.
12099      */
12100     public boolean onTextContextMenuItem(int id) {
12101         int min = 0;
12102         int max = mText.length();
12103
12104         if (isFocused()) {
12105             final int selStart = getSelectionStart();
12106             final int selEnd = getSelectionEnd();
12107
12108             min = Math.max(0, Math.min(selStart, selEnd));
12109             max = Math.max(0, Math.max(selStart, selEnd));
12110         }
12111
12112         switch (id) {
12113             case ID_SELECT_ALL:
12114                 final boolean hadSelection = hasSelection();
12115                 selectAllText();
12116                 if (mEditor != null && hadSelection) {
12117                     mEditor.invalidateActionModeAsync();
12118                 }
12119                 return true;
12120
12121             case ID_UNDO:
12122                 if (mEditor != null) {
12123                     mEditor.undo();
12124                 }
12125                 return true;  // Returns true even if nothing was undone.
12126
12127             case ID_REDO:
12128                 if (mEditor != null) {
12129                     mEditor.redo();
12130                 }
12131                 return true;  // Returns true even if nothing was undone.
12132
12133             case ID_PASTE:
12134                 paste(min, max, true /* withFormatting */);
12135                 return true;
12136
12137             case ID_PASTE_AS_PLAIN_TEXT:
12138                 paste(min, max, false /* withFormatting */);
12139                 return true;
12140
12141             case ID_CUT:
12142                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
12143                 if (setPrimaryClip(cutData)) {
12144                     deleteText_internal(min, max);
12145                 } else {
12146                     Toast.makeText(getContext(),
12147                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12148                             Toast.LENGTH_SHORT).show();
12149                 }
12150                 return true;
12151
12152             case ID_COPY:
12153                 // For link action mode in a non-selectable/non-focusable TextView,
12154                 // make sure that we set the appropriate min/max.
12155                 final int selStart = getSelectionStart();
12156                 final int selEnd = getSelectionEnd();
12157                 min = Math.max(0, Math.min(selStart, selEnd));
12158                 max = Math.max(0, Math.max(selStart, selEnd));
12159                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
12160                 if (setPrimaryClip(copyData)) {
12161                     stopTextActionMode();
12162                 } else {
12163                     Toast.makeText(getContext(),
12164                             com.android.internal.R.string.failed_to_copy_to_clipboard,
12165                             Toast.LENGTH_SHORT).show();
12166                 }
12167                 return true;
12168
12169             case ID_REPLACE:
12170                 if (mEditor != null) {
12171                     mEditor.replace();
12172                 }
12173                 return true;
12174
12175             case ID_SHARE:
12176                 shareSelectedText();
12177                 return true;
12178
12179             case ID_AUTOFILL:
12180                 requestAutofill();
12181                 stopTextActionMode();
12182                 return true;
12183         }
12184         return false;
12185     }
12186
12187     @UnsupportedAppUsage
12188     CharSequence getTransformedText(int start, int end) {
12189         return removeSuggestionSpans(mTransformed.subSequence(start, end));
12190     }
12191
12192     @Override
12193     public boolean performLongClick() {
12194         boolean handled = false;
12195         boolean performedHapticFeedback = false;
12196
12197         if (mEditor != null) {
12198             mEditor.mIsBeingLongClicked = true;
12199         }
12200
12201         if (super.performLongClick()) {
12202             handled = true;
12203             performedHapticFeedback = true;
12204         }
12205
12206         if (mEditor != null) {
12207             handled |= mEditor.performLongClick(handled);
12208             mEditor.mIsBeingLongClicked = false;
12209         }
12210
12211         if (handled) {
12212             if (!performedHapticFeedback) {
12213               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
12214             }
12215             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
12216         } else {
12217             MetricsLogger.action(
12218                     mContext,
12219                     MetricsEvent.TEXT_LONGPRESS,
12220                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
12221         }
12222
12223         return handled;
12224     }
12225
12226     @Override
12227     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
12228         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
12229         if (mEditor != null) {
12230             mEditor.onScrollChanged();
12231         }
12232     }
12233
12234     /**
12235      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
12236      * by the IME or by the spell checker as the user types. This is done by adding
12237      * {@link SuggestionSpan}s to the text.
12238      *
12239      * When suggestions are enabled (default), this list of suggestions will be displayed when the
12240      * user asks for them on these parts of the text. This value depends on the inputType of this
12241      * TextView.
12242      *
12243      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
12244      *
12245      * In addition, the type variation must be one of
12246      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
12247      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
12248      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
12249      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
12250      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
12251      *
12252      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
12253      *
12254      * @return true if the suggestions popup window is enabled, based on the inputType.
12255      */
12256     public boolean isSuggestionsEnabled() {
12257         if (mEditor == null) return false;
12258         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
12259             return false;
12260         }
12261         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
12262
12263         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
12264         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
12265                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
12266                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
12267                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
12268                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
12269     }
12270
12271     /**
12272      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12273      * selection is initiated in this View.
12274      *
12275      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
12276      * Paste, Replace and Share actions, depending on what this View supports.
12277      *
12278      * <p>A custom implementation can add new entries in the default menu in its
12279      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
12280      * method. The default actions can also be removed from the menu using
12281      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12282      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
12283      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
12284      *
12285      * <p>Returning false from
12286      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
12287      * will prevent the action mode from being started.
12288      *
12289      * <p>Action click events should be handled by the custom implementation of
12290      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
12291      * android.view.MenuItem)}.
12292      *
12293      * <p>Note that text selection mode is not started when a TextView receives focus and the
12294      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
12295      * that case, to allow for quick replacement.
12296      */
12297     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
12298         createEditorIfNeeded();
12299         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
12300     }
12301
12302     /**
12303      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
12304      *
12305      * @return The current custom selection callback.
12306      */
12307     public ActionMode.Callback getCustomSelectionActionModeCallback() {
12308         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
12309     }
12310
12311     /**
12312      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
12313      * insertion is initiated in this View.
12314      * The standard implementation populates the menu with a subset of Select All,
12315      * Paste and Replace actions, depending on what this View supports.
12316      *
12317      * <p>A custom implementation can add new entries in the default menu in its
12318      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
12319      * android.view.Menu)} method. The default actions can also be removed from the menu using
12320      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
12321      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
12322      *
12323      * <p>Returning false from
12324      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
12325      * android.view.Menu)} will prevent the action mode from being started.</p>
12326      *
12327      * <p>Action click events should be handled by the custom implementation of
12328      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
12329      * android.view.MenuItem)}.</p>
12330      *
12331      * <p>Note that text insertion mode is not started when a TextView receives focus and the
12332      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
12333      */
12334     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
12335         createEditorIfNeeded();
12336         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
12337     }
12338
12339     /**
12340      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
12341      *
12342      * @return The current custom insertion callback.
12343      */
12344     public ActionMode.Callback getCustomInsertionActionModeCallback() {
12345         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
12346     }
12347
12348     /**
12349      * Sets the {@link TextClassifier} for this TextView.
12350      */
12351     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
12352         mTextClassifier = textClassifier;
12353     }
12354
12355     /**
12356      * Returns the {@link TextClassifier} used by this TextView.
12357      * If no TextClassifier has been set, this TextView uses the default set by the
12358      * {@link TextClassificationManager}.
12359      */
12360     @NonNull
12361     public TextClassifier getTextClassifier() {
12362         if (mTextClassifier == null) {
12363             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12364             if (tcm != null) {
12365                 return tcm.getTextClassifier();
12366             }
12367             return TextClassifier.NO_OP;
12368         }
12369         return mTextClassifier;
12370     }
12371
12372     /**
12373      * Returns a session-aware text classifier.
12374      * This method creates one if none already exists or the current one is destroyed.
12375      */
12376     @NonNull
12377     TextClassifier getTextClassificationSession() {
12378         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
12379             final TextClassificationManager tcm = getTextClassificationManagerForUser();
12380             if (tcm != null) {
12381                 final String widgetType;
12382                 if (isTextEditable()) {
12383                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
12384                 } else if (isTextSelectable()) {
12385                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
12386                 } else {
12387                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
12388                 }
12389                 mTextClassificationContext = new TextClassificationContext.Builder(
12390                         mContext.getPackageName(), widgetType)
12391                         .build();
12392                 if (mTextClassifier != null) {
12393                     mTextClassificationSession = tcm.createTextClassificationSession(
12394                             mTextClassificationContext, mTextClassifier);
12395                 } else {
12396                     mTextClassificationSession = tcm.createTextClassificationSession(
12397                             mTextClassificationContext);
12398                 }
12399             } else {
12400                 mTextClassificationSession = TextClassifier.NO_OP;
12401             }
12402         }
12403         return mTextClassificationSession;
12404     }
12405
12406     /**
12407      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
12408      * @see #getTextClassificationSession()
12409      */
12410     @Nullable
12411     TextClassificationContext getTextClassificationContext() {
12412         return mTextClassificationContext;
12413     }
12414
12415     /**
12416      * Returns true if this TextView uses a no-op TextClassifier.
12417      */
12418     boolean usesNoOpTextClassifier() {
12419         return getTextClassifier() == TextClassifier.NO_OP;
12420     }
12421
12422
12423     /**
12424      * Starts an ActionMode for the specified TextLinkSpan.
12425      *
12426      * @return Whether or not we're attempting to start the action mode.
12427      * @hide
12428      */
12429     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12430         Preconditions.checkNotNull(clickedSpan);
12431
12432         if (!(mText instanceof Spanned)) {
12433             return false;
12434         }
12435
12436         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
12437         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
12438
12439         if (start < 0 || end > mText.length() || start >= end) {
12440             return false;
12441         }
12442
12443         createEditorIfNeeded();
12444         mEditor.startLinkActionModeAsync(start, end);
12445         return true;
12446     }
12447
12448     /**
12449      * Handles a click on the specified TextLinkSpan.
12450      *
12451      * @return Whether or not the click is being handled.
12452      * @hide
12453      */
12454     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
12455         Preconditions.checkNotNull(clickedSpan);
12456         if (mText instanceof Spanned) {
12457             final Spanned spanned = (Spanned) mText;
12458             final int start = spanned.getSpanStart(clickedSpan);
12459             final int end = spanned.getSpanEnd(clickedSpan);
12460             if (start >= 0 && end <= mText.length() && start < end) {
12461                 final TextClassification.Request request = new TextClassification.Request.Builder(
12462                         mText, start, end)
12463                         .setDefaultLocales(getTextLocales())
12464                         .build();
12465                 final Supplier<TextClassification> supplier = () ->
12466                         getTextClassifier().classifyText(request);
12467                 final Consumer<TextClassification> consumer = classification -> {
12468                     if (classification != null) {
12469                         if (!classification.getActions().isEmpty()) {
12470                             try {
12471                                 classification.getActions().get(0).getActionIntent().send();
12472                             } catch (PendingIntent.CanceledException e) {
12473                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
12474                             }
12475                         } else {
12476                             Log.d(LOG_TAG, "No link action to perform");
12477                         }
12478                     } else {
12479                         // classification == null
12480                         Log.d(LOG_TAG, "Timeout while classifying text");
12481                     }
12482                 };
12483                 CompletableFuture.supplyAsync(supplier)
12484                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
12485                         .thenAccept(consumer);
12486                 return true;
12487             }
12488         }
12489         return false;
12490     }
12491
12492     /**
12493      * @hide
12494      */
12495     @UnsupportedAppUsage
12496     protected void stopTextActionMode() {
12497         if (mEditor != null) {
12498             mEditor.stopTextActionMode();
12499         }
12500     }
12501
12502     /** @hide */
12503     public void hideFloatingToolbar(int durationMs) {
12504         if (mEditor != null) {
12505             mEditor.hideFloatingToolbar(durationMs);
12506         }
12507     }
12508
12509     boolean canUndo() {
12510         return mEditor != null && mEditor.canUndo();
12511     }
12512
12513     boolean canRedo() {
12514         return mEditor != null && mEditor.canRedo();
12515     }
12516
12517     boolean canCut() {
12518         if (hasPasswordTransformationMethod()) {
12519             return false;
12520         }
12521
12522         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
12523                 && mEditor.mKeyListener != null) {
12524             return true;
12525         }
12526
12527         return false;
12528     }
12529
12530     boolean canCopy() {
12531         if (hasPasswordTransformationMethod()) {
12532             return false;
12533         }
12534
12535         if (mText.length() > 0 && hasSelection() && mEditor != null) {
12536             return true;
12537         }
12538
12539         return false;
12540     }
12541
12542     boolean canShare() {
12543         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
12544             return false;
12545         }
12546         return canCopy();
12547     }
12548
12549     boolean isDeviceProvisioned() {
12550         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
12551             mDeviceProvisionedState = Settings.Global.getInt(
12552                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
12553                     ? DEVICE_PROVISIONED_YES
12554                     : DEVICE_PROVISIONED_NO;
12555         }
12556         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
12557     }
12558
12559     @UnsupportedAppUsage
12560     boolean canPaste() {
12561         return (mText instanceof Editable
12562                 && mEditor != null && mEditor.mKeyListener != null
12563                 && getSelectionStart() >= 0
12564                 && getSelectionEnd() >= 0
12565                 && getClipboardManagerForUser().hasPrimaryClip());
12566     }
12567
12568     boolean canPasteAsPlainText() {
12569         if (!canPaste()) {
12570             return false;
12571         }
12572
12573         final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
12574         final ClipDescription description = clipData.getDescription();
12575         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
12576         final CharSequence text = clipData.getItemAt(0).getText();
12577         if (isPlainType && (text instanceof Spanned)) {
12578             Spanned spanned = (Spanned) text;
12579             if (TextUtils.hasStyleSpan(spanned)) {
12580                 return true;
12581             }
12582         }
12583         return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
12584     }
12585
12586     boolean canProcessText() {
12587         if (getId() == View.NO_ID) {
12588             return false;
12589         }
12590         return canShare();
12591     }
12592
12593     boolean canSelectAllText() {
12594         return canSelectText() && !hasPasswordTransformationMethod()
12595                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
12596     }
12597
12598     boolean selectAllText() {
12599         if (mEditor != null) {
12600             // Hide the toolbar before changing the selection to avoid flickering.
12601             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
12602         }
12603         final int length = mText.length();
12604         Selection.setSelection(mSpannable, 0, length);
12605         return length > 0;
12606     }
12607
12608     void replaceSelectionWithText(CharSequence text) {
12609         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
12610     }
12611
12612     /**
12613      * Paste clipboard content between min and max positions.
12614      */
12615     private void paste(int min, int max, boolean withFormatting) {
12616         ClipboardManager clipboard = getClipboardManagerForUser();
12617         ClipData clip = clipboard.getPrimaryClip();
12618         if (clip != null) {
12619             boolean didFirst = false;
12620             for (int i = 0; i < clip.getItemCount(); i++) {
12621                 final CharSequence paste;
12622                 if (withFormatting) {
12623                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
12624                 } else {
12625                     // Get an item as text and remove all spans by toString().
12626                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
12627                     paste = (text instanceof Spanned) ? text.toString() : text;
12628                 }
12629                 if (paste != null) {
12630                     if (!didFirst) {
12631                         Selection.setSelection(mSpannable, max);
12632                         ((Editable) mText).replace(min, max, paste);
12633                         didFirst = true;
12634                     } else {
12635                         ((Editable) mText).insert(getSelectionEnd(), "\n");
12636                         ((Editable) mText).insert(getSelectionEnd(), paste);
12637                     }
12638                 }
12639             }
12640             sLastCutCopyOrTextChangedTime = 0;
12641         }
12642     }
12643
12644     private void shareSelectedText() {
12645         String selectedText = getSelectedText();
12646         if (selectedText != null && !selectedText.isEmpty()) {
12647             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
12648             sharingIntent.setType("text/plain");
12649             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
12650             selectedText = TextUtils.trimToParcelableSize(selectedText);
12651             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
12652             getContext().startActivity(Intent.createChooser(sharingIntent, null));
12653             Selection.setSelection(mSpannable, getSelectionEnd());
12654         }
12655     }
12656
12657     @CheckResult
12658     private boolean setPrimaryClip(ClipData clip) {
12659         ClipboardManager clipboard = getClipboardManagerForUser();
12660         try {
12661             clipboard.setPrimaryClip(clip);
12662         } catch (Throwable t) {
12663             return false;
12664         }
12665         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
12666         return true;
12667     }
12668
12669     /**
12670      * Get the character offset closest to the specified absolute position. A typical use case is to
12671      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
12672      *
12673      * @param x The horizontal absolute position of a point on screen
12674      * @param y The vertical absolute position of a point on screen
12675      * @return the character offset for the character whose position is closest to the specified
12676      *  position. Returns -1 if there is no layout.
12677      */
12678     public int getOffsetForPosition(float x, float y) {
12679         if (getLayout() == null) return -1;
12680         final int line = getLineAtCoordinate(y);
12681         final int offset = getOffsetAtCoordinate(line, x);
12682         return offset;
12683     }
12684
12685     float convertToLocalHorizontalCoordinate(float x) {
12686         x -= getTotalPaddingLeft();
12687         // Clamp the position to inside of the view.
12688         x = Math.max(0.0f, x);
12689         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
12690         x += getScrollX();
12691         return x;
12692     }
12693
12694     @UnsupportedAppUsage
12695     int getLineAtCoordinate(float y) {
12696         y -= getTotalPaddingTop();
12697         // Clamp the position to inside of the view.
12698         y = Math.max(0.0f, y);
12699         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
12700         y += getScrollY();
12701         return getLayout().getLineForVertical((int) y);
12702     }
12703
12704     int getLineAtCoordinateUnclamped(float y) {
12705         y -= getTotalPaddingTop();
12706         y += getScrollY();
12707         return getLayout().getLineForVertical((int) y);
12708     }
12709
12710     int getOffsetAtCoordinate(int line, float x) {
12711         x = convertToLocalHorizontalCoordinate(x);
12712         return getLayout().getOffsetForHorizontal(line, x);
12713     }
12714
12715     @Override
12716     public boolean onDragEvent(DragEvent event) {
12717         switch (event.getAction()) {
12718             case DragEvent.ACTION_DRAG_STARTED:
12719                 return mEditor != null && mEditor.hasInsertionController();
12720
12721             case DragEvent.ACTION_DRAG_ENTERED:
12722                 TextView.this.requestFocus();
12723                 return true;
12724
12725             case DragEvent.ACTION_DRAG_LOCATION:
12726                 if (mText instanceof Spannable) {
12727                     final int offset = getOffsetForPosition(event.getX(), event.getY());
12728                     Selection.setSelection(mSpannable, offset);
12729                 }
12730                 return true;
12731
12732             case DragEvent.ACTION_DROP:
12733                 if (mEditor != null) mEditor.onDrop(event);
12734                 return true;
12735
12736             case DragEvent.ACTION_DRAG_ENDED:
12737             case DragEvent.ACTION_DRAG_EXITED:
12738             default:
12739                 return true;
12740         }
12741     }
12742
12743     boolean isInBatchEditMode() {
12744         if (mEditor == null) return false;
12745         final Editor.InputMethodState ims = mEditor.mInputMethodState;
12746         if (ims != null) {
12747             return ims.mBatchEditNesting > 0;
12748         }
12749         return mEditor.mInBatchEditControllers;
12750     }
12751
12752     @Override
12753     public void onRtlPropertiesChanged(int layoutDirection) {
12754         super.onRtlPropertiesChanged(layoutDirection);
12755
12756         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
12757         if (mTextDir != newTextDir) {
12758             mTextDir = newTextDir;
12759             if (mLayout != null) {
12760                 checkForRelayout();
12761             }
12762         }
12763     }
12764
12765     /**
12766      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
12767      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
12768      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
12769      * return value may not be the same as the one TextView uses if the View's layout direction is
12770      * not resolved or detached from parent root view.
12771      */
12772     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
12773         if (hasPasswordTransformationMethod()) {
12774             // passwords fields should be LTR
12775             return TextDirectionHeuristics.LTR;
12776         }
12777
12778         if (mEditor != null
12779                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
12780                     == EditorInfo.TYPE_CLASS_PHONE) {
12781             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
12782             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
12783             // RTL digits.
12784             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
12785             final String zero = symbols.getDigitStrings()[0];
12786             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
12787             // direction.
12788             final int firstCodepoint = zero.codePointAt(0);
12789             final byte digitDirection = Character.getDirectionality(firstCodepoint);
12790             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
12791                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
12792                 return TextDirectionHeuristics.RTL;
12793             } else {
12794                 return TextDirectionHeuristics.LTR;
12795             }
12796         }
12797
12798         // Always need to resolve layout direction first
12799         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
12800
12801         // Now, we can select the heuristic
12802         switch (getTextDirection()) {
12803             default:
12804             case TEXT_DIRECTION_FIRST_STRONG:
12805                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
12806                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
12807             case TEXT_DIRECTION_ANY_RTL:
12808                 return TextDirectionHeuristics.ANYRTL_LTR;
12809             case TEXT_DIRECTION_LTR:
12810                 return TextDirectionHeuristics.LTR;
12811             case TEXT_DIRECTION_RTL:
12812                 return TextDirectionHeuristics.RTL;
12813             case TEXT_DIRECTION_LOCALE:
12814                 return TextDirectionHeuristics.LOCALE;
12815             case TEXT_DIRECTION_FIRST_STRONG_LTR:
12816                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
12817             case TEXT_DIRECTION_FIRST_STRONG_RTL:
12818                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
12819         }
12820     }
12821
12822     /**
12823      * @hide
12824      */
12825     @Override
12826     public void onResolveDrawables(int layoutDirection) {
12827         // No need to resolve twice
12828         if (mLastLayoutDirection == layoutDirection) {
12829             return;
12830         }
12831         mLastLayoutDirection = layoutDirection;
12832
12833         // Resolve drawables
12834         if (mDrawables != null) {
12835             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
12836                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
12837                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
12838                 applyCompoundDrawableTint();
12839             }
12840         }
12841     }
12842
12843     /**
12844      * Prepares a drawable for display by propagating layout direction and
12845      * drawable state.
12846      *
12847      * @param dr the drawable to prepare
12848      */
12849     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
12850         if (dr == null) {
12851             return;
12852         }
12853
12854         dr.setLayoutDirection(getLayoutDirection());
12855
12856         if (dr.isStateful()) {
12857             dr.setState(getDrawableState());
12858             dr.jumpToCurrentState();
12859         }
12860     }
12861
12862     /**
12863      * @hide
12864      */
12865     protected void resetResolvedDrawables() {
12866         super.resetResolvedDrawables();
12867         mLastLayoutDirection = -1;
12868     }
12869
12870     /**
12871      * @hide
12872      */
12873     protected void viewClicked(InputMethodManager imm) {
12874         if (imm != null) {
12875             imm.viewClicked(this);
12876         }
12877     }
12878
12879     /**
12880      * Deletes the range of text [start, end[.
12881      * @hide
12882      */
12883     @UnsupportedAppUsage
12884     protected void deleteText_internal(int start, int end) {
12885         ((Editable) mText).delete(start, end);
12886     }
12887
12888     /**
12889      * Replaces the range of text [start, end[ by replacement text
12890      * @hide
12891      */
12892     protected void replaceText_internal(int start, int end, CharSequence text) {
12893         ((Editable) mText).replace(start, end, text);
12894     }
12895
12896     /**
12897      * Sets a span on the specified range of text
12898      * @hide
12899      */
12900     protected void setSpan_internal(Object span, int start, int end, int flags) {
12901         ((Editable) mText).setSpan(span, start, end, flags);
12902     }
12903
12904     /**
12905      * Moves the cursor to the specified offset position in text
12906      * @hide
12907      */
12908     protected void setCursorPosition_internal(int start, int end) {
12909         Selection.setSelection(((Editable) mText), start, end);
12910     }
12911
12912     /**
12913      * An Editor should be created as soon as any of the editable-specific fields (grouped
12914      * inside the Editor object) is assigned to a non-default value.
12915      * This method will create the Editor if needed.
12916      *
12917      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
12918      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
12919      * Editor for backward compatibility, as soon as one of these fields is assigned.
12920      *
12921      * Also note that for performance reasons, the mEditor is created when needed, but not
12922      * reset when no more edit-specific fields are needed.
12923      */
12924     @UnsupportedAppUsage
12925     private void createEditorIfNeeded() {
12926         if (mEditor == null) {
12927             mEditor = new Editor(this);
12928         }
12929     }
12930
12931     /**
12932      * @hide
12933      */
12934     @Override
12935     @UnsupportedAppUsage
12936     public CharSequence getIterableTextForAccessibility() {
12937         return mText;
12938     }
12939
12940     private void ensureIterableTextForAccessibilitySelectable() {
12941         if (!(mText instanceof Spannable)) {
12942             setText(mText, BufferType.SPANNABLE);
12943         }
12944     }
12945
12946     /**
12947      * @hide
12948      */
12949     @Override
12950     public TextSegmentIterator getIteratorForGranularity(int granularity) {
12951         switch (granularity) {
12952             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
12953                 Spannable text = (Spannable) getIterableTextForAccessibility();
12954                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
12955                     AccessibilityIterators.LineTextSegmentIterator iterator =
12956                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
12957                     iterator.initialize(text, getLayout());
12958                     return iterator;
12959                 }
12960             } break;
12961             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
12962                 Spannable text = (Spannable) getIterableTextForAccessibility();
12963                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
12964                     AccessibilityIterators.PageTextSegmentIterator iterator =
12965                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
12966                     iterator.initialize(this);
12967                     return iterator;
12968                 }
12969             } break;
12970         }
12971         return super.getIteratorForGranularity(granularity);
12972     }
12973
12974     /**
12975      * @hide
12976      */
12977     @Override
12978     public int getAccessibilitySelectionStart() {
12979         return getSelectionStart();
12980     }
12981
12982     /**
12983      * @hide
12984      */
12985     public boolean isAccessibilitySelectionExtendable() {
12986         return true;
12987     }
12988
12989     /**
12990      * @hide
12991      */
12992     @Override
12993     public int getAccessibilitySelectionEnd() {
12994         return getSelectionEnd();
12995     }
12996
12997     /**
12998      * @hide
12999      */
13000     @Override
13001     public void setAccessibilitySelection(int start, int end) {
13002         if (getAccessibilitySelectionStart() == start
13003                 && getAccessibilitySelectionEnd() == end) {
13004             return;
13005         }
13006         CharSequence text = getIterableTextForAccessibility();
13007         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
13008             Selection.setSelection((Spannable) text, start, end);
13009         } else {
13010             Selection.removeSelection((Spannable) text);
13011         }
13012         // Hide all selection controllers used for adjusting selection
13013         // since we are doing so explicitlty by other means and these
13014         // controllers interact with how selection behaves.
13015         if (mEditor != null) {
13016             mEditor.hideCursorAndSpanControllers();
13017             mEditor.stopTextActionMode();
13018         }
13019     }
13020
13021     /** @hide */
13022     @Override
13023     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
13024         super.encodeProperties(stream);
13025
13026         TruncateAt ellipsize = getEllipsize();
13027         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
13028         stream.addProperty("text:textSize", getTextSize());
13029         stream.addProperty("text:scaledTextSize", getScaledTextSize());
13030         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
13031         stream.addProperty("text:selectionStart", getSelectionStart());
13032         stream.addProperty("text:selectionEnd", getSelectionEnd());
13033         stream.addProperty("text:curTextColor", mCurTextColor);
13034         stream.addProperty("text:text", mText == null ? null : mText.toString());
13035         stream.addProperty("text:gravity", mGravity);
13036     }
13037
13038     /**
13039      * User interface state that is stored by TextView for implementing
13040      * {@link View#onSaveInstanceState}.
13041      */
13042     public static class SavedState extends BaseSavedState {
13043         int selStart = -1;
13044         int selEnd = -1;
13045         @UnsupportedAppUsage
13046         CharSequence text;
13047         boolean frozenWithFocus;
13048         CharSequence error;
13049         ParcelableParcel editorState;  // Optional state from Editor.
13050
13051         SavedState(Parcelable superState) {
13052             super(superState);
13053         }
13054
13055         @Override
13056         public void writeToParcel(Parcel out, int flags) {
13057             super.writeToParcel(out, flags);
13058             out.writeInt(selStart);
13059             out.writeInt(selEnd);
13060             out.writeInt(frozenWithFocus ? 1 : 0);
13061             TextUtils.writeToParcel(text, out, flags);
13062
13063             if (error == null) {
13064                 out.writeInt(0);
13065             } else {
13066                 out.writeInt(1);
13067                 TextUtils.writeToParcel(error, out, flags);
13068             }
13069
13070             if (editorState == null) {
13071                 out.writeInt(0);
13072             } else {
13073                 out.writeInt(1);
13074                 editorState.writeToParcel(out, flags);
13075             }
13076         }
13077
13078         @Override
13079         public String toString() {
13080             String str = "TextView.SavedState{"
13081                     + Integer.toHexString(System.identityHashCode(this))
13082                     + " start=" + selStart + " end=" + selEnd;
13083             if (text != null) {
13084                 str += " text=" + text;
13085             }
13086             return str + "}";
13087         }
13088
13089         @SuppressWarnings("hiding")
13090         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
13091                 new Parcelable.Creator<SavedState>() {
13092                     public SavedState createFromParcel(Parcel in) {
13093                         return new SavedState(in);
13094                     }
13095
13096                     public SavedState[] newArray(int size) {
13097                         return new SavedState[size];
13098                     }
13099                 };
13100
13101         private SavedState(Parcel in) {
13102             super(in);
13103             selStart = in.readInt();
13104             selEnd = in.readInt();
13105             frozenWithFocus = (in.readInt() != 0);
13106             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13107
13108             if (in.readInt() != 0) {
13109                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
13110             }
13111
13112             if (in.readInt() != 0) {
13113                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
13114             }
13115         }
13116     }
13117
13118     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
13119         private char[] mChars;
13120         private int mStart, mLength;
13121
13122         public CharWrapper(char[] chars, int start, int len) {
13123             mChars = chars;
13124             mStart = start;
13125             mLength = len;
13126         }
13127
13128         /* package */ void set(char[] chars, int start, int len) {
13129             mChars = chars;
13130             mStart = start;
13131             mLength = len;
13132         }
13133
13134         public int length() {
13135             return mLength;
13136         }
13137
13138         public char charAt(int off) {
13139             return mChars[off + mStart];
13140         }
13141
13142         @Override
13143         public String toString() {
13144             return new String(mChars, mStart, mLength);
13145         }
13146
13147         public CharSequence subSequence(int start, int end) {
13148             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13149                 throw new IndexOutOfBoundsException(start + ", " + end);
13150             }
13151
13152             return new String(mChars, start + mStart, end - start);
13153         }
13154
13155         public void getChars(int start, int end, char[] buf, int off) {
13156             if (start < 0 || end < 0 || start > mLength || end > mLength) {
13157                 throw new IndexOutOfBoundsException(start + ", " + end);
13158             }
13159
13160             System.arraycopy(mChars, start + mStart, buf, off, end - start);
13161         }
13162
13163         @Override
13164         public void drawText(BaseCanvas c, int start, int end,
13165                              float x, float y, Paint p) {
13166             c.drawText(mChars, start + mStart, end - start, x, y, p);
13167         }
13168
13169         @Override
13170         public void drawTextRun(BaseCanvas c, int start, int end,
13171                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
13172             int count = end - start;
13173             int contextCount = contextEnd - contextStart;
13174             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
13175                     contextCount, x, y, isRtl, p);
13176         }
13177
13178         public float measureText(int start, int end, Paint p) {
13179             return p.measureText(mChars, start + mStart, end - start);
13180         }
13181
13182         public int getTextWidths(int start, int end, float[] widths, Paint p) {
13183             return p.getTextWidths(mChars, start + mStart, end - start, widths);
13184         }
13185
13186         public float getTextRunAdvances(int start, int end, int contextStart,
13187                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
13188                 Paint p) {
13189             int count = end - start;
13190             int contextCount = contextEnd - contextStart;
13191             return p.getTextRunAdvances(mChars, start + mStart, count,
13192                     contextStart + mStart, contextCount, isRtl, advances,
13193                     advancesIndex);
13194         }
13195
13196         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
13197                 int offset, int cursorOpt, Paint p) {
13198             int contextCount = contextEnd - contextStart;
13199             return p.getTextRunCursor(mChars, contextStart + mStart,
13200                     contextCount, isRtl, offset + mStart, cursorOpt);
13201         }
13202     }
13203
13204     private static final class Marquee {
13205         // TODO: Add an option to configure this
13206         private static final float MARQUEE_DELTA_MAX = 0.07f;
13207         private static final int MARQUEE_DELAY = 1200;
13208         private static final int MARQUEE_DP_PER_SECOND = 30;
13209
13210         private static final byte MARQUEE_STOPPED = 0x0;
13211         private static final byte MARQUEE_STARTING = 0x1;
13212         private static final byte MARQUEE_RUNNING = 0x2;
13213
13214         private final WeakReference<TextView> mView;
13215         private final Choreographer mChoreographer;
13216
13217         private byte mStatus = MARQUEE_STOPPED;
13218         private final float mPixelsPerMs;
13219         private float mMaxScroll;
13220         private float mMaxFadeScroll;
13221         private float mGhostStart;
13222         private float mGhostOffset;
13223         private float mFadeStop;
13224         private int mRepeatLimit;
13225
13226         private float mScroll;
13227         private long mLastAnimationMs;
13228
13229         Marquee(TextView v) {
13230             final float density = v.getContext().getResources().getDisplayMetrics().density;
13231             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
13232             mView = new WeakReference<TextView>(v);
13233             mChoreographer = Choreographer.getInstance();
13234         }
13235
13236         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
13237             @Override
13238             public void doFrame(long frameTimeNanos) {
13239                 tick();
13240             }
13241         };
13242
13243         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
13244             @Override
13245             public void doFrame(long frameTimeNanos) {
13246                 mStatus = MARQUEE_RUNNING;
13247                 mLastAnimationMs = mChoreographer.getFrameTime();
13248                 tick();
13249             }
13250         };
13251
13252         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
13253             @Override
13254             public void doFrame(long frameTimeNanos) {
13255                 if (mStatus == MARQUEE_RUNNING) {
13256                     if (mRepeatLimit >= 0) {
13257                         mRepeatLimit--;
13258                     }
13259                     start(mRepeatLimit);
13260                 }
13261             }
13262         };
13263
13264         void tick() {
13265             if (mStatus != MARQUEE_RUNNING) {
13266                 return;
13267             }
13268
13269             mChoreographer.removeFrameCallback(mTickCallback);
13270
13271             final TextView textView = mView.get();
13272             if (textView != null && (textView.isFocused() || textView.isSelected())) {
13273                 long currentMs = mChoreographer.getFrameTime();
13274                 long deltaMs = currentMs - mLastAnimationMs;
13275                 mLastAnimationMs = currentMs;
13276                 float deltaPx = deltaMs * mPixelsPerMs;
13277                 mScroll += deltaPx;
13278                 if (mScroll > mMaxScroll) {
13279                     mScroll = mMaxScroll;
13280                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
13281                 } else {
13282                     mChoreographer.postFrameCallback(mTickCallback);
13283                 }
13284                 textView.invalidate();
13285             }
13286         }
13287
13288         void stop() {
13289             mStatus = MARQUEE_STOPPED;
13290             mChoreographer.removeFrameCallback(mStartCallback);
13291             mChoreographer.removeFrameCallback(mRestartCallback);
13292             mChoreographer.removeFrameCallback(mTickCallback);
13293             resetScroll();
13294         }
13295
13296         private void resetScroll() {
13297             mScroll = 0.0f;
13298             final TextView textView = mView.get();
13299             if (textView != null) textView.invalidate();
13300         }
13301
13302         void start(int repeatLimit) {
13303             if (repeatLimit == 0) {
13304                 stop();
13305                 return;
13306             }
13307             mRepeatLimit = repeatLimit;
13308             final TextView textView = mView.get();
13309             if (textView != null && textView.mLayout != null) {
13310                 mStatus = MARQUEE_STARTING;
13311                 mScroll = 0.0f;
13312                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
13313                         - textView.getCompoundPaddingRight();
13314                 final float lineWidth = textView.mLayout.getLineWidth(0);
13315                 final float gap = textWidth / 3.0f;
13316                 mGhostStart = lineWidth - textWidth + gap;
13317                 mMaxScroll = mGhostStart + textWidth;
13318                 mGhostOffset = lineWidth + gap;
13319                 mFadeStop = lineWidth + textWidth / 6.0f;
13320                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
13321
13322                 textView.invalidate();
13323                 mChoreographer.postFrameCallback(mStartCallback);
13324             }
13325         }
13326
13327         float getGhostOffset() {
13328             return mGhostOffset;
13329         }
13330
13331         float getScroll() {
13332             return mScroll;
13333         }
13334
13335         float getMaxFadeScroll() {
13336             return mMaxFadeScroll;
13337         }
13338
13339         boolean shouldDrawLeftFade() {
13340             return mScroll <= mFadeStop;
13341         }
13342
13343         boolean shouldDrawGhost() {
13344             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
13345         }
13346
13347         boolean isRunning() {
13348             return mStatus == MARQUEE_RUNNING;
13349         }
13350
13351         boolean isStopped() {
13352             return mStatus == MARQUEE_STOPPED;
13353         }
13354     }
13355
13356     private class ChangeWatcher implements TextWatcher, SpanWatcher {
13357
13358         private CharSequence mBeforeText;
13359
13360         public void beforeTextChanged(CharSequence buffer, int start,
13361                                       int before, int after) {
13362             if (DEBUG_EXTRACT) {
13363                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
13364                         + " before=" + before + " after=" + after + ": " + buffer);
13365             }
13366
13367             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
13368                 mBeforeText = mTransformed.toString();
13369             }
13370
13371             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
13372         }
13373
13374         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
13375             if (DEBUG_EXTRACT) {
13376                 Log.v(LOG_TAG, "onTextChanged start=" + start
13377                         + " before=" + before + " after=" + after + ": " + buffer);
13378             }
13379             TextView.this.handleTextChanged(buffer, start, before, after);
13380
13381             if (AccessibilityManager.getInstance(mContext).isEnabled()
13382                     && (isFocused() || isSelected() && isShown())) {
13383                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
13384                 mBeforeText = null;
13385             }
13386         }
13387
13388         public void afterTextChanged(Editable buffer) {
13389             if (DEBUG_EXTRACT) {
13390                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
13391             }
13392             TextView.this.sendAfterTextChanged(buffer);
13393
13394             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
13395                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
13396             }
13397         }
13398
13399         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
13400             if (DEBUG_EXTRACT) {
13401                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
13402                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
13403             }
13404             TextView.this.spanChange(buf, what, s, st, e, en);
13405         }
13406
13407         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
13408             if (DEBUG_EXTRACT) {
13409                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
13410             }
13411             TextView.this.spanChange(buf, what, -1, s, -1, e);
13412         }
13413
13414         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
13415             if (DEBUG_EXTRACT) {
13416                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
13417             }
13418             TextView.this.spanChange(buf, what, s, -1, e, -1);
13419         }
13420     }
13421 }